Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(likes counter): Implementing Likes Counter #4

Merged
merged 6 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ __debug_*
# DB files
BloTils.db
*.db
*.session.sql
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# BloTils
# **BLOTILS** aka Blog Utils

Minimal go server to count clapping
Utils for your static blog or pages
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"DBLocation": "./BloTils.db",
"Vacuum": "full",
"ForeignKeys": true
}
},
"StaticFiles": "static"
},

"Name": "BloTils",
Expand Down
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ require (
github.com/mattn/go-sqlite3 v1.14.22
)

require (
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/didip/tollbooth v4.0.2+incompatible // indirect
github.com/didip/tollbooth/v8 v8.0.1 // indirect
github.com/go-pkgz/expirable-cache/v3 v3.0.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
golang.org/x/time v0.9.0 // indirect
)

require (
github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/rs/cors v1.11.1
)

require (
Expand Down
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M=
github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY=
github.com/didip/tollbooth/v8 v8.0.1 h1:VAAapTo1t4Bn6bbpcHjuovwoa9u3JH++wgjbpWv+rB8=
github.com/didip/tollbooth/v8 v8.0.1/go.mod h1:oEd9l+ep373d7DmvKLc0a5gasPOev2mTewi6KPQBGJ4=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw=
github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec=
github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
Expand All @@ -22,9 +31,15 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
18 changes: 14 additions & 4 deletions src/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"os"
"path/filepath"
fp "path/filepath"
)

type App struct {
Expand All @@ -15,10 +16,11 @@ type App struct {
}

type Config struct {
ServerConfig server.ServerConfig
Name string
Version string
Env string
ServerConfig server.ServerConfig
Name string
Version string
Env string
BaseDirectory string
}

func (app *App) Start() {
Expand All @@ -32,6 +34,13 @@ func LoadConfig(filepath string) Config {
fmt.Println(err)
return Config{}
}
baseDir, err := os.Getwd()
if err != nil {
panic(err)
}
config.BaseDirectory = baseDir
config.ServerConfig.BaseDirectory = baseDir
config.ServerConfig.DB.DBBaseDirectory = fp.Join(baseDir, "src/db")
return config
}

Expand Down Expand Up @@ -65,5 +74,6 @@ func New(config Config) *App {
Config: config,
}
routes.RegisterRoutes(server)
routes.ServeStaticFiles(server)
return app
}
16 changes: 11 additions & 5 deletions src/app/app_logo.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
var logoStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#7D56F4"))
var subtitleStyle = lipgloss.NewStyle().
Padding(0, 22).
Bold(true)

