From 0ddeff0c905644b0be92f9a7598a7da44febc7db Mon Sep 17 00:00:00 2001 From: Bhasher Date: Fri, 22 Dec 2023 21:29:33 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Dockerfile | 10 ++++ create.html | 17 +++++++ dashboard.html | 28 +++++++++++ db.go | 66 ++++++++++++++++++++++++ go.mod | 8 +++ go.sum | 4 ++ handlers.go | 134 +++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 39 ++++++++++++++ new.html | 63 +++++++++++++++++++++++ 10 files changed, 370 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 create.html create mode 100644 dashboard.html create mode 100644 db.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handlers.go create mode 100644 main.go create mode 100644 new.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1dffd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.sqlite diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..019af81 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +# Dockerfile +FROM golang:latest + +WORKDIR /app + +COPY . . + +RUN go build -o main . + +CMD ["/app/main"] diff --git a/create.html b/create.html new file mode 100644 index 0000000..618b247 --- /dev/null +++ b/create.html @@ -0,0 +1,17 @@ + +
+

Your Tracking Pixel Link

+ + +
+ + + \ No newline at end of file diff --git a/dashboard.html b/dashboard.html new file mode 100644 index 0000000..39d412b --- /dev/null +++ b/dashboard.html @@ -0,0 +1,28 @@ + + + + + Dashboard + + + +

Tracking Pixel Dashboard

