Skip to content

Commit

Permalink
add info button to dashboard, details page, qr codes
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-streltsov committed Sep 16, 2024
1 parent 66d2ffb commit 826140a
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 15 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
31 changes: 17 additions & 14 deletions internal/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type URL struct {
CreatedAt time.Time
Clicks int
Password string
QRCode string
}

func NewDB(dbPath string) (*DB, error) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
56 changes: 55 additions & 1 deletion internal/handlers/handlers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package handlers

import (
"encoding/base64"
"fmt"
"html/template"
"log"
"net/http"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
1 change: 1 addition & 0 deletions internal/templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ <h2>Your Shortened URLs</h2>
<td>{{.Clicks}}</td>
<td>
<div class="btn-group" role="group">
<a href="/details/{{.ID}}" class="btn btn-sm btn-info">Details</a>
<a href="/edit/{{.ID}}" class="btn btn-sm btn-primary">Edit</a>
<a href="/delete/{{.ID}}" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this URL?')">Delete</a>
</div>
Expand Down
36 changes: 36 additions & 0 deletions internal/templates/details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>URL Details</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h1>Details for Short URL</h1>
<div class="row">
<div class="col-md-6">
<h3>Original URL:</h3>
<p>{{.URL.URL}}</p>

<h3>Short URL:</h3>
<p><a href="http://{{.Host}}/r/{{.URL.Key}}">{{.ShortURL}}</a></p>

<h3>Clicks:</h3>
<p>{{.URL.Clicks}}</p>
</div>

<div class="col-md-6 text-center">
<h3>QR Code</h3>
<img src="data:image/png;base64,{{.QRCode}}" alt="QR Code" class="img-fluid">
</div>
</div>

<div class="mt-3">
<a href="/dashboard" class="btn btn-secondary">Back to Dashboard</a>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

0 comments on commit 826140a

Please sign in to comment.