Compare commits

...

12 Commits

Author SHA1 Message Date
586a3b1cd8 move from deadbeef.codes to stevenpolley.net
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-28 11:58:40 -06:00
cacbfa66fb no longer need to manually initialize db
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
2024-01-01 11:15:53 -07:00
bb4112e145 initialize database if table doesn't exist
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-01 10:39:16 -07:00
022327d539 update go module
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-10 12:28:00 -06:00
ef7d5411af a new age - go mod init
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2021-03-23 19:06:56 -06:00
98f50317be Update comments regarding strange types
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-06 01:06:21 +00:00
15c8ac5f68 add maintainer to Dockerfile
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-04 21:08:59 +00:00
53eaf066b3 Include requirements and how to build application
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-20 20:55:48 -06:00
596835a0a6 Improve build container section
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-20 20:50:44 -06:00
214bdd0358 Reorganize sections on readme to make more sense
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-20 20:44:54 -06:00
d432521dae wildcard CORS is bad. Make note of this, not fixed
All checks were successful
continuous-integration/drone/push Build is passing
2020-06-20 20:42:42 -06:00
86ffcb6f3b Provide example front end code 2020-06-20 20:42:03 -06:00
7 changed files with 104 additions and 24 deletions

View File

@ -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

View File

@ -1,5 +1,5 @@
FROM scratch
LABEL maintainer="himself@stevenpolley.net"
COPY siteviewcounter .
EXPOSE 8080

View File

@ -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,10 +41,8 @@ version: '3.7'
services:
counter:
image: registry.deadbeef.codes/siteviewcounter:latest
image: siteviewcounter:latest
restart: always
depends_on:
- traefik
expose:
- "8080"
environment:
@ -67,4 +65,35 @@ services:
- MYSQL_DATABASE=counter
- TZ=America/Edmonton
```
```
### Example front end usage
You can pretty much implement this in your front end however you want, you just need to make a GET request to whatever endpoint the counter container is running at. This is how I use it though...
```html
<html>
<head>
<script>
var counterReq = new XMLHttpRequest();
counterReq.onreadystatechange = function() {
console.log("counterReq ready state is " + this.readyState);
if (this.readyState == 4) {
console.log("counterReq status is " + this.status);
if (this.status == 200) {
document.getElementById("counter").innerHTML = this.responseText + " unique visitors"
} else { // failed to load
console.log("failed to load counter module")
}
}
}
counterReq.open("GET", "https://counter.example.com", true);
counterReq.send();
</script>
</head>
<body>
<div id="counter"></div>
</body>
</html>
```

View File

@ -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
View 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
View 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=

11
main.go
View File

@ -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)
@ -78,7 +85,9 @@ func main() {
// HTTP handler function
func countHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
// CORS header change required
// CORS header change required.
//TBD wildcard is bad because it could allow illegitmate visits to be recorded if someone was nefarious and embedded
// front end code on a different website than your own. Need to implement environment variable to set allowed origin.
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write([]byte(strconv.Itoa(uniqueVisits)))