From 826140a21dcaaab59c92fd7d310745f1320808c0 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Mon, 16 Sep 2024 14:21:54 +0200 Subject: [PATCH] add info button to dashboard, details page, qr codes --- go.mod | 1 + go.sum | 2 ++ internal/database/database.go | 31 +++++++++-------- internal/handlers/handlers.go | 56 ++++++++++++++++++++++++++++++- internal/templates/dashboard.html | 1 + internal/templates/details.html | 36 ++++++++++++++++++++ 6 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 internal/templates/details.html diff --git a/go.mod b/go.mod index 7d7dd19..9641dbd 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/google/safebrowsing v0.0.0-20190624211811-bbf0d20d26b3 github.com/gorilla/sessions v1.2.1 github.com/joho/godotenv v1.5.1 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e golang.org/x/crypto v0.9.0 modernc.org/sqlite v1.22.1 ) diff --git a/go.sum b/go.sum index 8875984..fedb862 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/rakyll/statik v0.1.5/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6 github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= diff --git a/internal/database/database.go b/internal/database/database.go index 0f65589..4031677 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -27,6 +27,7 @@ type URL struct { CreatedAt time.Time Clicks int Password string + QRCode string } func NewDB(dbPath string) (*DB, error) { @@ -55,16 +56,17 @@ func initSchema(db *sql.DB) error { password TEXT NOT NULL ); - CREATE TABLE IF NOT EXISTS urls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - url TEXT NOT NULL, - key TEXT UNIQUE NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - clicks INTEGER DEFAULT 0, - password TEXT, - FOREIGN KEY (user_id) REFERENCES users(id) - ); + CREATE TABLE IF NOT EXISTS urls ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + url TEXT NOT NULL, + key TEXT UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + clicks INTEGER DEFAULT 0, + password TEXT, + qr_code TEXT, -- Add this line to store the QR code in base64 format + FOREIGN KEY (user_id) REFERENCES users(id) + ); ` _, err := db.Exec(schema) @@ -103,14 +105,14 @@ func (db *DB) GetUserByUsername(username string) (*User, error) { return &user, nil } -func (db *DB) InsertURL(url, key string, userID int64, password string) error { - stmt, err := db.Prepare("INSERT INTO urls (url, key, user_id, password) VALUES (?, ?, ?, ?)") +func (db *DB) InsertURL(url, key string, userID int64, password string, qrCode string) error { + stmt, err := db.Prepare("INSERT INTO urls (url, key, user_id, password, qr_code) VALUES (?, ?, ?, ?, ?)") if err != nil { return fmt.Errorf("error preparing statement: %w", err) } defer stmt.Close() - _, err = stmt.Exec(url, key, userID, password) + _, err = stmt.Exec(url, key, userID, password, qrCode) // Add qrCode as an argument if err != nil { return fmt.Errorf("error inserting URL: %w", err) } @@ -179,7 +181,8 @@ func (db *DB) DeleteURL(id int64) error { func (db *DB) GetURLByID(id int64) (*URL, error) { var url URL - err := db.QueryRow("SELECT id, user_id, url, key, created_at, clicks, password FROM urls WHERE id = ?", id).Scan(&url.ID, &url.UserID, &url.URL, &url.Key, &url.CreatedAt, &url.Clicks, &url.Password) + err := db.QueryRow("SELECT id, user_id, url, key, created_at, clicks, password, qr_code FROM urls WHERE id = ?", id).Scan( + &url.ID, &url.UserID, &url.URL, &url.Key, &url.CreatedAt, &url.Clicks, &url.Password, &url.QRCode) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("no URL found for id: %d", id) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 72ac566..9925ac6 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -1,6 +1,8 @@ package handlers import ( + "encoding/base64" + "fmt" "html/template" "log" "net/http" @@ -15,6 +17,7 @@ import ( "github.com/artem-streltsov/url-shortener/internal/safebrowsing" "github.com/artem-streltsov/url-shortener/internal/utils" "github.com/gorilla/sessions" + "github.com/skip2/go-qrcode" "golang.org/x/crypto/bcrypt" ) @@ -49,6 +52,7 @@ func (h *Handler) Routes() http.Handler { mux.HandleFunc("/dashboard", h.dashboardHandler) mux.HandleFunc("/edit/", h.editURLHandler) mux.HandleFunc("/delete/", h.deleteURLHandler) + mux.HandleFunc("/details/", h.urlDetailsHandler) rl := middleware.NewRateLimiter(100, time.Minute) return middleware.LoggingMiddleware(middleware.RateLimitingMiddleware(rl)(mux)) @@ -170,7 +174,18 @@ func (h *Handler) newURLHandler(w http.ResponseWriter, r *http.Request) { hashedPassword = string(hash) } - if err := h.db.InsertURL(url, key, user.ID, hashedPassword); err != nil { + shortURL := fmt.Sprintf("http://%s/r/%s", r.Host, key) + qrCode, err := qrcode.Encode(shortURL, qrcode.Medium, 256) + if err != nil { + session.AddFlash("Error generating QR code", "error") + session.Save(r, w) + http.Redirect(w, r, "/new", http.StatusSeeOther) + return + } + + qrCodeBase64 := base64.StdEncoding.EncodeToString(qrCode) + + if err := h.db.InsertURL(url, key, user.ID, hashedPassword, qrCodeBase64); err != nil { session.AddFlash("Error inserting URL into database", "error") session.Save(r, w) http.Redirect(w, r, "/new", http.StatusSeeOther) @@ -561,3 +576,42 @@ func (h *Handler) deleteURLHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/dashboard", http.StatusSeeOther) } + +func (h *Handler) urlDetailsHandler(w http.ResponseWriter, r *http.Request) { + urlID, err := strconv.ParseInt(strings.TrimPrefix(r.URL.Path, "/details/"), 10, 64) + if err != nil { + http.Error(w, "Invalid URL ID", http.StatusBadRequest) + return + } + + url, err := h.db.GetURLByID(urlID) + if err != nil { + http.Error(w, "URL not found", http.StatusNotFound) + return + } + + shortURL := fmt.Sprintf("http://%s/r/%s", r.Host, url.Key) + qrCode, err := qrcode.Encode(shortURL, qrcode.Medium, 256) + if err != nil { + http.Error(w, "Error generating QR code", http.StatusInternalServerError) + return + } + + data := struct { + URL *database.URL + QRCode string + Host string + ShortURL string + }{ + URL: url, + QRCode: base64.StdEncoding.EncodeToString(qrCode), + Host: r.Host, + ShortURL: shortURL, + } + + err = h.templates.ExecuteTemplate(w, "details.html", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} diff --git a/internal/templates/dashboard.html b/internal/templates/dashboard.html index eb6d637..7fa3047 100644 --- a/internal/templates/dashboard.html +++ b/internal/templates/dashboard.html @@ -73,6 +73,7 @@

Your Shortened URLs

{{.Clicks}}
+ Details Edit Delete
diff --git a/internal/templates/details.html b/internal/templates/details.html new file mode 100644 index 0000000..67fff15 --- /dev/null +++ b/internal/templates/details.html @@ -0,0 +1,36 @@ + + + + + + URL Details + + + +
+

Details for Short URL

+
+
+

Original URL:

+

{{.URL.URL}}

+ +

Short URL:

+

{{.ShortURL}}

+ +

Clicks:

+

{{.URL.Clicks}}

+
+ +
+

QR Code

+ QR Code +
+
+ + +
+ + +