siteviewcounter/countersql/database.go

89 lines
2.7 KiB
Go

package countersql
import (
"database/sql"
"fmt"
)
// Configuration holds the DSN connection string and a resource Semaphore to limit the number of active connections
// Note: This is a copied design pattern I've used for other projects which had much more data inside a Configuration struct.
// Examples include semaphores and persistent connection pools, which I've stripped out for this project.
type Configuration struct {
DSN string
}
// Connection represents a single connection to the database, however there may be many instances / connections
// Note: This is a copied design pattern I've used for other projects which had much more data inside a Connection struct.
type Connection struct {
DB *sql.DB
}
// HasIPVisited returns true only if the IP address is present in the database
func (conn Connection) HasIPVisited(ipAddress string) (bool, error) {
rows, err := conn.DB.Query(`SELECT id FROM visit WHERE ip_address = ? LIMIT 1;`, ipAddress)
if err != nil {
return false, fmt.Errorf("SELECT query failed: %v", err)
}
defer rows.Close()
if !rows.Next() {
return false, nil
}
return true, nil
}
// GetUniqueVisits counts the number of entires in the visits table, representing one unique source IP address per row
func (conn Connection) GetUniqueVisits() (int, error) {
rows, err := conn.DB.Query(`SELECT COUNT(*) FROM visit`)
if err != nil {
return 0, fmt.Errorf("SELECT query failed: %v", err)
}
defer rows.Close()
if !rows.Next() {
return 0, nil
}
var uniqueVists int
if err := rows.Scan(&uniqueVists); err != nil {
return 0, fmt.Errorf("failed to scan database row: %v", err)
}
return uniqueVists, nil
}
// IncrementVisitor accepts an IP address and updates the row matching that IP address
// It does not check if the row matching the IP address supplied exists or not
func (conn Connection) IncrementVisitor(ipAddress string) error {
_, err := conn.DB.Exec(`UPDATE visit SET visits = visits + 1, last_visited = NOW() WHERE ip_address = ?`, ipAddress)
if err != nil {
return fmt.Errorf("UPDATE query failed: %v", err)
}
return nil
}
// AddVisitor accepts an IP address and inserts a new row into the database as this represents a new unique visitor
func (conn Connection) AddVisitor(ipAddress string) error {
_, err := conn.DB.Exec(`INSERT INTO visit (ip_address, visits, last_visited) VALUES (?, '0', NOW())`, ipAddress)
if err != nil {
return fmt.Errorf("INSERT query failed: %v", err)
}
return nil
}
// Connect will open a TCP connection to the database with the given DSN configuration
func (conf Configuration) Connect() (*Connection, error) {
conn := &Connection{}
var err error
conn.DB, err = sql.Open("mysql", conf.DSN)
if err != nil {
return nil, fmt.Errorf("failed to open db: %v", err)
}
return conn, nil
}