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:
|
||||
base: /go
|
||||
path: src/deadbeef.codes/steven/siteviewcounter
|
||||
path: src/code.stevenpolley.net/steven/siteviewcounter
|
||||
|
||||
steps:
|
||||
|
||||
@ -22,4 +22,4 @@ steps:
|
||||
- name: package in docker container
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: registry.deadbeef.codes/siteviewcounter
|
||||
repo: registry.stevenpolley.net/siteviewcounter
|
||||
|
@ -1,5 +1,5 @@
|
||||
FROM scratch
|
||||
|
||||
LABEL maintainer="himself@stevenpolley.net"
|
||||
COPY siteviewcounter .
|
||||
|
||||
EXPOSE 8080
|
||||
|
34
README.md
34
README.md
@ -4,31 +4,31 @@
|
||||
|
||||
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
|
||||
SET NAMES utf8;
|
||||
SET time_zone = '+00:00';
|
||||
SET foreign_key_checks = 0;
|
||||
### Build Application
|
||||
|
||||
CREATE DATABASE `counter` /*!40100 DEFAULT CHARACTER SET latin1 */;
|
||||
USE `counter`;
|
||||
```bash
|
||||
|
||||
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;
|
||||
go build -a -ldflags '-w'
|
||||
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
@ -41,7 +41,7 @@ version: '3.7'
|
||||
services:
|
||||
|
||||
counter:
|
||||
image: registry.deadbeef.codes/siteviewcounter:latest
|
||||
image: siteviewcounter:latest
|
||||
restart: always
|
||||
expose:
|
||||
- "8080"
|
||||
|
@ -6,11 +6,14 @@ import (
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -83,3 +86,31 @@ func (conf Configuration) Connect() (*Connection, error) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get number of unique visits from database: %v", err)
|
||||
|
Reference in New Issue
Block a user