Skip to content

Commit 0fd1224

Browse files
committed
Initial commit
0 parents  commit 0fd1224

File tree

12 files changed

+489
-0
lines changed

12 files changed

+489
-0
lines changed

.air.toml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
root = "."
2+
testdata_dir = "testdata"
3+
tmp_dir = "tmp"
4+
5+
[build]
6+
args_bin = []
7+
bin = "./main"
8+
cmd = "make build"
9+
delay = 1000
10+
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
11+
exclude_file = []
12+
exclude_regex = ["_test.go"]
13+
exclude_unchanged = false
14+
follow_symlink = false
15+
full_bin = ""
16+
include_dir = []
17+
include_ext = ["go", "tpl", "tmpl", "html"]
18+
include_file = []
19+
kill_delay = "0s"
20+
log = "build-errors.log"
21+
poll = false
22+
poll_interval = 0
23+
post_cmd = []
24+
pre_cmd = []
25+
rerun = false
26+
rerun_delay = 500
27+
send_interrupt = false
28+
stop_on_error = false
29+
30+
[color]
31+
app = ""
32+
build = "yellow"
33+
main = "magenta"
34+
runner = "green"
35+
watcher = "cyan"
36+
37+
[log]
38+
main_only = false
39+
time = false
40+
41+
[misc]
42+
clean_on_exit = false
43+
44+
[screen]
45+
clear_on_rebuild = false
46+
keep_scroll = true

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.exe~
4+
*.dll
5+
*.so
6+
*.dylib
7+
8+
# Test binary, built with "go test -c"
9+
*.test
10+
11+
# Output of the go coverage tool, specifically when used with LiteIDE
12+
*.out
13+
14+
# Dependency directories (remove the comment below to include it)
15+
# vendor/
16+
17+
# Go workspace file
18+
go.work
19+
tmp/
20+
21+
# IDE specific files
22+
.vscode
23+
.idea
24+
25+
# .env file
26+
.env
27+
28+
# Project build
29+
main

Makefile

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Simple Makefile for a Go project
2+
3+
# Build the application
4+
all: build
5+
6+
build:
7+
@echo "Building..."
8+
9+
@go build -o main cmd/api/main.go
10+
11+
# Run the application
12+
run:
13+
@go run cmd/api/main.go
14+
15+
# Create DB container
16+
docker-run:
17+
@if docker compose up 2>/dev/null; then \
18+
: ; \
19+
else \
20+
echo "Falling back to Docker Compose V1"; \
21+
docker-compose up; \
22+
fi
23+
24+
# Shutdown DB container
25+
docker-down:
26+
@if docker compose down 2>/dev/null; then \
27+
: ; \
28+
else \
29+
echo "Falling back to Docker Compose V1"; \
30+
docker-compose down; \
31+
fi
32+
33+
# Test the application
34+
test:
35+
@echo "Testing..."
36+
@go test ./tests -v
37+
38+
# Clean the binary
39+
clean:
40+
@echo "Cleaning..."
41+
@rm -f main
42+
43+
# Live Reload
44+
watch:
45+
@if command -v air > /dev/null; then \
46+
air; \
47+
echo "Watching...";\
48+
else \
49+
read -p "Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] " choice; \
50+
if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \
51+
go install github.com/cosmtrek/air@latest; \
52+
air; \
53+
echo "Watching...";\
54+
else \
55+
echo "You chose not to install air. Exiting..."; \
56+
exit 1; \
57+
fi; \
58+
fi
59+
60+
.PHONY: all build run test clean

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Project api
2+
3+
One Paragraph of project description goes here
4+
5+
## Getting Started
6+
7+
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
8+
9+
## MakeFile
10+
11+
run all make commands with clean tests
12+
```bash
13+
make all build
14+
```
15+
16+
build the application
17+
```bash
18+
make build
19+
```
20+
21+
run the application
22+
```bash
23+
make run
24+
```
25+
26+
Create DB container
27+
```bash
28+
make docker-run
29+
```
30+
31+
Shutdown DB container
32+
```bash
33+
make docker-down
34+
```
35+
36+
live reload the application
37+
```bash
38+
make watch
39+
```
40+
41+
run the test suite
42+
```bash
43+
make test
44+
```
45+
46+
clean up binary from the last build
47+
```bash
48+
make clean
49+
```

cmd/api/main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package main
2+
3+
import (
4+
"api/internal/server"
5+
"fmt"
6+
)
7+
8+
func main() {
9+
10+
server := server.NewServer()
11+
12+
err := server.ListenAndServe()
13+
if err != nil {
14+
panic(fmt.Sprintf("cannot start server: %s", err))
15+
}
16+
}