+ + + + + + + + {{range .}} + + + + + + + {{end}} +
TitleTotal ViewsUnique ViewsLink
{{.Title}}{{.TotalViews}}{{.UniqueViews}}link
+ + + \ No newline at end of file diff --git a/db.go b/db.go new file mode 100644 index 0000000..64ced72 --- /dev/null +++ b/db.go @@ -0,0 +1,66 @@ +package main + +import ( + "database/sql" + "log" +) + +var db *sql.DB + +func initDB(filepath string) *sql.DB { + db, err := sql.Open("sqlite3", filepath) + if err != nil { + log.Fatal(err) + } + + // Create tables if they do not exist + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS pixels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uuid TEXT UNIQUE, + title TEXT + ); + CREATE TABLE IF NOT EXISTS stats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pixel_id INTEGER, + view_time DATETIME DEFAULT CURRENT_TIMESTAMP, + ip TEXT, + user_agent TEXT, + fingerprint TEXT, + FOREIGN KEY(pixel_id) REFERENCES pixels(id) + ); + `) + if err != nil { + log.Fatal(err) + } + + return db +} + +func addStat(pixelID int, ip, userAgent string, fingerprint string) error { + statement, err := db.Prepare("INSERT INTO stats (pixel_id, ip, user_agent, fingerprint) VALUES (?, ?, ?, ?)") + if err != nil { + return err + } + _, err = statement.Exec(pixelID, ip, userAgent, fingerprint) + return err +} + +func getPixelIDFromUUID(uuid string) (int, error) { + var id int + row := db.QueryRow("SELECT id FROM pixels WHERE uuid = ?", uuid) + err := row.Scan(&id) + if err != nil { + return 0, err + } + return id, nil +} + +func savePixel(title, uuid string) error { + statement, err := db.Prepare("INSERT INTO pixels (title, uuid) VALUES (?, ?)") + if err != nil { + return err + } + _, err = statement.Exec(title, uuid) + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5a95183 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module trakr + +go 1.21.4 + +require ( + github.com/google/uuid v1.5.0 + github.com/mattn/go-sqlite3 v1.14.19 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b1bc515 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..f34eea0 --- /dev/null +++ b/handlers.go @@ -0,0 +1,134 @@ +package main + +import ( + "crypto/sha256" + "fmt" + "html/template" + "log" + "net/http" + "strings" + + "github.com/google/uuid" +) + +func newPixelPageHandler(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFiles("new.html") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = tmpl.Execute(w, nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func createPixelHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) + return + } + + if err := r.ParseForm(); err != nil { + http.Error(w, "Error parsing form", http.StatusInternalServerError) + return + } + + title := r.FormValue("title") + + pixelUUID := uuid.New().String() + + err := savePixel(title, pixelUUID) + if err != nil { + http.Error(w, "Error saving pixel to the database", http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/dashboard", http.StatusSeeOther) +} + +func pixelHandler(w http.ResponseWriter, r *http.Request) { + pathParts := strings.Split(r.URL.Path, "/") + if len(pathParts) < 3 { + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + uuid := pathParts[2] + + // Get the pixel ID from the database + pixelID, err := getPixelIDFromUUID(uuid) + if err != nil { + log.Println("Error getting pixel ID:", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + addr := strings.Split(r.RemoteAddr, ":") + + rawFingerprint := fmt.Sprintf( + "%s|%s|%s", + addr[0], + r.Header.Get("User-Agent"), + r.Header.Get("Accept-Language"), + ) + hash := sha256.Sum256([]byte(rawFingerprint)) + fingerprint := string(hash[:]) + + err = addStat(pixelID, addr[0], r.UserAgent(), fingerprint) + if err != nil { + log.Println("Error updating stats:", err) + } + + // Serve the pixel image + w.Header().Set("Content-Type", "image/gif") + w.Write(onePixelGIF) +} + +func dashboardHandler(w http.ResponseWriter, r *http.Request) { + query := ` + SELECT p.title, p.uuid, COUNT(s.id) AS total_views, COUNT(DISTINCT s.ip) AS unique_views + FROM pixels p + LEFT JOIN stats s ON p.id = s.pixel_id + GROUP BY p.id;` + + rows, err := db.Query(query) + if err != nil { + http.Error(w, "Error fetching data", http.StatusInternalServerError) + log.Println("Error fetching data:", err) + return + } + defer rows.Close() + + var stats []PixelStats + for rows.Next() { + var s PixelStats + if err := rows.Scan(&s.Title, &s.UUID, &s.TotalViews, &s.UniqueViews); err != nil { + http.Error(w, "Error reading data", http.StatusInternalServerError) + log.Println("Error reading data:", err) + return + } + stats = append(stats, s) + } + + // Handle any errors encountered during iteration + if err = rows.Err(); err != nil { + http.Error(w, "Error iterating data", http.StatusInternalServerError) + log.Println("Error iterating data:", err) + return + } + + // Render the template + tmpl, err := template.ParseFiles("dashboard.html") + if err != nil { + http.Error(w, "Error loading template", http.StatusInternalServerError) + return + } + + err = tmpl.Execute(w, stats) + if err != nil { + http.Error(w, "Error executing template", http.StatusInternalServerError) + return + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..2f2a0fb --- /dev/null +++ b/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + _ "github.com/mattn/go-sqlite3" +) + +var onePixelGIF = []byte{ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, + 0x01, 0x00, 0x80, 0xff, 0x00, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, + 0x01, 0x00, 0x3b, +} + +func main() { + db = initDB("data.sqlite") + + http.HandleFunc("/new", newPixelPageHandler) + http.HandleFunc("/create-pixel", createPixelHandler) + http.HandleFunc("/p/", pixelHandler) + http.HandleFunc("/dashboard", dashboardHandler) + + addr := ":8080" + + fmt.Printf("Server is up and running on http://localhost%s\n", addr) + + log.Fatal(http.ListenAndServe(addr, nil)) +} + +type PixelStats struct { + Title string + TotalViews int + UniqueViews int + UUID string +} diff --git a/new.html b/new.html new file mode 100644 index 0000000..0fd30b4 --- /dev/null +++ b/new.html @@ -0,0 +1,63 @@ + + + + + + Create New Tracking Pixel + + + + +
+

Create New Tracking Pixel

+
+ + +
+
+ + + \ No newline at end of file