From 213efef45aa9caed6b365c8c7575fd54e7f2ba61 Mon Sep 17 00:00:00 2001 From: Darko Luketic Date: Fri, 3 Mar 2017 22:07:39 +0100 Subject: [PATCH] moved things around, added storage --- main.go | 523 ++--------------------------------------------- mongo/site.go | 87 ++++++++ mongo/visit.go | 51 +++++ piwik/handler.go | 120 +++++++++++ piwik/parse.go | 500 ++++++++++++++++++++++++++++++++++++++++++++ piwik/storage.go | 98 +++++++++ 6 files changed, 876 insertions(+), 503 deletions(-) create mode 100644 mongo/site.go create mode 100644 mongo/visit.go create mode 100644 piwik/handler.go create mode 100644 piwik/parse.go create mode 100644 piwik/storage.go diff --git a/main.go b/main.go index c03bf2b..0853e92 100644 --- a/main.go +++ b/main.go @@ -1,512 +1,29 @@ package main import ( - "encoding/json" - "github.com/davecgh/go-spew/spew" - "net/http" - "strconv" - "time" "log" + "net/http" + + "github.com/dalu/gopiwik/mongo" + "github.com/dalu/gopiwik/piwik" + "gopkg.in/mgo.v2" +) + +const ( + database = "piwik" + siteCol = "sites" + visitCol = "visits" ) func main() { - - http.HandleFunc("/piwik.php", func(w http.ResponseWriter, r *http.Request) { - pr := ParseV3(r) - spew.Dump(pr) - }) - http.Handle("/", http.FileServer(http.Dir("assets"))) + ms, e := mgo.Dial("localhost") + if e != nil { + log.Fatal(e) + } + defer ms.Close() + siteStorage := mongo.NewSiteStorage(ms, database, siteCol) + visitStorage := mongo.NewVisitStorage(ms, database, visitCol) + ph := piwik.NewPiwikHandler(siteStorage, visitStorage) + http.Handle("/", ph) http.ListenAndServe(":8080", nil) } - -type V3Visit struct { - SiteID string //idsite - Record bool //rec - URL string //url - ActionName string //action_anem - VisitorID string //_id - Referrer string //urlref - CustomVars map[string]interface{} //_cvar - VisitsCount string //_idvc - ViewTime time.Time //_viewts - VisitorFirstVisitTime time.Time //_idts - CampaignName string //_rcn - CampaignKeyword string //_rck - Resolution string //res - LocalHour string //h - LocalMinute string //m - LocalSeconds string //s - LocalTimeUTC time.Time - Plugins struct { - Flash bool //fla - Java bool //java - Director bool //dir - Quicktime bool //qt - RealPlayer bool //realp - PDF bool //pdf - WindowsMedia bool //wma - Gears bool //gears - Silverlight bool //ag - } - SupportsCookies bool //cookie - UserAgent string //ua - Language string //lang - UserID string //uid - CustomUserID string //cid - NewVisit bool //new_visit - - PageCustomVars map[string]interface{} //cvar - Link string //link - Download string //download - Search string //search - SearchCategory string //search_cat - SearchCount string //search_count - PageViewID string //pv_id - GoalID string //idgoal - Revenue string //revenue - GenerationTime string //gt_ms - Characterset string //cs - - EventCategory string //e_c - EventAction string //e_a - EventName string //e_n - EventValue string //e_v - - ContentName string //c_n - ContentPiece string //c_p - ContentTarget string //c_t - ContentInteraction string //c_i - - ECommerceID string // ec_id - ECommerceItems [][]string // ec_items - ECommerceSubTotal string // ec_st - ECommerceTax string // ec_tx - ECommerceShipping string // ec_sh - ECommerceDiscount string // ec_dt - ECommerceTime time.Time // ec_ts - - TokenAuth string //token_auth - CustomIP string //cip - CustomDateTime string //cdt - CustomCountry string //country - CustomRegion string //region - CustomCity string //city - CustomLatitude string //lat - CustomLongitude string //long - - MediaID string // ma_id - MediaTitle string // ma_ti - MediaResource string // ma_re - MediaType string // ma_mt - MediaPlayerName string // ma_pn - MediaSecondsPlayingTime string // ma_st - MediaLength string // ma_le - MediaProgress string // ma_ps - MediaTimeUntilPlay string // ma_ttp - MediaWidth string // ma_w - MediaHeight string // ma_h - MediaFullscreen bool // ma_fs - - SendImage string //send_image -} - -func ParseV3(r *http.Request) *V3Visit { - /* - https://developer.piwik.org/api-reference/tracking-api - */ - - v3 := new(V3Visit) - // (required) — The ID of the website we're tracking a visit/action for. - v3.SiteID = r.URL.Query().Get("idsite") - - // (required) — Required for tracking, must be set to one, eg, &rec=1. - if r.URL.Query().Get("rec") == "1" { - v3.Record = true - } else { - v3.Record = false - } - - // (required) — The full URL for the current action. - v3.URL = r.URL.Query().Get("url") - - // (recommended) — The title of the action being tracked. - // It is possible to use slashes / to set one or several categories for this action. - // For example, Help / Feedback will create the Action Feedback in the category Help. - v3.ActionName = r.URL.Query().Get("action_name") - - // (recommended) — The unique visitor ID, must be a 16 characters hexadecimal string. - // Every unique visitor must be assigned a different ID and this ID must not change after it is assigned. - // If this value is not set Piwik will still track visits, but the unique visitors metric might be less accurate. - v3.VisitorID = r.URL.Query().Get("_id") - - /* - r.URL.Query().Get("rand") // (recommended) — Meant to hold a random value that is generated before each request. Using it helps avoid the tracking request being cached by the browser or a proxy. - r.URL.Query().Get("apiv") // (recommended) — The parameter &apiv=1 defines the api version to use (currently always set to 1) - */ - - // The full HTTP Referrer URL. - // This value is used to determine how someone got to your website (ie, through a website, search engine or campaign). - v3.Referrer = r.URL.Query().Get("urlref") - - // Visit scope custom variables. - // This is a JSON encoded string of the custom variable array (see below for an example value). - if e := json.Unmarshal([]byte(r.URL.Query().Get("_cvar")), v3.CustomVars);e != nil { - log.Println(e) - } - - // The current count of visits for this visitor. - // To set this value correctly, it would be required to store the value for each visitor in your application (using sessions or persisting in a database). - // Then you would manually increment the counts by one on each new visit or "session", depending on how you choose to define a visit. - // This value is used to populate the report Visitors > Engagement > Visits by visit number. - v3.VisitsCount = r.URL.Query().Get("_idvc") - - // The UNIX timestamp of this visitor's previous visit. This parameter is used to populate the report Visitors > Engagement > Visits by days since last visit. - v3.ViewTime = stringToTime(r.URL.Query().Get("_viewts")) - - // The UNIX timestamp of this visitor's first visit. This could be set to the date where the user first started using your software/app, or when he/she created an account. - // This parameter is used to populate the Goals > Days to Conversion report. - v3.VisitorFirstVisitTime = stringToTime(r.URL.Query().Get("_idts")) - - // The Campaign name (see Tracking Campaigns). - // Used to populate the Referrers > Campaigns report. - // Note: this parameter will only be used for the first pageview of a visit. - v3.CampaignName = r.URL.Query().Get("_rcn") - - // The Campaign Keyword (see Tracking Campaigns). - // Used to populate the Referrers > Campaigns report (clicking on a campaign loads all keywords for this campaign). - // Note: this parameter will only be used for the first pageview of a visit. - v3.CampaignKeyword = r.URL.Query().Get("_rck") - - // The resolution of the device the visitor is using, eg 1280x1024. - v3.Resolution = r.URL.Query().Get("res") - - // The current hour (local time). - v3.LocalHour = r.URL.Query().Get("h") - - // The current minute (local time). - v3.LocalMinute = r.URL.Query().Get("m") - - // The current second (local time). - v3.LocalSeconds = r.URL.Query().Get("s") - - v3.LocalTimeUTC = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), atoi(r.URL.Query().Get("h")), atoi(r.URL.Query().Get("m")), atoi(r.URL.Query().Get("s")), 0, time.UTC) - - // plugins used by the visitor can be specified by setting the following parameters to 1 - - // Flash - if r.URL.Query().Get("fla") == "1" { - v3.Plugins.Flash = true - } - - // Java - if r.URL.Query().Get("java") == "1" { - v3.Plugins.Java = true - } - - // Director - if r.URL.Query().Get("dir") == "1" { - v3.Plugins.Director = true - } - - // Quicktime - if r.URL.Query().Get("qt") == "1" { - v3.Plugins.Quicktime = true - } - - // Real Player - if r.URL.Query().Get("realp") == "1" { - v3.Plugins.RealPlayer = true - } - - // PDF - if r.URL.Query().Get("pdf") == "1" { - v3.Plugins.PDF = true - } - - // Windows Media - if r.URL.Query().Get("wma") == "1" { - v3.Plugins.WindowsMedia = true - } - - // Gears - if r.URL.Query().Get("gears") == "1" { - v3.Plugins.Gears = true - } - - // Silverlight - if r.URL.Query().Get("ag") == "1" { - v3.Plugins.Silverlight = true - } - - // when set to 1, the visitor's client is known to support cookies. - if r.URL.Query().Get("cookie") == "1" { - v3.SupportsCookies = true - } - - // An override value for the User-Agent HTTP header field. The user agent is used to detect the operating system and browser used. - if r.URL.Query().Get("ua") != "" { - v3.UserAgent = r.URL.Query().Get("ua") - } else { - v3.UserAgent = r.UserAgent() - } - - // An override value for the Accept-Language HTTP header field. This value is used to detect the visitor's country if GeoIP is not enabled. - if r.URL.Query().Get("lang") != "" { - v3.Language = r.URL.Query().Get("lang") - } else { - v3.Language = r.Header.Get("Accept-Language") - } - - // defines the User ID for this request. - // User ID is any non empty unique string identifying the user (such as an email address or a username). - // To access this value, users must be logged-in in your system so you can fetch this user ID from your system, and pass it to Piwik. - // The User ID appears in the visitor log, the Visitor profile, and you can Segment reports for one or several User ID (userId segment). - // When specified, the User ID will be "enforced". - // This means that if there is no recent visit with this User ID, a new one will be created. - // If a visit is found in the last 30 minutes with your specified User ID, then the new action will be recorded to this existing visit. - v3.UserID = r.URL.Query().Get("uid") - - // defines the visitor ID for this request. - // You must set this value to exactly a 16 character hexadecimal string (containing only characters 01234567890abcdefABCDEF). - // We recommended to set the User ID via uid rather than use this cid - v3.CustomUserID = r.URL.Query().Get("cid") - - // If set to 1, will force a new visit to be created for this action. This feature is also available in JavaScript. - if r.URL.Query().Get("new_visit") == "1" { - v3.NewVisit = true - } - - /* - // ? scopes? - dimension := make(map[string]string) - for k, v := range r.URL.Query() { - if strings.HasPrefix(k, "dimension") { - dimension[strings.TrimPrefix(k, "dimension")] = v - } - } - */ - - /* - Optional Action info (measure Page view, Outlink, Download, Site search) - */ - - // Page scope custom variables. This is a JSON encoded string of the custom variable array (see below for an example value). - json.Unmarshal([]byte(r.URL.Query().Get("cvar")), v3.PageCustomVars) - - // An external URL the user has opened. - // Used for tracking outlink clicks. - // We recommend to also set the url parameter to this same value. - v3.Link = r.URL.Query().Get("link") - - // URL of a file the user has downloaded. - // Used for tracking downloads. - // We recommend to also set the url parameter to this same value. - v3.Download = r.URL.Query().Get("download") - - // The Site Search keyword. - // When specified, the request will not be tracked as a normal pageview but will instead be tracked as a Site Search request. - v3.Search = r.URL.Query().Get("search") - - // when search is specified, you can optionally specify a search category with this parameter. - v3.SearchCategory = r.URL.Query().Get("search_cat") - - // when search is specified, we also recommend to set the search_count to the number of search results displayed on the results page. - // When keywords are tracked with &search_count=0 they will appear in the "No Result Search Keyword" report. - v3.SearchCount = r.URL.Query().Get("search_count") - - // Accepts a six character unique ID that identifies which actions were performed on a specific page view. - // When a page was viewed, all following tracking requests (such as events) during that page view should use the same pageview ID. - // Once another page was viewed a new unique ID should be generated. - // Use [0-9a-Z] as possible characters for the unique ID. - v3.PageViewID = r.URL.Query().Get("pv_id") - - // If specified, the tracking request will trigger a conversion for the goal of the website being tracked with this ID. - v3.GoalID = r.URL.Query().Get("idgoal") - - // A monetary value that was generated as revenue by this goal conversion. - // Only used if idgoal is specified in the request. - v3.Revenue = r.URL.Query().Get("revenue") - - // The amount of time it took the server to generate this action, in milliseconds. - // This value is used to process the Page speed report Avg. generation time column in the Page URL and Page Title reports, - // as well as a site wide running average of the speed of your server. - // Note: when using the JavaScript tracker this value is set to the time for server to generate response + the time for client to download response. - v3.GenerationTime = r.URL.Query().Get("gt_ms") - - // The charset of the page being tracked. - // Specify the charset if the data you send to Piwik is encoded in a different character set than the default utf-8. - v3.Characterset = r.URL.Query().Get("cs") - - /* - Optional Event Tracking info - */ - - // The event category. Must not be empty. (eg. Videos, Music, Games...) - v3.EventCategory = r.URL.Query().Get("e_c") - - // The event action. Must not be empty. (eg. Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) - v3.EventAction = r.URL.Query().Get("e_a") - - // The event name. (eg. a Movie name, or Song name, or File name...) - v3.EventName = r.URL.Query().Get("e_n") - - // The event value. Must be a float or integer value (numeric), not a string. - v3.EventValue = r.URL.Query().Get("e_v") - - /* - Optional Content Tracking info - */ - - // The name of the content. For instance 'Ad Foo Bar' - v3.ContentName = r.URL.Query().Get("c_n") - // The actual content piece. For instance the path to an image, video, audio, any text - v3.ContentPiece = r.URL.Query().Get("c_p") - // The target of the content. For instance the URL of a landing page - v3.ContentTarget = r.URL.Query().Get("c_t") - // The name of the interaction with the content. For instance a 'click' - v3.ContentInteraction = r.URL.Query().Get("c_i") - - // Optional Ecommerce info - // you must set &idgoal=0 in the request to track an ecommerce interaction: cart update or an ecommerce order. - if r.URL.Query().Get("idgoal") == "0" { - // The unique string identifier for the ecommerce order (required when tracking an ecommerce order) - v3.ECommerceID = r.URL.Query().Get("ec_id") - - // Items in the Ecommerce order. - // This is a JSON encoded array of items. Each item is an array with the following info in this order: item sku, item name, item category, item price, item quantity. - json.Unmarshal([]byte(r.URL.Query().Get("ec_items")), v3.ECommerceItems) - - // The grand total for the ecommerce order (required when tracking an ecommerce order) - v3.Revenue = r.URL.Query().Get("revenue") - - // The sub total of the order; excludes shipping. - v3.ECommerceSubTotal = r.URL.Query().Get("ec_st") - - // Tax Amount of the order - v3.ECommerceTax = r.URL.Query().Get("ec_tx") - - // Shipping cost of the Order - v3.ECommerceShipping = r.URL.Query().Get("ec_sh") - - // Discount offered - v3.ECommerceDiscount = r.URL.Query().Get("ec_dt") - - // The UNIX timestamp of this customer's last ecommerce order. - // This value is used to process the "Days since last order" report. - v3.ECommerceTime = stringToTime(r.URL.Query().Get("_ects")) - } - - /* - Other parameters (require authentication via token_auth) - */ - - // 32 character authorization key used to authenticate the API request. - v3.TokenAuth = r.URL.Query().Get("token_auth") - - // Override value for the visitor IP (both IPv4 and IPv6 notations supported). - v3.CustomIP = r.URL.Query().Get("cip") - - // Override for the datetime of the request (normally the current time is used). - // This can be used to record visits and page views in the past. - // The expected format is either a datetime such as: 2011-04-05 00:11:42 (remember to URL encode the value!), or a valid UNIX timestamp such as 1301919102. - // The datetime must be sent in UTC timezone. - // Note: if you record data in the past, you will need to force Piwik to re-process reports for the past dates. - // If you set cdt to a datetime older than 24 hours then token_auth must be set. - // If you set cdt with a datetime in the last 24 hours then you don't need to pass token_auth. - v3.CustomDateTime = r.URL.Query().Get("cdt") - - // An override value for the country. Should be set to the two letter country code of the visitor (lowercase), eg fr, de, us. - v3.CustomCountry = r.URL.Query().Get("country") - - // An override value for the region. Should be set to the two letter region code as defined by MaxMind's GeoIP databases. See here for a list of them for every country (the region codes are located in the second column, to the left of the region name and to the right of the country code). - v3.CustomRegion = r.URL.Query().Get("region") - - // An override value for the city. The name of the city the visitor is located in, eg, Tokyo. - v3.CustomCity = r.URL.Query().Get("city") - - // An override value for the visitor's latitude, eg 22.456. - v3.CustomLatitude = r.URL.Query().Get("lat") - - // An override value for the visitor's longitude, eg 22.456. - v3.CustomLongitude = r.URL.Query().Get("long") - - /* - Media Analytics parameters - https://developer.piwik.org/guides/media-analytics/custom-player#media-analytics-http-tracking-api-reference - */ - - // A unique id that is always the same while playing a media. As soon as the played media changes (new video or audio started), this ID has to change. - v3.MediaID = r.URL.Query().Get("ma_id") - - // The name / title of the media. - v3.MediaTitle = r.URL.Query().Get("ma_ti") - - // The URL of the media resource. - v3.MediaResource = r.URL.Query().Get("ma_re") - - // video or audio depending on the type of the media. - v3.MediaType = r.URL.Query().Get("ma_mt") - - // The name of the media player, for example html5. - v3.MediaPlayerName = r.URL.Query().Get("ma_pn") - - // The time in seconds for how long a user has been playing this media. - // This number should typically increase when you send a media tracking request. - // It should be 0 if the media was only visible/impressed but not played. - // Do not increase this number when a media is paused. - v3.MediaSecondsPlayingTime = r.URL.Query().Get("ma_st") - - // The duration (the length) of the media in seconds. For example if a video is 90 seconds long, the value should be 90. - v3.MediaLength = r.URL.Query().Get("ma_le") - - // The progress / current position within the media. Defines basically at which position within the total length the user is currently playing. - v3.MediaProgress = r.URL.Query().Get("ma_ps") - - // Defines after how many seconds the user has started playing this media. - // For example a user might have seen the poster of the video for 30 seconds before a user actually pressed the play button. - v3.MediaTimeUntilPlay = r.URL.Query().Get("ma_ttp") - - // The resolution width of the media in pixels. Only recommended to be set for videos. - v3.MediaWidth = r.URL.Query().Get("ma_w") - - // The resolution height of the media in pixels. Only recommended to be set for videos. - v3.MediaHeight = r.URL.Query().Get("ma_h") - - // Should be 0 or 1 and defines whether the media is currently viewed in full screen. Only recommended to be set for videos. - switch r.URL.Query().Get("ma_fs") { - case "0": - v3.MediaFullscreen = false - case "1": - v3.MediaFullscreen = true - default: - v3.MediaFullscreen = false - } - - // If set to 0 (send_image=0) Piwik will respond with a HTTP 204 response code instead of a GIF image. - // This improves performance and can fix errors if images are not allowed to be obtained directly (eg Chrome Apps). - // Available since Piwik 2.10.0 - v3.SendImage = r.URL.Query().Get("send_image") - - return v3 - - /* - r.URL.Query().Get("") // - */ - -} - -func stringToTime(s string) time.Time { - i, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return time.Time{} - } - return time.Unix(i, 0) -} - -func atoi(a string) int { - i, err := strconv.Atoi(a) - if err != nil { - panic(err) - } - return i -} diff --git a/mongo/site.go b/mongo/site.go new file mode 100644 index 0000000..c920b9d --- /dev/null +++ b/mongo/site.go @@ -0,0 +1,87 @@ +package mongo + +import ( + "github.com/dalu/gopiwik/piwik" + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +func NewSiteStorage(ms *mgo.Session, db, col string) SiteStorage { + return SiteStorage{ + ms: ms.Clone(), + db: db, + col: col, + } +} + +type SiteStorage struct { + ms *mgo.Session + db, col string +} + +func (s SiteStorage) Add(site *piwik.Site) error { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + site.ID = bson.NewObjectId().Hex() + return c.Insert(site) +} + +func (s SiteStorage) Remove(site *piwik.Site) error { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + return c.RemoveId(site.ID) + +} + +func (s SiteStorage) Update(site *piwik.Site) error { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + return c.UpdateId(site.ID, site) +} + +func (s SiteStorage) List() ([]*piwik.Site, error) { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + sites := []*piwik.Site{} + if e := c.Find(nil).All(&sites); e != nil { + return sites, e + } + return sites, nil +} + +func (s SiteStorage) FindId(id string) (*piwik.Site, error) { + ms := s.ms.Copy() + defer ms.Close() + site := new(piwik.Site) + c := ms.DB(s.db).C(s.col) + if e := c.FindId(id).One(site); e != nil { + return nil, e + } + return site, nil +} + +func (s SiteStorage) Find(query map[string]interface{}) (*piwik.Site, error) { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + site := new(piwik.Site) + if e := c.Find(query).One(site); e != nil { + return nil, e + } + return site, nil +} + +func (s SiteStorage) FindAll(query map[string]interface{}) ([]*piwik.Site, error) { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + sites := []*piwik.Site{} + if e := c.Find(query).All(&sites); e != nil { + return sites, e + } + return sites, nil +} diff --git a/mongo/visit.go b/mongo/visit.go new file mode 100644 index 0000000..3173d4e --- /dev/null +++ b/mongo/visit.go @@ -0,0 +1,51 @@ +package mongo + +import ( + "github.com/dalu/gopiwik/piwik" + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +func NewVisitStorage(ms *mgo.Session, db, col string) VisitStorage { + return VisitStorage{ + ms: ms.Clone(), + db: db, + col: col, + } +} + +type VisitStorage struct { + ms *mgo.Session + db, col string +} + +func (s VisitStorage) Add(visit *piwik.Visit) error { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + visit.ID = bson.NewObjectId().Hex() + return c.Insert(visit) +} + +func (s VisitStorage) Find(query map[string]interface{}) (*piwik.Visit, error) { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + visit := new(piwik.Visit) + if e := c.Find(query).One(visit); e != nil { + return nil, e + } + return visit, nil +} + +func (s VisitStorage) FindAll(query map[string]interface{}) ([]*piwik.Visit, error) { + ms := s.ms.Copy() + defer ms.Close() + c := ms.DB(s.db).C(s.col) + visits := []*piwik.Visit{} + if e := c.Find(query).All(visits); e != nil { + return nil, e + } + return visits, nil + +} diff --git a/piwik/handler.go b/piwik/handler.go new file mode 100644 index 0000000..59b3b65 --- /dev/null +++ b/piwik/handler.go @@ -0,0 +1,120 @@ +package piwik + +import ( + "log" + "net/http" + + "encoding/json" + "os" +) + +type PiwikHandler struct { + fileServer http.Handler + siteStorage SiteStorageInterface + visitStorage VisitStorageInterface +} + +func NewPiwikHandler(ss SiteStorageInterface, vs VisitStorageInterface) *PiwikHandler { + return &PiwikHandler{ + fileServer: http.FileServer(http.Dir("assets")), + siteStorage: ss, + visitStorage: vs, + } +} + +func (h *PiwikHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + switch r.URL.Path { + case "/piwik.php": + h.handlePiwik(w, r) + default: + h.fileServer.ServeHTTP(w, r) + } + + default: + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + } +} + +func (h *PiwikHandler) handlePiwik(w http.ResponseWriter, r *http.Request) { + pr := ParseV3(r) + v := &Visit{ + SiteID: pr.SiteID, + URL: pr.URL, + ActionName: pr.ActionName, + VisitorID: pr.VisitorID, + Referrer: pr.Referrer, + CustomVars: pr.CustomVars, + VisitsCount: pr.VisitsCount, + ViewTime: pr.ViewTime, + VisitorFirstVisitTime: pr.VisitorFirstVisitTime, + CampaignName: pr.CampaignName, + CampaignKeyword: pr.CampaignKeyword, + Resolution: pr.Resolution, + LocalHour: pr.LocalHour, + LocalMinute: pr.LocalMinute, + LocalSeconds: pr.LocalSeconds, + LocalTimeUTC: pr.LocalTimeUTC, + Plugins: pr.Plugins, + SupportsCookies: pr.SupportsCookies, + UserAgent: pr.UserAgent, + Language: pr.Language, + UserID: pr.UserID, + CustomUserID: pr.CustomUserID, + NewVisit: pr.NewVisit, + PageCustomVars: pr.PageCustomVars, + Link: pr.Link, + Download: pr.Download, + Search: pr.Search, + SearchCategory: pr.SearchCategory, + SearchCount: pr.SearchCount, + PageViewID: pr.PageViewID, + GoalID: pr.GoalID, + Revenue: pr.Revenue, + GenerationTime: pr.GenerationTime, + Characterset: pr.Characterset, + EventCategory: pr.EventCategory, + EventAction: pr.EventAction, + EventName: pr.EventName, + EventValue: pr.EventValue, + ContentName: pr.ContentName, + ContentPiece: pr.ContentPiece, + ContentTarget: pr.ContentTarget, + ContentInteraction: pr.ContentInteraction, + ECommerceID: pr.ECommerceID, + ECommerceItems: pr.ECommerceItems, + ECommerceSubTotal: pr.ECommerceSubTotal, + ECommerceTax: pr.ECommerceTax, + ECommerceShipping: pr.ECommerceShipping, + ECommerceDiscount: pr.ECommerceDiscount, + ECommerceTime: pr.ECommerceTime, + TokenAuth: pr.TokenAuth, + CustomIP: pr.CustomIP, + CustomDateTime: pr.CustomDateTime, + CustomCountry: pr.CustomCountry, + CustomRegion: pr.CustomRegion, + CustomCity: pr.CustomCity, + CustomLatitude: pr.CustomLatitude, + CustomLongitude: pr.CustomLongitude, + MediaID: pr.MediaID, + MediaTitle: pr.MediaTitle, + MediaResource: pr.MediaResource, + MediaType: pr.MediaType, + MediaPlayerName: pr.MediaPlayerName, + MediaSecondsPlayingTime: pr.MediaSecondsPlayingTime, + MediaLength: pr.MediaLength, + MediaProgress: pr.MediaProgress, + MediaTimeUntilPlay: pr.MediaTimeUntilPlay, + MediaWidth: pr.MediaWidth, + MediaHeight: pr.MediaHeight, + MediaFullscreen: pr.MediaFullscreen, + } + if e := h.visitStorage.Add(v); e != nil { + log.Println(e) + } + if pr.SendImage == "0" { + w.WriteHeader(204) + } + json.NewEncoder(os.Stdout).Encode(v) +} diff --git a/piwik/parse.go b/piwik/parse.go new file mode 100644 index 0000000..aefe174 --- /dev/null +++ b/piwik/parse.go @@ -0,0 +1,500 @@ +package piwik + +import ( + "encoding/json" + "log" + "net/http" + "strconv" + "time" +) + +type V3Visit struct { + SiteID string `json:"site_id"` //idsite + Record bool `json:"record"` //rec + URL string `json:"url"` //url + ActionName string `json:"action_name"` //action_anem + VisitorID string `json:"visitor_id"` //_id + Referrer string `json:"referrer"` //urlref + CustomVars map[string]interface{} `json:"custom_vars"` //_cvar + VisitsCount string `json:"visits_count"` //_idvc + ViewTime time.Time `json:"view_time"` //_viewts + VisitorFirstVisitTime time.Time `json:"visitor_first_visit_time"` //_idts + CampaignName string `json:"campaign_name"` //_rcn + CampaignKeyword string `json:"campaign_keyword"` //_rck + Resolution string `json:"resolution"` //res + LocalHour string `json:"local_hour"` //h + LocalMinute string `json:"local_minute"` //m + LocalSeconds string `json:"local_seconds"` //s + LocalTimeUTC time.Time `json:"local_time_utc"` + Plugins *Plugins `json:"plugins"` + SupportsCookies bool `json:"supports_cookies"` //cookie + UserAgent string `json:"user_agent"` //ua + Language string `json:"language"` //lang + UserID string `json:"user_id"` //uid + CustomUserID string `json:"custom_user_id"` //cid + NewVisit bool `json:"new_visit"` //new_visit + PageCustomVars map[string]interface{} `json:"page_custom_vars"` //cvar + Link string `json:"link"` //link + Download string `json:"download"` //download + Search string `json:"search"` //search + SearchCategory string `json:"search_category"` //search_cat + SearchCount string `json:"search_count"` //search_count + PageViewID string `json:"page_view_id"` //pv_id + GoalID string `json:"goal_id"` //idgoal + Revenue string `json:"revenue"` //revenue + GenerationTime string `json:"generation_time"` //gt_ms + Characterset string `json:"characterset"` //cs + EventCategory string `json:"event_category"` //e_c + EventAction string `json:"event_action"` //e_a + EventName string `json:"event_name"` //e_n + EventValue string `json:"event_value"` //e_v + ContentName string `json:"content_name"` //c_n + ContentPiece string `json:"content_piece"` //c_p + ContentTarget string `json:"content_target"` //c_t + ContentInteraction string `json:"content_interaction"` //c_i + ECommerceID string `json:"ecommerce_id"` // ec_id + ECommerceItems [][]string `json:"ecommerce_items"` // ec_items + ECommerceSubTotal string `json:"ecommerce_sub_total"` // ec_st + ECommerceTax string `json:"ecommerce_tax"` // ec_tx + ECommerceShipping string `json:"ecommerce_shipping"` // ec_sh + ECommerceDiscount string `json:"ecommerce_discount"` // ec_dt + ECommerceTime time.Time `json:"ecommerce_time"` // ec_ts + TokenAuth string `json:"token_auth"` //token_auth + CustomIP string `json:"custom_ip"` //cip + CustomDateTime string `json:"custom_date_time"` //cdt + CustomCountry string `json:"custom_country"` //country + CustomRegion string `json:"custom_region"` //region + CustomCity string `json:"custom_city"` //city + CustomLatitude string `json:"custom_latitude"` //lat + CustomLongitude string `json:"custom_longitude"` //long + MediaID string `json:"media_id"` // ma_id + MediaTitle string `json:"media_title"` // ma_ti + MediaResource string `json:"media_resource"` // ma_re + MediaType string `json:"media_type"` // ma_mt + MediaPlayerName string `json:"media_player_name"` // ma_pn + MediaSecondsPlayingTime string `json:"media_seconds_playing_time"` // ma_st + MediaLength string `json:"media_length"` // ma_le + MediaProgress string `json:"media_progress"` // ma_ps + MediaTimeUntilPlay string `json:"media_time_until_play"` // ma_ttp + MediaWidth string `json:"media_width"` // ma_w + MediaHeight string `json:"media_height"` // ma_h + MediaFullscreen bool `json:"media_fullscreen"` // ma_fs + SendImage string `json:"send_image"` //send_image +} + +type Plugins struct { + Flash bool `json:"flash"` //fla + Java bool `json:"java"` //java + Director bool `json:"director"` //dir + Quicktime bool `json:"quicktime"` //qt + RealPlayer bool `json:"real_player"` //realp + PDF bool `json:"pdf"` //pdf + WindowsMedia bool `json:"windows_media"` //wma + Gears bool `json:"gears"` //gears + Silverlight bool `json:"silverlight"` //ag +} + +func ParseV3(r *http.Request) *V3Visit { + /* + https://developer.piwik.org/api-reference/tracking-api + */ + + v3 := new(V3Visit) + // (required) — The ID of the website we're tracking a visit/action for. + v3.SiteID = r.URL.Query().Get("idsite") + + // (required) — Required for tracking, must be set to one, eg, &rec=1. + if r.URL.Query().Get("rec") == "1" { + v3.Record = true + } else { + v3.Record = false + } + + // (required) — The full URL for the current action. + v3.URL = r.URL.Query().Get("url") + + // (recommended) — The title of the action being tracked. + // It is possible to use slashes / to set one or several categories for this action. + // For example, Help / Feedback will create the Action Feedback in the category Help. + v3.ActionName = r.URL.Query().Get("action_name") + + // (recommended) — The unique visitor ID, must be a 16 characters hexadecimal string. + // Every unique visitor must be assigned a different ID and this ID must not change after it is assigned. + // If this value is not set Piwik will still track visits, but the unique visitors metric might be less accurate. + v3.VisitorID = r.URL.Query().Get("_id") + + /* + r.URL.Query().Get("rand") // (recommended) — Meant to hold a random value that is generated before each request. Using it helps avoid the tracking request being cached by the browser or a proxy. + r.URL.Query().Get("apiv") // (recommended) — The parameter &apiv=1 defines the api version to use (currently always set to 1) + */ + + // The full HTTP Referrer URL. + // This value is used to determine how someone got to your website (ie, through a website, search engine or campaign). + v3.Referrer = r.URL.Query().Get("urlref") + + // Visit scope custom variables. + // This is a JSON encoded string of the custom variable array (see below for an example value). + if r.URL.Query().Get("_cvar") != "" { + if e := json.Unmarshal([]byte(r.URL.Query().Get("_cvar")), v3.CustomVars); e != nil { + log.Println("Error unmarshalling _cvar", e) + } + } + + // The current count of visits for this visitor. + // To set this value correctly, it would be required to store the value for each visitor in your application (using sessions or persisting in a database). + // Then you would manually increment the counts by one on each new visit or "session", depending on how you choose to define a visit. + // This value is used to populate the report Visitors > Engagement > Visits by visit number. + v3.VisitsCount = r.URL.Query().Get("_idvc") + + // The UNIX timestamp of this visitor's previous visit. This parameter is used to populate the report Visitors > Engagement > Visits by days since last visit. + v3.ViewTime = stringToTime(r.URL.Query().Get("_viewts")) + + // The UNIX timestamp of this visitor's first visit. This could be set to the date where the user first started using your software/app, or when he/she created an account. + // This parameter is used to populate the Goals > Days to Conversion report. + v3.VisitorFirstVisitTime = stringToTime(r.URL.Query().Get("_idts")) + + // The Campaign name (see Tracking Campaigns). + // Used to populate the Referrers > Campaigns report. + // Note: this parameter will only be used for the first pageview of a visit. + v3.CampaignName = r.URL.Query().Get("_rcn") + + // The Campaign Keyword (see Tracking Campaigns). + // Used to populate the Referrers > Campaigns report (clicking on a campaign loads all keywords for this campaign). + // Note: this parameter will only be used for the first pageview of a visit. + v3.CampaignKeyword = r.URL.Query().Get("_rck") + + // The resolution of the device the visitor is using, eg 1280x1024. + v3.Resolution = r.URL.Query().Get("res") + + // The current hour (local time). + v3.LocalHour = r.URL.Query().Get("h") + + // The current minute (local time). + v3.LocalMinute = r.URL.Query().Get("m") + + // The current second (local time). + v3.LocalSeconds = r.URL.Query().Get("s") + + v3.LocalTimeUTC = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), atoi(r.URL.Query().Get("h")), atoi(r.URL.Query().Get("m")), atoi(r.URL.Query().Get("s")), 0, time.UTC) + + // plugins used by the visitor can be specified by setting the following parameters to 1 + + v3.Plugins = new(Plugins) + + // Flash + if r.URL.Query().Get("fla") == "1" { + v3.Plugins.Flash = true + } + + // Java + if r.URL.Query().Get("java") == "1" { + v3.Plugins.Java = true + } + + // Director + if r.URL.Query().Get("dir") == "1" { + v3.Plugins.Director = true + } + + // Quicktime + if r.URL.Query().Get("qt") == "1" { + v3.Plugins.Quicktime = true + } + + // Real Player + if r.URL.Query().Get("realp") == "1" { + v3.Plugins.RealPlayer = true + } + + // PDF + if r.URL.Query().Get("pdf") == "1" { + v3.Plugins.PDF = true + } + + // Windows Media + if r.URL.Query().Get("wma") == "1" { + v3.Plugins.WindowsMedia = true + } + + // Gears + if r.URL.Query().Get("gears") == "1" { + v3.Plugins.Gears = true + } + + // Silverlight + if r.URL.Query().Get("ag") == "1" { + v3.Plugins.Silverlight = true + } + + // when set to 1, the visitor's client is known to support cookies. + if r.URL.Query().Get("cookie") == "1" { + v3.SupportsCookies = true + } + + // An override value for the User-Agent HTTP header field. The user agent is used to detect the operating system and browser used. + if r.URL.Query().Get("ua") != "" { + v3.UserAgent = r.URL.Query().Get("ua") + } else { + v3.UserAgent = r.UserAgent() + } + + // An override value for the Accept-Language HTTP header field. This value is used to detect the visitor's country if GeoIP is not enabled. + if r.URL.Query().Get("lang") != "" { + v3.Language = r.URL.Query().Get("lang") + } else { + v3.Language = r.Header.Get("Accept-Language") + } + + // defines the User ID for this request. + // User ID is any non empty unique string identifying the user (such as an email address or a username). + // To access this value, users must be logged-in in your system so you can fetch this user ID from your system, and pass it to Piwik. + // The User ID appears in the visitor log, the Visitor profile, and you can Segment reports for one or several User ID (userId segment). + // When specified, the User ID will be "enforced". + // This means that if there is no recent visit with this User ID, a new one will be created. + // If a visit is found in the last 30 minutes with your specified User ID, then the new action will be recorded to this existing visit. + v3.UserID = r.URL.Query().Get("uid") + + // defines the visitor ID for this request. + // You must set this value to exactly a 16 character hexadecimal string (containing only characters 01234567890abcdefABCDEF). + // We recommended to set the User ID via uid rather than use this cid + v3.CustomUserID = r.URL.Query().Get("cid") + + // If set to 1, will force a new visit to be created for this action. This feature is also available in JavaScript. + if r.URL.Query().Get("new_visit") == "1" { + v3.NewVisit = true + } + + /* + // ? scopes? + dimension := make(map[string]string) + for k, v := range r.URL.Query() { + if strings.HasPrefix(k, "dimension") { + dimension[strings.TrimPrefix(k, "dimension")] = v + } + } + */ + + /* + Optional Action info (measure Page view, Outlink, Download, Site search) + */ + + // Page scope custom variables. This is a JSON encoded string of the custom variable array (see below for an example value). + json.Unmarshal([]byte(r.URL.Query().Get("cvar")), v3.PageCustomVars) + + // An external URL the user has opened. + // Used for tracking outlink clicks. + // We recommend to also set the url parameter to this same value. + v3.Link = r.URL.Query().Get("link") + + // URL of a file the user has downloaded. + // Used for tracking downloads. + // We recommend to also set the url parameter to this same value. + v3.Download = r.URL.Query().Get("download") + + // The Site Search keyword. + // When specified, the request will not be tracked as a normal pageview but will instead be tracked as a Site Search request. + v3.Search = r.URL.Query().Get("search") + + // when search is specified, you can optionally specify a search category with this parameter. + v3.SearchCategory = r.URL.Query().Get("search_cat") + + // when search is specified, we also recommend to set the search_count to the number of search results displayed on the results page. + // When keywords are tracked with &search_count=0 they will appear in the "No Result Search Keyword" report. + v3.SearchCount = r.URL.Query().Get("search_count") + + // Accepts a six character unique ID that identifies which actions were performed on a specific page view. + // When a page was viewed, all following tracking requests (such as events) during that page view should use the same pageview ID. + // Once another page was viewed a new unique ID should be generated. + // Use [0-9a-Z] as possible characters for the unique ID. + v3.PageViewID = r.URL.Query().Get("pv_id") + + // If specified, the tracking request will trigger a conversion for the goal of the website being tracked with this ID. + v3.GoalID = r.URL.Query().Get("idgoal") + + // A monetary value that was generated as revenue by this goal conversion. + // Only used if idgoal is specified in the request. + v3.Revenue = r.URL.Query().Get("revenue") + + // The amount of time it took the server to generate this action, in milliseconds. + // This value is used to process the Page speed report Avg. generation time column in the Page URL and Page Title reports, + // as well as a site wide running average of the speed of your server. + // Note: when using the JavaScript tracker this value is set to the time for server to generate response + the time for client to download response. + v3.GenerationTime = r.URL.Query().Get("gt_ms") + + // The charset of the page being tracked. + // Specify the charset if the data you send to Piwik is encoded in a different character set than the default utf-8. + v3.Characterset = r.URL.Query().Get("cs") + + /* + Optional Event Tracking info + */ + + // The event category. Must not be empty. (eg. Videos, Music, Games...) + v3.EventCategory = r.URL.Query().Get("e_c") + + // The event action. Must not be empty. (eg. Play, Pause, Duration, Add Playlist, Downloaded, Clicked...) + v3.EventAction = r.URL.Query().Get("e_a") + + // The event name. (eg. a Movie name, or Song name, or File name...) + v3.EventName = r.URL.Query().Get("e_n") + + // The event value. Must be a float or integer value (numeric), not a string. + v3.EventValue = r.URL.Query().Get("e_v") + + /* + Optional Content Tracking info + */ + + // The name of the content. For instance 'Ad Foo Bar' + v3.ContentName = r.URL.Query().Get("c_n") + // The actual content piece. For instance the path to an image, video, audio, any text + v3.ContentPiece = r.URL.Query().Get("c_p") + // The target of the content. For instance the URL of a landing page + v3.ContentTarget = r.URL.Query().Get("c_t") + // The name of the interaction with the content. For instance a 'click' + v3.ContentInteraction = r.URL.Query().Get("c_i") + + // Optional Ecommerce info + // you must set &idgoal=0 in the request to track an ecommerce interaction: cart update or an ecommerce order. + if r.URL.Query().Get("idgoal") == "0" { + // The unique string identifier for the ecommerce order (required when tracking an ecommerce order) + v3.ECommerceID = r.URL.Query().Get("ec_id") + + // Items in the Ecommerce order. + // This is a JSON encoded array of items. Each item is an array with the following info in this order: item sku, item name, item category, item price, item quantity. + json.Unmarshal([]byte(r.URL.Query().Get("ec_items")), v3.ECommerceItems) + + // The grand total for the ecommerce order (required when tracking an ecommerce order) + v3.Revenue = r.URL.Query().Get("revenue") + + // The sub total of the order; excludes shipping. + v3.ECommerceSubTotal = r.URL.Query().Get("ec_st") + + // Tax Amount of the order + v3.ECommerceTax = r.URL.Query().Get("ec_tx") + + // Shipping cost of the Order + v3.ECommerceShipping = r.URL.Query().Get("ec_sh") + + // Discount offered + v3.ECommerceDiscount = r.URL.Query().Get("ec_dt") + + // The UNIX timestamp of this customer's last ecommerce order. + // This value is used to process the "Days since last order" report. + v3.ECommerceTime = stringToTime(r.URL.Query().Get("_ects")) + } + + /* + Other parameters (require authentication via token_auth) + */ + + // 32 character authorization key used to authenticate the API request. + v3.TokenAuth = r.URL.Query().Get("token_auth") + + // Override value for the visitor IP (both IPv4 and IPv6 notations supported). + v3.CustomIP = r.URL.Query().Get("cip") + + // Override for the datetime of the request (normally the current time is used). + // This can be used to record visits and page views in the past. + // The expected format is either a datetime such as: 2011-04-05 00:11:42 (remember to URL encode the value!), or a valid UNIX timestamp such as 1301919102. + // The datetime must be sent in UTC timezone. + // Note: if you record data in the past, you will need to force Piwik to re-process reports for the past dates. + // If you set cdt to a datetime older than 24 hours then token_auth must be set. + // If you set cdt with a datetime in the last 24 hours then you don't need to pass token_auth. + v3.CustomDateTime = r.URL.Query().Get("cdt") + + // An override value for the country. Should be set to the two letter country code of the visitor (lowercase), eg fr, de, us. + v3.CustomCountry = r.URL.Query().Get("country") + + // An override value for the region. Should be set to the two letter region code as defined by MaxMind's GeoIP databases. See here for a list of them for every country (the region codes are located in the second column, to the left of the region name and to the right of the country code). + v3.CustomRegion = r.URL.Query().Get("region") + + // An override value for the city. The name of the city the visitor is located in, eg, Tokyo. + v3.CustomCity = r.URL.Query().Get("city") + + // An override value for the visitor's latitude, eg 22.456. + v3.CustomLatitude = r.URL.Query().Get("lat") + + // An override value for the visitor's longitude, eg 22.456. + v3.CustomLongitude = r.URL.Query().Get("long") + + /* + Media Analytics parameters + https://developer.piwik.org/guides/media-analytics/custom-player#media-analytics-http-tracking-api-reference + */ + + // A unique id that is always the same while playing a media. As soon as the played media changes (new video or audio started), this ID has to change. + v3.MediaID = r.URL.Query().Get("ma_id") + + // The name / title of the media. + v3.MediaTitle = r.URL.Query().Get("ma_ti") + + // The URL of the media resource. + v3.MediaResource = r.URL.Query().Get("ma_re") + + // video or audio depending on the type of the media. + v3.MediaType = r.URL.Query().Get("ma_mt") + + // The name of the media player, for example html5. + v3.MediaPlayerName = r.URL.Query().Get("ma_pn") + + // The time in seconds for how long a user has been playing this media. + // This number should typically increase when you send a media tracking request. + // It should be 0 if the media was only visible/impressed but not played. + // Do not increase this number when a media is paused. + v3.MediaSecondsPlayingTime = r.URL.Query().Get("ma_st") + + // The duration (the length) of the media in seconds. For example if a video is 90 seconds long, the value should be 90. + v3.MediaLength = r.URL.Query().Get("ma_le") + + // The progress / current position within the media. Defines basically at which position within the total length the user is currently playing. + v3.MediaProgress = r.URL.Query().Get("ma_ps") + + // Defines after how many seconds the user has started playing this media. + // For example a user might have seen the poster of the video for 30 seconds before a user actually pressed the play button. + v3.MediaTimeUntilPlay = r.URL.Query().Get("ma_ttp") + + // The resolution width of the media in pixels. Only recommended to be set for videos. + v3.MediaWidth = r.URL.Query().Get("ma_w") + + // The resolution height of the media in pixels. Only recommended to be set for videos. + v3.MediaHeight = r.URL.Query().Get("ma_h") + + // Should be 0 or 1 and defines whether the media is currently viewed in full screen. Only recommended to be set for videos. + switch r.URL.Query().Get("ma_fs") { + case "0": + v3.MediaFullscreen = false + case "1": + v3.MediaFullscreen = true + default: + v3.MediaFullscreen = false + } + + // If set to 0 (send_image=0) Piwik will respond with a HTTP 204 response code instead of a GIF image. + // This improves performance and can fix errors if images are not allowed to be obtained directly (eg Chrome Apps). + // Available since Piwik 2.10.0 + v3.SendImage = r.URL.Query().Get("send_image") + + return v3 + + /* + r.URL.Query().Get("") // + */ + +} + +func stringToTime(s string) time.Time { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return time.Time{} + } + return time.Unix(i, 0) +} + +func atoi(a string) int { + i, err := strconv.Atoi(a) + if err != nil { + panic(err) + } + return i +} diff --git a/piwik/storage.go b/piwik/storage.go new file mode 100644 index 0000000..4930726 --- /dev/null +++ b/piwik/storage.go @@ -0,0 +1,98 @@ +package piwik + +import "time" + +type SiteStorageInterface interface { + Add(site *Site) error + Remove(site *Site) error + Update(site *Site) error + List() ([]*Site, error) + FindId(id string) (*Site, error) + Find(query map[string]interface{}) (*Site, error) + FindAll(query map[string]interface{}) ([]*Site, error) +} + +type VisitStorageInterface interface { + Add(visit *Visit) error + Find(query map[string]interface{}) (*Visit, error) + FindAll(query map[string]interface{}) ([]*Visit, error) +} + +type Site struct { + ID string `bson:"_id" json:"id"` + Name string `bson:"name" json:"name"` + Domain string `bson:"domain" json:"domain"` +} + +type Visit struct { + ID string `bson:"_id" json:"id"` + SiteID string `bson:"site_id" json:"site_id"` //idsite + URL string `bson:"url" json:"url"` //url + ActionName string `bson:"action_name,omitempty" json:"action_name,omitempty"` //action_anem + VisitorID string `bson:"visitor_id,omitempty" json:"visitor_id,omitempty"` //_id + Referrer string `bson:"referrer,omitempty" json:"referrer,omitempty"` //urlref + CustomVars map[string]interface{} `bson:"custom_vars,omitempty" json:"custom_vars,omitempty"` //_cvar + VisitsCount string `bson:"visits_count,omitempty" json:"visits_count,omitempty"` //_idvc + ViewTime time.Time `bson:"view_time,omitempty" json:"view_time,omitempty"` //_viewts + VisitorFirstVisitTime time.Time `bson:"visitor_first_visit_time,omitempty" json:"visitor_first_visit_time,omitempty"` //_idts + CampaignName string `bson:"campaign_name,omitempty" json:"campaign_name,omitempty"` //_rcn + CampaignKeyword string `bson:"campaign_keyword,omitempty" json:"campaign_keyword,omitempty"` //_rck + Resolution string `bson:"resolution,omitempty" json:"resolution,omitempty"` //res + LocalHour string `bson:"local_hour,omitempty" json:"local_hour,omitempty"` //h + LocalMinute string `bson:"local_minute,omitempty" json:"local_minute,omitempty"` //m + LocalSeconds string `bson:"local_seconds,omitempty" json:"local_seconds,omitempty"` //s + LocalTimeUTC time.Time `bson:"local_time_utc,omitempty" json:"local_time_utc,omitempty"` + Plugins *Plugins `bson:"plugins" json:"plugins"` + SupportsCookies bool `bson:"supports_cookies,omitempty" json:"supports_cookies,omitempty"` //cookie + UserAgent string `bson:"user_agent,omitempty" json:"user_agent,omitempty"` //ua + Language string `bson:"language,omitempty" json:"language,omitempty"` //lang + UserID string `bson:"user_id,omitempty" json:"user_id,omitempty"` //uid + CustomUserID string `bson:"custom_user_id,omitempty" json:"custom_user_id,omitempty"` //cid + NewVisit bool `bson:"new_visit,omitempty" json:"new_visit,omitempty"` //new_visit + PageCustomVars map[string]interface{} `bson:"page_custom_vars,omitempty" json:"page_custom_vars,omitempty"` //cvar + Link string `bson:"link,omitempty" json:"link,omitempty"` //link + Download string `bson:"download,omitempty" json:"download,omitempty"` //download + Search string `bson:"search,omitempty" json:"search,omitempty"` //search + SearchCategory string `bson:"search_category,omitempty" json:"search_category,omitempty"` //search_cat + SearchCount string `bson:"search_count,omitempty" json:"search_count,omitempty"` //search_count + PageViewID string `bson:"page_view_id,omitempty" json:"page_view_id,omitempty"` //pv_id + GoalID string `bson:"goal_id,omitempty" json:"goal_id,omitempty"` //idgoal + Revenue string `bson:"revenue,omitempty" json:"revenue,omitempty"` //revenue + GenerationTime string `bson:"generation_time,omitempty" json:"generation_time,omitempty"` //gt_ms + Characterset string `bson:"characterset,omitempty" json:"characterset,omitempty"` //cs + EventCategory string `bson:"event_category,omitempty" json:"event_category,omitempty"` //e_c + EventAction string `bson:"event_action,omitempty" json:"event_action,omitempty"` //e_a + EventName string `bson:"event_name,omitempty" json:"event_name,omitempty"` //e_n + EventValue string `bson:"event_value,omitempty" json:"event_value,omitempty"` //e_v + ContentName string `bson:"content_name,omitempty" json:"content_name,omitempty"` //c_n + ContentPiece string `bson:"content_piece,omitempty" json:"content_piece,omitempty"` //c_p + ContentTarget string `bson:"content_target,omitempty" json:"content_target,omitempty"` //c_t + ContentInteraction string `bson:"content_interaction,omitempty" json:"content_interaction,omitempty"` //c_i + ECommerceID string `bson:"ecommerce_id,omitempty" json:"ecommerce_id,omitempty"` // ec_id + ECommerceItems [][]string `bson:"ecommerce_items,omitempty" json:"ecommerce_items,omitempty"` // ec_items + ECommerceSubTotal string `bson:"ecommerce_sub_total,omitempty" json:"ecommerce_sub_total,omitempty"` // ec_st + ECommerceTax string `bson:"ecommerce_tax,omitempty" json:"ecommerce_tax,omitempty"` // ec_tx + ECommerceShipping string `bson:"ecommerce_shipping,omitempty" json:"ecommerce_shipping,omitempty"` // ec_sh + ECommerceDiscount string `bson:"ecommerce_discount,omitempty" json:"ecommerce_discount,omitempty"` // ec_dt + ECommerceTime time.Time `bson:"ecommerce_time,omitempty" json:"ecommerce_time,omitempty"` // ec_ts + TokenAuth string `bson:"token_auth,omitempty" json:"token_auth,omitempty"` //token_auth + CustomIP string `bson:"custom_ip,omitempty" json:"custom_ip,omitempty"` //cip + CustomDateTime string `bson:"custom_date_time,omitempty" json:"custom_date_time,omitempty"` //cdt + CustomCountry string `bson:"custom_country,omitempty" json:"custom_country,omitempty"` //country + CustomRegion string `bson:"custom_region,omitempty" json:"custom_region,omitempty"` //region + CustomCity string `bson:"custom_city,omitempty" json:"custom_city,omitempty"` //city + CustomLatitude string `bson:"custom_latitude,omitempty" json:"custom_latitude,omitempty"` //lat + CustomLongitude string `bson:"custom_longitude,omitempty" json:"custom_longitude,omitempty"` //long + MediaID string `bson:"media_id,omitempty" json:"media_id,omitempty"` // ma_id + MediaTitle string `bson:"media_title,omitempty" json:"media_title,omitempty"` // ma_ti + MediaResource string `bson:"media_resource,omitempty" json:"media_resource,omitempty"` // ma_re + MediaType string `bson:"media_type,omitempty" json:"media_type,omitempty"` // ma_mt + MediaPlayerName string `bson:"media_player_name,omitempty" json:"media_player_name,omitempty"` // ma_pn + MediaSecondsPlayingTime string `bson:"media_seconds_playing_time,omitempty" json:"media_seconds_playing_time,omitempty"` // ma_st + MediaLength string `bson:"media_length,omitempty" json:"media_length,omitempty"` // ma_le + MediaProgress string `bson:"media_progress,omitempty" json:"media_progress,omitempty"` // ma_ps + MediaTimeUntilPlay string `bson:"media_time_until_play,omitempty" json:"media_time_until_play,omitempty"` // ma_ttp + MediaWidth string `bson:"media_width,omitempty" json:"media_width,omitempty"` // ma_w + MediaHeight string `bson:"media_height,omitempty" json:"media_height,omitempty"` // ma_h + MediaFullscreen bool `bson:"media_fullscreen,omitempty" json:"media_fullscreen,omitempty"` // ma_fs +}