2019-04-02 14:28:06 +00:00
package main
import (
"database/sql"
"fmt"
2019-04-03 06:21:25 +00:00
"github.com/gobuffalo/packr/v2"
2019-04-02 14:28:06 +00:00
_ "github.com/mattn/go-sqlite3"
2019-04-05 15:32:11 +00:00
"github.com/mssola/user_agent"
2019-04-05 14:47:04 +00:00
"github.com/rubenv/sql-migrate"
2019-04-05 15:08:05 +00:00
"net/url"
2019-04-02 14:28:06 +00:00
"os"
"path/filepath"
"strings"
)
type Database struct {
sqlDB * sql . DB
}
func initDatabase ( ) ( database * Database , e error ) {
database = & Database { }
if _ , err := os . Stat ( appConfig . dbPath ) ; os . IsNotExist ( err ) {
_ = os . MkdirAll ( filepath . Dir ( appConfig . dbPath ) , os . ModePerm )
}
database . sqlDB , e = sql . Open ( "sqlite3" , appConfig . dbPath )
if e != nil {
return
}
e = migrateDatabase ( database . sqlDB )
return
}
func migrateDatabase ( database * sql . DB ) ( e error ) {
2019-04-05 14:47:04 +00:00
migrations := & migrate . PackrMigrationSource {
2019-04-03 06:21:25 +00:00
Box : packr . New ( "migrations" , "migrations" ) ,
2019-04-02 14:28:06 +00:00
}
2019-04-05 14:47:04 +00:00
_ , e = migrate . Exec ( database , "sqlite3" , migrations , migrate . Up )
2019-04-02 14:28:06 +00:00
return
}
// Tracking
2019-04-05 15:32:11 +00:00
func ( db * Database ) trackView ( urlString string , ref string , ua string ) {
2019-04-05 15:08:05 +00:00
if len ( urlString ) == 0 {
// Don't track empty urls
2019-04-02 14:28:06 +00:00
return
}
2019-04-05 15:08:05 +00:00
if ref != "" {
// Clean referrer and just keep the hostname for more privacy
parsedRef , _ := url . Parse ( ref )
ref = parsedRef . Hostname ( )
}
2019-04-05 15:32:11 +00:00
if ua != "" {
// Parse Useragent
uaName , uaVersion := user_agent . New ( ua ) . Browser ( )
ua = uaName + " " + uaVersion
}
_ , e := db . sqlDB . Exec ( "insert into views(url, ref, useragent) values(:url, :ref, :ua)" , sql . Named ( "url" , urlString ) , sql . Named ( "ref" , ref ) , sql . Named ( "ua" , ua ) )
2019-04-02 14:28:06 +00:00
if e != nil {
fmt . Println ( "Inserting into DB failed:" , e )
}
}
// Requesting
type View int
const (
PAGES View = iota + 1
REFERRERS
2019-04-08 19:41:16 +00:00
USERAGENTS
2019-04-09 12:31:15 +00:00
USERAGENTNAMES
2019-04-02 14:28:06 +00:00
HOURS
DAYS
WEEKS
MONTHS
2019-04-15 07:56:41 +00:00
ALLHOURS
ALLDAYS
2019-04-02 14:28:06 +00:00
)
type ViewsRequest struct {
2019-04-09 13:17:18 +00:00
view View
from string
to string
url string
ref string
ua string
orderrow string
order string
2019-04-02 14:28:06 +00:00
}
type RequestResultRow struct {
First string ` json:"first" `
Second int ` json:"second" `
}
func ( db * Database ) request ( request * ViewsRequest ) ( resultRows [ ] * RequestResultRow , e error ) {
2019-04-09 13:17:18 +00:00
statement , parameters := request . buildStatement ( )
2019-04-02 14:28:06 +00:00
namedArgs := make ( [ ] interface { } , len ( parameters ) )
for i , v := range parameters {
namedArgs [ i ] = v
}
rows , e := db . sqlDB . Query ( statement , namedArgs ... )
if e != nil {
return
} else {
resultRows = [ ] * RequestResultRow { }
for rows . Next ( ) {
var first string
var second int
e = rows . Scan ( & first , & second )
if e != nil {
_ = rows . Close ( )
return
}
2019-04-09 12:31:34 +00:00
if first == "" {
first = "Undefined"
}
2019-04-02 14:28:06 +00:00
resultRows = append ( resultRows , & RequestResultRow {
First : first ,
Second : second ,
} )
}
return
}
}
2019-04-09 13:17:18 +00:00
func ( request * ViewsRequest ) buildStatement ( ) ( statement string , parameters [ ] sql . NamedArg ) {
filters , parameters := request . buildFilter ( )
2019-04-02 14:28:06 +00:00
if len ( filters ) > 0 {
filters = " where " + filters + " "
} else {
filters = " "
}
2019-04-09 13:17:18 +00:00
orderrow := "first"
order := "ASC"
2019-04-15 07:41:59 +00:00
if request . orderrow == "second" {
2019-04-09 13:17:18 +00:00
orderrow = "second"
}
2019-04-15 07:41:59 +00:00
if request . order == "DESC" {
2019-04-09 13:17:18 +00:00
order = "DESC"
}
orderstatement := " ORDER BY " + orderrow + " " + order
2019-04-02 14:28:06 +00:00
switch request . view {
case PAGES :
2019-04-09 13:17:18 +00:00
statement = "SELECT url as first, count(*) as second from views" + filters + "group by first" + orderstatement + ";"
2019-04-02 14:28:06 +00:00
case REFERRERS :
2019-04-09 13:17:18 +00:00
statement = "SELECT ref as first, count(*) as second from views" + filters + "group by first" + orderstatement + ";"
2019-04-08 19:41:16 +00:00
case USERAGENTS :
2019-04-09 13:17:18 +00:00
statement = "SELECT useragent as first, count(*) as second from views" + filters + "group by first" + orderstatement + ";"
2019-04-09 12:31:15 +00:00
case USERAGENTNAMES :
2019-04-09 13:17:18 +00:00
statement = "SELECT substr(useragent, 1, pos-1) as first, COUNT(*) as second from (SELECT *, instr(useragent,' ') AS pos FROM views)" + filters + "group by first" + orderstatement + ";"
2019-04-15 07:56:41 +00:00
case ALLHOURS :
2019-04-24 09:54:04 +00:00
statement = "WITH RECURSIVE hours(hour) AS ( VALUES (datetime((SELECT min(time) from views), 'localtime', 'start of day')) UNION ALL SELECT datetime(hour, '+1 hour') FROM hours WHERE hour <= strftime('%Y-%m-%d %H', (SELECT max(time) from views), 'localtime') ) SELECT strftime('%Y-%m-%d %H', hours.hour) as first, COUNT(views.time) as second FROM hours LEFT OUTER JOIN views ON strftime('%Y-%m-%d %H', hours.hour) = strftime('%Y-%m-%d %H', time, 'localtime')" + filters + "GROUP BY first" + orderstatement + ";"
2019-04-15 07:56:41 +00:00
case ALLDAYS :
2019-04-24 09:54:04 +00:00
statement = "WITH RECURSIVE days(day) AS ( VALUES (datetime((SELECT min(time) from views), 'localtime', 'start of day')) UNION ALL SELECT datetime(day, '+1 day') FROM days WHERE day <= date((SELECT max(time) from views), 'localtime') ) SELECT strftime('%Y-%m-%d', days.day) as first, COUNT(views.time) as second FROM days LEFT OUTER JOIN views ON strftime('%Y-%m-%d', days.day) = strftime('%Y-%m-%d', time, 'localtime')" + filters + "GROUP BY first" + orderstatement + ";"
2019-04-02 14:28:06 +00:00
case HOURS , DAYS , WEEKS , MONTHS :
format := ""
switch request . view {
case HOURS :
format = "%Y-%m-%d %H"
case DAYS :
format = "%Y-%m-%d"
case WEEKS :
format = "%Y-%W"
case MONTHS :
format = "%Y-%m"
}
2019-04-09 13:17:18 +00:00
statement = "SELECT strftime('" + format + "', time, 'localtime') as first, count(*) as second from views" + filters + "group by first" + orderstatement + ";"
2019-04-02 14:28:06 +00:00
}
return
}
// Request filters
func ( request * ViewsRequest ) buildFilter ( ) ( filters string , parameters [ ] sql . NamedArg ) {
parameters = [ ] sql . NamedArg { }
var allFilters [ ] string
for _ , filter := range [ ] string {
request . buildDateTimeFilter ( & parameters ) ,
request . buildUrlFilter ( & parameters ) ,
request . buildRefFilter ( & parameters ) ,
2019-04-08 19:48:34 +00:00
request . buildUseragentFilter ( & parameters ) ,
2019-04-02 14:28:06 +00:00
} {
if len ( filter ) > 0 {
allFilters = append ( allFilters , filter )
}
}
filters = strings . Join ( allFilters , " and " )
return
}
func ( request * ViewsRequest ) buildDateTimeFilter ( namedArg * [ ] sql . NamedArg ) ( dateTimeFilter string ) {
2019-04-15 07:56:41 +00:00
selector := ""
switch request . view {
case ALLHOURS , ALLDAYS :
selector = "first"
default :
selector = "datetime(time, 'localtime')"
}
2019-04-02 14:28:06 +00:00
if len ( request . from ) > 0 && len ( request . to ) > 0 {
* namedArg = append ( * namedArg , sql . Named ( "from" , request . from ) )
* namedArg = append ( * namedArg , sql . Named ( "to" , request . to ) )
2019-04-15 07:56:41 +00:00
dateTimeFilter = selector + " between :from and :to"
2019-04-02 14:28:06 +00:00
} else if len ( request . from ) > 0 {
* namedArg = append ( * namedArg , sql . Named ( "from" , request . from ) )
2019-04-15 07:56:41 +00:00
dateTimeFilter = selector + " >= :from"
2019-04-02 14:28:06 +00:00
} else if len ( request . to ) > 0 {
* namedArg = append ( * namedArg , sql . Named ( "to" , request . to ) )
2019-04-15 07:56:41 +00:00
dateTimeFilter = selector + " <= :to"
2019-04-02 14:28:06 +00:00
}
return
}
func ( request * ViewsRequest ) buildUrlFilter ( namedArg * [ ] sql . NamedArg ) ( urlFilter string ) {
if len ( request . url ) > 0 {
2019-04-08 19:36:11 +00:00
* namedArg = append ( * namedArg , sql . Named ( "url" , "%" + request . url + "%" ) )
urlFilter = "url like :url"
2019-04-02 14:28:06 +00:00
}
return
}
func ( request * ViewsRequest ) buildRefFilter ( namedArg * [ ] sql . NamedArg ) ( refFilter string ) {
2019-04-08 19:45:37 +00:00
if len ( request . ref ) > 0 {
* namedArg = append ( * namedArg , sql . Named ( "ref" , "%" + request . ref + "%" ) )
refFilter = "ref like :ref"
2019-04-02 14:28:06 +00:00
}
return
}
2019-04-08 19:48:34 +00:00
func ( request * ViewsRequest ) buildUseragentFilter ( namedArg * [ ] sql . NamedArg ) ( refFilter string ) {
if len ( request . ua ) > 0 {
* namedArg = append ( * namedArg , sql . Named ( "ua" , "%" + request . ua + "%" ) )
refFilter = "useragent like :ua"
}
return
}