From d9057f2aaae9bc97e26a2fbb02c79d5c889a230a Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Tue, 26 Nov 2024 22:20:10 -0600 Subject: [PATCH] Auto generate short links if empty --- go.mod | 9 +++++++-- go.sum | 2 ++ main.go | 36 ++++++++++++++++++++++++++++++++++++ static/index.html | 11 ++++++++--- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 528ea91..b109a0a 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,10 @@ module github.com/gopatchy/shortlinks -go 1.22 +go 1.22.0 -require github.com/lib/pq v1.10.9 +toolchain go1.23.3 + +require ( + github.com/lib/pq v1.10.9 + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f +) diff --git a/go.sum b/go.sum index aeddeae..51eebe0 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= diff --git a/main.go b/main.go index 3cc7f33..f85e1fa 100644 --- a/main.go +++ b/main.go @@ -7,14 +7,17 @@ import ( "log" "net/http" "os" + "time" _ "github.com/lib/pq" + "golang.org/x/exp/rand" ) type ShortLinks struct { tmpl *template.Template mux *http.ServeMux db *sql.DB + r *rand.Rand } type response struct { @@ -33,6 +36,7 @@ func NewShortLinks(db *sql.DB) (*ShortLinks, error) { tmpl: tmpl, mux: http.NewServeMux(), db: db, + r: rand.New(rand.NewSource(uint64(time.Now().UnixNano()))), } sl.mux.HandleFunc("GET /{$}", sl.serveRoot) @@ -88,6 +92,14 @@ func (sl *ShortLinks) serveSet(w http.ResponseWriter, r *http.Request) { short := r.Form.Get("short") + if short == "" { + short, err = sl.genShort() + if err != nil { + sendError(w, http.StatusInternalServerError, "genShort: %s", err) + return + } + } + long := r.Form.Get("long") if long == "" { sendError(w, http.StatusBadRequest, "long= param required") @@ -110,6 +122,30 @@ DO UPDATE SET long = $2; }) } +func (sl *ShortLinks) genShort() (string, error) { + for chars := 3; chars <= 10; chars++ { + b := make([]byte, chars) + + for i := range b { + b[i] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[sl.r.Intn(62)] + } + + short := string(b) + + exists := false + err := sl.db.QueryRow("SELECT EXISTS(SELECT 1 FROM links WHERE short = $1)", short).Scan(&exists) + if err != nil { + return "", fmt.Errorf("check exists: %w", err) + } + + if !exists { + return short, nil + } + } + + return "", fmt.Errorf("no available short link found") +} + func main() { port := os.Getenv("PORT") if port == "" { diff --git a/static/index.html b/static/index.html index ad88ad7..26f6c74 100644 --- a/static/index.html +++ b/static/index.html @@ -87,7 +87,10 @@ async function set() { document.getElementById('short-icon').setAttribute('name', 'check-square-fill'); document.getElementById('long-icon').setAttribute('name', 'check-square-fill'); - setShortItem(short, 'check-square-fill'); + + if (short != '') { + setShortItem(short, 'check-square-fill'); + } const params = new URLSearchParams(); params.set('short', short); @@ -98,14 +101,16 @@ async function set() { }); if (resp.status !== 200) { - error('Failed to set', await resp.text()); + error('Failed to set', (await resp.json()).message); return; } + const newShort = (await resp.json()).short; + if (document.getElementById('short').value == short && document.getElementById('long').value == long) { document.getElementById('short-icon').setAttribute('name', 'check-square'); document.getElementById('long-icon').setAttribute('name', 'check-square'); - setShortItem(short, 'check-square'); + setShortItem(newShort, 'check-square'); } }