docker-compose.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: '3.8'
2+
3+
services:
4+
psql:
5+
image: postgres:latest
6+
environment:
7+
POSTGRES_DB: ${DB_DATABASE}
8+
POSTGRES_USER: ${DB_USERNAME}
9+
POSTGRES_PASSWORD: ${DB_PASSWORD}
10+
ports:
11+
- "${DB_PORT}:5432"
12+
volumes:
13+
- psql_volume:/var/lib/postgresql/data
14+
15+
volumes:
16+
psql_volume:

go.mod

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module api
2+
3+
go 1.22.3
4+
5+
require (
6+
github.com/go-chi/chi/v5 v5.0.12
7+
github.com/jackc/pgx/v5 v5.6.0
8+
github.com/joho/godotenv v1.5.1
9+
)
10+
11+
require (
12+
github.com/jackc/pgpassfile v1.0.0 // indirect
13+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
14+
github.com/jackc/puddle/v2 v2.2.1 // indirect
15+
golang.org/x/crypto v0.17.0 // indirect
16+
golang.org/x/sync v0.1.0 // indirect
17+
golang.org/x/text v0.14.0 // indirect
18+
)

go.sum

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
5+
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
6+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
7+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
8+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
9+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
10+
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
11+
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
12+
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
13+
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
14+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
15+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
16+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
17+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
18+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
19+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
20+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
21+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
22+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
23+
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
24+
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
25+
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
26+
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
27+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
28+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
29+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
30+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/database/database.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package database
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"log"
8+
"os"
9+
"strconv"
10+
"time"
11+
12+
_ "github.com/jackc/pgx/v5/stdlib"
13+
_ "github.com/joho/godotenv/autoload"
14+
)
15+
16+
// Service represents a service that interacts with a database.
17+
type Service interface {
18+
// Health returns a map of health status information.
19+
// The keys and values in the map are service-specific.
20+
Health() map[string]string
21+
22+
// Close terminates the database connection.
23+
// It returns an error if the connection cannot be closed.
24+
Close() error
25+
}
26+
27+
type service struct {
28+
db *sql.DB
29+
}
30+
31+
var (
32+
database = os.Getenv("DB_DATABASE")
33+
password = os.Getenv("DB_PASSWORD")
34+
username = os.Getenv("DB_USERNAME")
35+
port = os.Getenv("DB_PORT")
36+
host = os.Getenv("DB_HOST")
37+
dbInstance *service
38+
)
39+
40+
func New() Service {
41+
// Reuse Connection
42+
if dbInstance != nil {
43+
return dbInstance
44+
}
45+
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", username, password, host, port, database)
46+
db, err := sql.Open("pgx", connStr)
47+
if err != nil {
48+
log.Fatal(err)
49+
}
50+
dbInstance = &service{
51+
db: db,
52+
}
53+
return dbInstance
54+
}
55+
56+
// Health checks the health of the database connection by pinging the database.
57+
// It returns a map with keys indicating various health statistics.
58+
func (s *service) Health() map[string]string {
59+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
60+
defer cancel()
61+
62+
stats := make(map[string]string)
63+
64+
// Ping the database
65+
err := s.db.PingContext(ctx)
66+
if err != nil {
67+
stats["status"] = "down"
68+
stats["error"] = fmt.Sprintf("db down: %v", err)
69+
log.Fatalf(fmt.Sprintf("db down: %v", err)) // Log the error and terminate the program
70+
return stats
71+
}
72+
73+
// Database is up, add more statistics
74+
stats["status"] = "up"
75+
stats["message"] = "It's healthy"
76+
77+
// Get database stats (like open connections, in use, idle, etc.)
78+
dbStats := s.db.Stats()
79+
stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
80+
stats["in_use"] = strconv.Itoa(dbStats.InUse)
81+
stats["idle"] = strconv.Itoa(dbStats.Idle)
82+
stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10)
83+
stats["wait_duration"] = dbStats.WaitDuration.String()
84+
stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)
85+
stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)
86+
87+
// Evaluate stats to provide a health message
88+
if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example
89+
stats["message"] = "The database is experiencing heavy load."
90+
}
91+
92+
if dbStats.WaitCount > 1000 {
93+
stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks."
94+
}
95+
96+
if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 {
97+
stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings."
98+
}
99+
100+
if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 {
101+
stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern."
102+
}
103+
104+
return stats
105+
}
106+
107+
// Close closes the database connection.
108+
// It logs a message indicating the disconnection from the specific database.
109+
// If the connection is successfully closed, it returns nil.
110+
// If an error occurs while closing the connection, it returns the error.
111+
func (s *service) Close() error {
112+
log.Printf("Disconnected from database: %s", database)
113+
return s.db.Close()
114+
}

0 commit comments

Comments
 (0)