Compare commits
10 Commits
d432521dae
...
master
Author | SHA1 | Date | |
---|---|---|---|
586a3b1cd8 | |||
cacbfa66fb | |||
bb4112e145 | |||
022327d539 | |||
ef7d5411af | |||
98f50317be | |||
15c8ac5f68 | |||
53eaf066b3 | |||
596835a0a6 | |||
214bdd0358 |
@ -3,7 +3,7 @@ name: default
|
|||||||
|
|
||||||
workspace:
|
workspace:
|
||||||
base: /go
|
base: /go
|
||||||
path: src/deadbeef.codes/steven/siteviewcounter
|
path: src/code.stevenpolley.net/steven/siteviewcounter
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
@ -22,4 +22,4 @@ steps:
|
|||||||
- name: package in docker container
|
- name: package in docker container
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
repo: registry.deadbeef.codes/siteviewcounter
|
repo: registry.stevenpolley.net/siteviewcounter
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
FROM scratch
|
FROM scratch
|
||||||
|
LABEL maintainer="himself@stevenpolley.net"
|
||||||
COPY siteviewcounter .
|
COPY siteviewcounter .
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
34
README.md
34
README.md
@ -4,31 +4,31 @@
|
|||||||
|
|
||||||
A simple view counter for a website
|
A simple view counter for a website
|
||||||
|
|
||||||
### Database initialization
|
### Requirements
|
||||||
|
|
||||||
The following SQL will initialize the database for this application. No automigrate / initialization is done upon first running the application, so this must be ran by an administrator.
|
* Go
|
||||||
|
* Docker
|
||||||
|
* Docker Compose (Optional) or Kubernetes (Optional)
|
||||||
|
|
||||||
```sql
|
### Build Application
|
||||||
SET NAMES utf8;
|
|
||||||
SET time_zone = '+00:00';
|
|
||||||
SET foreign_key_checks = 0;
|
|
||||||
|
|
||||||
CREATE DATABASE `counter` /*!40100 DEFAULT CHARACTER SET latin1 */;
|
```bash
|
||||||
USE `counter`;
|
|
||||||
|
|
||||||
CREATE TABLE `visit` (
|
go build -a -ldflags '-w'
|
||||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`ip_address` varchar(15) NOT NULL,
|
|
||||||
`visits` int(11) NOT NULL,
|
|
||||||
`last_visited` datetime NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build Container
|
### Build Container
|
||||||
|
|
||||||
Disclaimer! If you use this, you'll need to build the container yourself. My registry is used for my internal infrastructure only and is not publicly available.
|
Disclaimer! If you use this, you'll need to build the container yourself. I have a CICD pipeline setup, but my registry is used for my internal infrastructure only and is not publicly available.
|
||||||
|
|
||||||
|
Because this is a staticly linked binary with no external runtime dependancies, the container literally only contains the binary file, keeping it clean and low in size (6.3MB). I never did understand why people include operating systems in containers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
docker build -t siteviewconter:latest .
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Example docker-compose.yml
|
### Example docker-compose.yml
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ version: '3.7'
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
counter:
|
counter:
|
||||||
image: registry.deadbeef.codes/siteviewcounter:latest
|
image: siteviewcounter:latest
|
||||||
restart: always
|
restart: always
|
||||||
expose:
|
expose:
|
||||||
- "8080"
|
- "8080"
|
||||||
|
@ -6,11 +6,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Configuration holds the DSN connection string and a resource Semaphore to limit the number of active connections
|
// 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 {
|
type Configuration struct {
|
||||||
DSN string
|
DSN string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection represents a single connection to the database, however there may be many instances / connections
|
// 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 {
|
type Connection struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
@ -83,3 +86,31 @@ func (conf Configuration) Connect() (*Connection, error) {
|
|||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitializeDatabase will check if tables exist in the database, and if not then create them.
|
||||||
|
func (conn Connection) InitializeDatabase() error {
|
||||||
|
rows, err := conn.DB.Query(`SHOW TABLES`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SHOW TABLES query failed: %v", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
if rows.Next() { // Table already exists, leave with no error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table does not exist, create it
|
||||||
|
_, err = conn.DB.Exec(`CREATE TABLE visit (
|
||||||
|
id int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
ip_address varchar(15) NOT NULL,
|
||||||
|
visits int(11) NOT NULL,
|
||||||
|
last_visited datetime NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create table: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
7
go.mod
Normal file
7
go.mod
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module deadbeef.codes/steven/siteviewcounter
|
||||||
|
|
||||||
|
go 1.22
|
||||||
|
|
||||||
|
require github.com/go-sql-driver/mysql v1.8.1
|
||||||
|
|
||||||
|
require filippo.io/edwards25519 v1.1.0 // indirect
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
7
main.go
7
main.go
@ -57,6 +57,13 @@ func init() {
|
|||||||
log.Fatalf("failed to connect to database: %v", err)
|
log.Fatalf("failed to connect to database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if database needs to be initialized (create tables)
|
||||||
|
// does nothing if tables exist
|
||||||
|
err = dbConn.InitializeDatabase()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to initialize database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
uniqueVisits, err = dbConn.GetUniqueVisits()
|
uniqueVisits, err = dbConn.GetUniqueVisits()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to get number of unique visits from database: %v", err)
|
log.Fatalf("failed to get number of unique visits from database: %v", err)
|
||||||
|
Reference in New Issue
Block a user