const appLogo = `
██████ ██ ██████ ██████ ██ ██ ███████
Expand All @@ -21,11 +18,20 @@ const appLogo = `
██████ █████ ██████ ██ ██ █████ ███████
`

func getversion(c Config) string {
return fmt.Sprintf("Version : %s\n", c.Version)
}

func getPort(c Config) string {
return fmt.Sprintf("Server Running On %s:%d\n", c.ServerConfig.Host, c.ServerConfig.Port)
}

func logo(c Config) {
fmt.Print(logoStyle.Render(appLogo))
fmt.Println()
fmt.Printf("Version : %s", c.Version)
fmt.Print(subtitleStyle.Render(fmt.Sprintf("Server Running On %s:%d", c.ServerConfig.Host, c.ServerConfig.Port)))
fmt.Print("Utilities For Your Blog Engine\n")
fmt.Print(getversion(c))
fmt.Print(getPort(c))
fmt.Println()
fmt.Println()
}
29 changes: 17 additions & 12 deletions src/db/db.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package db provides functionality for interacting with the application's database.
package db

import (
Expand All @@ -6,6 +7,7 @@ import (
"fmt"
"log"
"os"
"path/filepath"

"github.com/golang-migrate/migrate"
"github.com/golang-migrate/migrate/database/sqlite3"
Expand All @@ -15,10 +17,11 @@ import (
)

type DB struct {
DBLocation string
Vacuum string
ForeignKeys bool
Connection *sql.DB
DBLocation string
Vacuum string
ForeignKeys bool
Connection *sql.DB
DBBaseDirectory string
}

func closeDB(new_db *sql.DB) {
Expand All @@ -32,7 +35,7 @@ func closeDB(new_db *sql.DB) {
func closeFile(fSrc source.Driver) {
defer func() {
if err := fSrc.Close(); err != nil {
log.Printf("Error Closing Migration Files %s", err)
log.Printf("Error Closing Migration Files: %s", err)
}
}()
}
Expand All @@ -56,7 +59,8 @@ func (db *DB) Initialize() error {
closeDB(new_db)
return err
}
err = runMigrations(new_db)
migrationFiles := filepath.Join(db.DBBaseDirectory, "migrations")
err = runMigrations(new_db, migrationFiles)
if err != nil {
log.Println("Error Running Migrations")
closeDB(new_db)
Expand All @@ -66,32 +70,33 @@ func (db *DB) Initialize() error {
return nil
}

func runMigrations(db *sql.DB) error {
func runMigrations(db *sql.DB, migrationFiles string) error {
instance, err := sqlite3.WithInstance(db, &sqlite3.Config{})
if err != nil {
log.Printf("Error Connecting With SQLite Instance %s", err)
log.Printf("Error Connecting With SQLite Instance: %s", err)
return err
}

fSrc, err := (&file.File{}).Open("./src/db/migrations")
fSrc, err := (&file.File{}).Open(migrationFiles)
print()
if err != nil {
closeFile(fSrc)
log.Printf("Error Getting Migration Files %s", err)
log.Printf("Error Getting Migration Files: %s", err)
return err
}

m, err := migrate.NewWithInstance("file", fSrc, "sqlite3", instance)
if err != nil {
closeFile(fSrc)
log.Printf("Error Creating Migration Instance %s", err)
log.Printf("Error Creating Migration Instance: %s", err)
return err
}
if err := m.Up(); err != nil {
if err == migrate.ErrNoChange {
log.Println("No Migrations To Run")
} else {
closeFile(fSrc)
log.Printf("Error While Running UP Migrations %s", err)
log.Printf("Error While Running UP Migrations: %s", err)
return err
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/db/migrations/000001_create_likes_table.down.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
DROP TABLE IF EXISTS sources;
DROP TABLE IF EXISTS liked_ips;
DROP TABLE IF EXISTS likes;
DROP TABLE IF EXISTS DomainSettings;
DROP TABLE IF EXISTS Domain;
DROP TABLE IF EXISTS Liked_IPs;
DROP TABLE IF EXISTS Likes;
46 changes: 31 additions & 15 deletions src/db/migrations/000001_create_likes_table.up.sql
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
CREATE TABLE IF NOT EXISTS sources (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Source TEXT,
Likes NUMERIC,
Comments NUMERIC,
CreatedTime DATETIME DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS DomainSettings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
likes NUMERIC check(
likes = 0
or likes = 1
),
comments NUMERIC check(
comments = 0
or comments = 1
),
created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS liked_ips (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
IP TEXT,
Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS Domain (
id INTEGER PRIMARY KEY AUTOINCREMENT,
settings_id INTEGER,
domain VARCHAR(255) UNIQUE,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (settings_id) REFERENCES DomainSettings(id)
);
CREATE TABLE IF NOT EXISTS likes (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Uri TEXT,
Count INTEGER,
Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
CREATE TABLE IF NOT EXISTS Liked_IPs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip VARCHAR(255) UNIQUE,
count INTEGER check(count >= 0),
domain VARCHAR(255),
path TEXT,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS Likes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uri TEXT UNIQUE,
count INTEGER check(count >= 0),
domain_id INTEGER,
FOREIGN KEY (domain_id) REFERENCES Domain(id)
);
72 changes: 62 additions & 10 deletions src/db/models.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,76 @@
// Package db provides functionality for interacting with the application's database.
package db

import "time"

type Source struct {
// DomainSettings represents the settings for a domain, including whether likes and comments are enabled,
// and the timestamp of the last update.
type DomainSettings struct {
id int
source string
like bool
comment bool
likes bool
comments bool
timestamp time.Time
}

type LikedIP struct {
id int
ip string
// Domain represents a domain and its associated settings.
// The id field is the unique identifier for the domain.
// The settings field contains the domain-specific settings.
// The domain field contains the domain name.
// The timestamp field contains the timestamp for when the domain was created or updated.
type Domain struct {
ID int
settings DomainSettings
domain string
timestamp time.Time
}

type Like struct {
// IsEmpty returns true if the Domain is the zero value.
func (domain Domain) IsEmpty() bool {
return domain == Domain{}
}

// LikesEnabled returns whether likes are enabled for the given Domain.
func (domain Domain) LikesEnabled() bool {
return domain.settings.likes
}

// CommentsEnabled returns whether comments are enabled for the given Domain.
func (domain Domain) CommentsEnabled() bool {
return domain.settings.comments
}

// Like represents a like for a domain and URI.
// The id field is the unique identifier for the like.
// The domain field is the domain the like is for.
// The uri field is the URI the like is for.
// The count field is the number of likes for the domain and URI.
// The timestamp field is the timestamp of when the like was created.
type Likes struct {
id int
domain_id int
Domain Domain
URI string
Count int
}

func (likes Likes) IsEmpty() bool {
return likes == Likes{}
}

func (likes Likes) GetDomainID() int {
return likes.Domain.ID
}

// LikedIPs represents a record of an IP address that has liked something, along with the count of likes and the timestamp of the last like.
type LikedIPs struct {
id int
uri string
count int
IP string
Count int
Domain string
Path string
timestamp time.Time
}

func (likedIPs LikedIPs) IsEmpty() bool {
return likedIPs == LikedIPs{}
}
Loading