goatcounter

package module
v1.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2020 License: EUPL-1.2, ISC, MIT, + 1 more Imports: 55 Imported by: 0

README

Build Status Awesome Humane Tech

GoatCounter is an open source web analytics platform available as a hosted service (free for non-commercial use) or self-hosted app. It aims to offer easy to use and meaningful privacy-friendly web analytics as an alternative to Google Analytics or Matomo.

There are two ways to run this: as hosted service on goatcounter.com, free for non-commercial use, or run it on your own server (the source code is completely Open Source/Free Software, and it can be self-hosted without restrictions).

See docs/rationale.markdown for some more details on the "why?" of this project.

There's a live demo at https://stats.arp242.net.

Please consider contributing financially if you're self-hosting GoatCounter so I can pay my rent :-)

GoatCounter is sponsored by a grant from NLnet's NGI Zero PET fund.

Features

  • Privacy-aware; doesn't track users with unique identifiers and doesn't need a GDPR consent notice. Also see the privacy policy.

  • Lightweight and fast; adds just ~5K (~2.5K compressed) of extra data to your site. Also has JavaScript-free "tracking pixel" option, or you can use it from your application's middleware.

  • Easy; if you've been confused by the myriad of options and flexibility of Google Analytics and Matomo that you don't need then GoatCounter will be a breath of fresh air.

  • Identify unique visits without cookies using a non-identifiable hash (technical details).

  • Keeps useful statistics such as browser information, location, and screen size. Keep track of referring sites and campaigns.

  • Accessibility is a high-priority feature, and the interface works well with screen readers, no JavaScript, and even text browsers (although not all features work equally well without JS).

  • 100% committed to open source; you can see exactly what the code does and make improvements.

  • Own your data; you can always export all data and cancel at any time.

  • Integrate on your site with just a single script tag:

    <script data-goatcounter="https://yoursite.goatcounter.com/count"
            async src="//gc.zgo.at/count.js"></script>
    
  • The JavaScript integration is a good option for most, but you can also use a no-JavaScript image-based tracker or integrate in your backend middleware.

Technical

  • Fast: can handle about 800 hits/second on a $5/month Linode VPS using the default settings.

  • Self-contained binary: everything – including static assets – is in a single ~7M statically compiled binary. The only other thing you need is a SQLite database file or PostgreSQL connection (no way around that).

Running your own

The release page has binaries for Linux amd64, arm, and arm64. These are statically compiled and should work in pretty much any Linux environment. GoatCounter should run on any platform supported by Go, but there are no binaries for them (yet); you'll have to build from source for now (it's not hard, I promise).

Note this README is for the latest master; use the release-1.4 branch for the 1.4 README.

Generally speaking only the latest release is supported, although critical fixes (security, data loss, etc.) may get backported to previous releases.

Deploy scripts and such

Building from source

Compile from source with:

$ git clone -b release-1.4 https://github.com/zgoat/goatcounter.git
$ cd goatcounter
$ go build -ldflags="-X main.version=$(git log -n1 --format='%h_%cI')" ./cmd/goatcounter

The -ldflags=[..] sets the version; this isn't strictly required as such, but it's recommended as it's used to "bust" the cache for static files and may also be useful later when reporting bugs. This can be any string and doesn't follow any particular format, you can also set this to the current date or banana or anything you want really.

Or to build a statically linked binary:

$ go build -ldflags="-X main.version=$(git log -n1 --format='%h_%cI')" \
    -tags osusergo,netgo,sqlite_omit_load_extension \
    -ldflags='-extldflags=-static' \
    ./cmd/goatcounter

You'll now have a goatcounter binary in the current directory.

You need Go 1.13 or newer and a C compiler (for SQLite), or compile it with CGO_ENABLED=0 go build and use PostgreSQL.

It's recommended to use the latest release as in the above command. The master branch should be reasonably stable but no guarantees, and sometimes I don't write detailed release/upgrade notes until the actual release.

It's not recommended to use go get in GOPATH mode since that will ignore the dependency versions in go.mod.

Running

You can start a server with:

$ goatcounter serve

The default is to use a SQLite database at ./db/goatcounter.sqlite3, which will be created if it doesn't exist yet. See the -db flag and goatcounter help db to customize this.

GoatCounter will listen on port *:80 and *:443 by default. You don't need to run it as root and can grant the appropriate permissions on Linux with:

$ setcap 'cap_net_bind_service=+ep' goatcounter

Listening on a different port can be a bit tricky due to the ACME/Let's Encrypt certificate generation; goatcounter help listen documents this in depth.

You can create new sites with the create command:

$ goatcounter create -email me@example.com -domain stats.example.com

This will ask for a password for your new account; you can also add a password on the commandline with -password. If you use a custom DB, you must also pass the -db flag here.

Updating

You may need to run the database migrations when updating. Use goatcounter -automigrate to always run all pending migrations on startup. This is the easiest way, although arguably not the "best" way.

Use goatcounter migrate <file> or goatcounter migrate all to manually run migrations; generally you want to upload the new version, run migrations while the old one is still running, and then restart so the new version takes effect.

Use goatcounter migrate show to get a list of pending migrations.

PostgreSQL

Both SQLite and PostgreSQL are supported. SQLite should work well for the vast majority of people and is the recommended database engine. PostgreSQL will not be faster in most cases, and the chief reason for adding support in the first place is to support load balancing web requests over multiple servers. To use it:

  1. Create the database, unlike SQLite it's not done automatically (you may need to modify the -db flag):

    $ createdb goatcounter
    $ psql goatcounter -c '\i db/schema.pgsql'
    $ goatcounter -db 'postgresql://dbname=goatcounter' migrate all
    
  2. Run with custom -db flag:

    $ goatcounter serve \
        -db 'postgresql://user=goatcounter dbname=goatcounter sslmode=disable'
    

    See the pq docs for more details on the connection string.

  3. You can compile goatcounter without cgo if you don't use SQLite:

    $ CGO_ENABLED=0 go build -ldflags="-X main.version=$(git log -n1 --format='%h_%cI')" ./cmd/goatcounter
    

    Functionally it doesn't matter too much, but builds will be a bit easier and faster as it won't require a C compiler.

Development/testing

You can start a test/development server with:

$ goatcounter serve -dev

The -dev flag makes some small things a bit more convenient for development; TLS is disabled by default, it will listen on localhost:8081, the application will automatically restart on recompiles, and a few other minor changes.

See .github/CONTRIBUTING.markdown for more details on how to run a development server, write patches, etc.

Various aggregate data files are available at https://www.goatcounter.com/data

Documentation

Index

Constants

View Source
const (
	StateActive  = "a"
	StateRequest = "r"
	StateDeleted = "d"
)

State column values.

View Source
const (
	PlanPersonal     = "personal"
	PlanPersonalPlus = "personalplus" // This is really the "starter" plan.
	PlanBusiness     = "business"
	PlanBusinessPlus = "businessplus"
	PlanChild        = "child"
)

Plan column values.

View Source
const ExportVersion = "1"
View Source
const PathTotals = "TOTAL "

PathTotals is a special path to indicate this is the "total" overview.

Trailing whitespace is trimmed on paths, so this should never conflict.

Variables

View Source
var (
	// Valid UUID for testing: 00112233-4455-6677-8899-aabbccddeeff
	TestSession    = zint.Uint128{0x11223344556677, 0x8899aabbccddeeff}
	TestSeqSession = zint.Uint128{TestSession[0], TestSession[1] + 1}
)
View Source
var (
	RefSchemeHTTP      = ptr("h")
	RefSchemeOther     = ptr("o")
	RefSchemeGenerated = ptr("g")
	RefSchemeCampaign  = ptr("c")
)

ref_scheme column

View Source
var Memstore ms
View Source
var Now = func() time.Time { return time.Now().UTC() }

Now gets the current time in UTC; can be overwritten in tests.

View Source
var SQLiteHook = func(c *sqlite3.SQLiteConn) error {

	return c.RegisterFunc("percent_diff", func(start, final int) float64 {
		if start == 0 {
			return math.Inf(0)
		}
		return float64(float64((final-start)/start) * 100)
	}, true)
}

Functions

func BarChart

func BarChart(ctx context.Context, stats []Stat, max int, daily bool) template.HTML

func ChunkStat

func ChunkStat(stats []Stat) (int, []int)

Compress all the data in to 12 chunks.

func EmailTemplate

func EmailTemplate(tplname string, args interface{}) func() ([]byte, error)

func GetMax

func GetMax(ctx context.Context, start, end time.Time, filter string, daily bool) (int, error)

func GetTotalCount

func GetTotalCount(ctx context.Context, start, end time.Time, filter string) (int, int, error)

func GetTotalCountUTC

func GetTotalCountUTC(ctx context.Context, start, end time.Time, filter string) (int, int, error)

func HorizontalChart

func HorizontalChart(ctx context.Context, stats Stats, total, pageSize int, link, paginate bool) template.HTML

func Import

func Import(ctx context.Context, fp io.Reader, replace, email bool)

Import data from an export.

func NewContext

func NewContext(ctx context.Context) context.Context

NewContext creates a new context with the all the request values set.

Useful for tests, or for "removing" the timeout on the request context so it can be passed to background functions.

func ResetCache

func ResetCache()

func TextChart

func TextChart(ctx context.Context, stats []Stat, max int, daily bool) template.HTML

func WithSite

func WithSite(ctx context.Context, s *Site) context.Context

WithSite adds the site to the context.

func WithUser

func WithUser(ctx context.Context, u *User) context.Context

WithUser adds the site to the context.

Types

type APIToken

type APIToken struct {
	ID     int64 `db:"api_token_id" json:"-"`
	SiteID int64 `db:"site_id" json:"-"`
	UserID int64 `db:"user_id" json:"-"`

	Name        string              `db:"name" json:"name"`
	Token       string              `db:"token" json:"-"`
	Permissions APITokenPermissions `db:"permissions" json:"permissions"`

	CreatedAt time.Time `db:"created_at" json:"-"`
}

func (*APIToken) ByID

func (t *APIToken) ByID(ctx context.Context, id int64) error

func (*APIToken) ByToken

func (t *APIToken) ByToken(ctx context.Context, token string) error

func (*APIToken) Defaults

func (t *APIToken) Defaults(ctx context.Context)

Defaults sets fields to default values, unless they're already set.

func (*APIToken) Delete

func (t *APIToken) Delete(ctx context.Context) error

func (*APIToken) Insert

func (t *APIToken) Insert(ctx context.Context) error

Insert a new row.

func (*APIToken) Validate

func (t *APIToken) Validate(ctx context.Context) error

type APITokenPermissions

type APITokenPermissions struct {
	Count      bool `db:"count" json:"count"`
	Export     bool `db:"export" json:"export"`
	SiteRead   bool `db:"site_read" json:"site_read"`
	SiteCreate bool `db:"site_create" json:"site_create"`
	SiteUpdate bool `db:"site_update" json:"site_update"`
}

func (*APITokenPermissions) Scan

func (tp *APITokenPermissions) Scan(v interface{}) error

Scan converts the data returned from the DB into the struct.

func (APITokenPermissions) String

func (tp APITokenPermissions) String() string

func (APITokenPermissions) Value

func (tp APITokenPermissions) Value() (driver.Value, error)

Value implements the SQL Value function to determine what to store in the DB.

type APITokens

type APITokens []APIToken

func (*APITokens) List

func (t *APITokens) List(ctx context.Context) error

type AdminBotlog

type AdminBotlog struct {
	ID int64 `json:"id"`
	IP int64 `json:"ip"`

	Bot       int         `db:"bot"`
	UserAgent string      `db:"ua"`
	Headers   http.Header `db:"headers"`
	URL       string      `db:"url"`
}

func (AdminBotlog) Insert

func (b AdminBotlog) Insert(ctx context.Context, ip string) error

type AdminBotlogIP

type AdminBotlogIP struct {
	ID        int64     `db:"id"`
	Count     int       `db:"count"`
	IP        string    `db:"ip"`
	PTR       *string   `db:"ptr"`
	Info      *string   `db:"info"`
	Hide      zdb.Bool  `db:"hide"`
	CreatedAt time.Time `db:"created_at"`
	LastSeen  time.Time `db:"last_seen"`

	Links [][]string `db:"-"`
}

type AdminBotlogIPs

type AdminBotlogIPs []AdminBotlogIP

func (*AdminBotlogIPs) List

func (b *AdminBotlogIPs) List(ctx context.Context) error

type AdminSiteStat

type AdminSiteStat struct {
	Site           Site      `db:"-"`
	User           User      `db:"-"`
	LastData       time.Time `db:"last_data"`
	CountTotal     int       `db:"count_total"`
	CountLastMonth int       `db:"count_last_month"`
	CountPrevMonth int       `db:"count_prev_month"`
}

func (*AdminSiteStat) ByCode

func (a *AdminSiteStat) ByCode(ctx context.Context, code string) error

ByCode gets stats for a single site.

func (*AdminSiteStat) ByID

func (a *AdminSiteStat) ByID(ctx context.Context, id int64) error

ByID gets stats for a single site.

type AdminStat

type AdminStat struct {
	ID            int64     `db:"id"`
	Parent        *int64    `db:"parent"`
	Code          string    `db:"code"`
	Stripe        *string   `db:"stripe"`
	BillingAmount *string   `db:"billing_amount"`
	LinkDomain    string    `db:"link_domain"`
	Email         string    `db:"email"`
	CreatedAt     time.Time `db:"created_at"`
	Plan          string    `db:"plan"`
	LastMonth     int       `db:"last_month"`
	Total         int       `db:"total"`
}

type AdminStats

type AdminStats []AdminStat

func (*AdminStats) List

func (a *AdminStats) List(ctx context.Context) error

List stats for all sites, for all time.

type Export

type Export struct {
	ID     int64 `db:"export_id" json:"id,readonly"`
	SiteID int64 `db:"site_id" json:"site_id,readonly"`

	// The hit ID this export was started from.
	StartFromHitID int64 `db:"start_from_hit_id" json:"start_from_hit_id"`

	// Last hit ID that was exported; can be used as start_from_hit_id.
	LastHitID *int64 `db:"last_hit_id" json:"last_hit_id,readonly"`

	Path      string    `db:"path" json:"path,readonly"` // {omitdoc}
	CreatedAt time.Time `db:"created_at" json:"created_at,readonly"`

	FinishedAt *time.Time `db:"finished_at" json:"finished_at,readonly"`
	NumRows    *int       `db:"num_rows" json:"num_rows,readonly"`

	// File size in MB.
	Size *string `db:"size" json:"size,readonly"`

	// SHA256 hash.
	Hash *string `db:"hash" json:"hash,readonly"`

	// Any errors that may have occured.
	Error *string `db:"error" json:"error,readonly"`
}

func (*Export) ByID

func (e *Export) ByID(ctx context.Context, id int64) error

func (*Export) Create

func (e *Export) Create(ctx context.Context, startFrom int64) (*os.File, error)

Create a new export.

Inserts a row in exports table and returns open file pointer to the destination file.

func (*Export) Run

func (e *Export) Run(ctx context.Context, fp *os.File, mailUser bool)

Export all data to a CSV file.

type ExportRow

type ExportRow struct {
	Path       string
	Title      string
	Event      string
	Bot        string
	Session    string
	FirstVisit string
	Ref        string
	RefScheme  string
	Browser    string
	Size       string
	Location   string
	CreatedAt  string
}

func (ExportRow) Hit

func (row ExportRow) Hit(siteID int64) (Hit, error)

func (*ExportRow) Read

func (row *ExportRow) Read(line []string) error

type Exports

type Exports []Export

func (*Exports) List

func (e *Exports) List(ctx context.Context) error

type Hit

type Hit struct {
	ID      int64        `db:"id" json:"-"`
	Site    int64        `db:"site" json:"-"`
	Session zint.Uint128 `db:"session2" json:"-"`

	// Old incremental session IDs; kinda expensive to migrate, and only used
	// for export. Will migrate later when I refactor the hits table.
	OldSession *int64 `db:"session" json:"-"`

	Path  string     `db:"path" json:"p,omitempty"`
	Title string     `db:"title" json:"t,omitempty"`
	Ref   string     `db:"ref" json:"r,omitempty"`
	Event zdb.Bool   `db:"event" json:"e,omitempty"`
	Size  zdb.Floats `db:"size" json:"s,omitempty"`
	Query string     `db:"-" json:"q,omitempty"`
	Bot   int        `db:"bot" json:"b,omitempty"`

	RefScheme  *string   `db:"ref_scheme" json:"-"`
	Browser    string    `db:"browser" json:"-"`
	Location   string    `db:"location" json:"-"`
	FirstVisit zdb.Bool  `db:"first_visit" json:"-"`
	CreatedAt  time.Time `db:"created_at" json:"-"`

	RefURL *url.URL `db:"-" json:"-"`   // Parsed Ref
	Random string   `db:"-" json:"rnd"` // Browser cache buster, as they don't always listen to Cache-Control

	// Some values we need to pass from the HTTP handler to memstore
	RemoteAddr    string `db:"-" json:"-"`
	UserSessionID string `db:"-" json:"-"`
}

func (*Hit) Defaults

func (h *Hit) Defaults(ctx context.Context)

Defaults sets fields to default values, unless they're already set.

func (Hit) String

func (h Hit) String() string

func (*Hit) Validate

func (h *Hit) Validate(ctx context.Context) error

Validate the object.

type HitStat

type HitStat struct {
	Count       int      `db:"count"`
	CountUnique int      `db:"count_unique"`
	Path        string   `db:"path"`
	Event       zdb.Bool `db:"event"`
	Title       string   `db:"title"`
	RefScheme   *string  `db:"ref_scheme"`
	Max         int
	Stats       []Stat
}

func (*HitStat) Totals

func (h *HitStat) Totals(ctx context.Context, start, end time.Time, filter string, daily bool) (int, error)

Totals gets the totals overview of all pages.

type HitStats

type HitStats []HitStat

func (*HitStats) List

func (h *HitStats) List(
	ctx context.Context, start, end time.Time, filter string, exclude []string, daily bool,
) (int, int, bool, error)

List the top paths for this site in the given time period.

func (*HitStats) ListPathsLike

func (h *HitStats) ListPathsLike(ctx context.Context, path string, matchTitle bool) error

ListPathsLike lists all paths matching the like pattern.

func (*HitStats) PathCountUnique

func (h *HitStats) PathCountUnique(ctx context.Context, path string) error

PathCountUnique gets the total_unique for one path.

func (*HitStats) SiteTotalUnique

func (h *HitStats) SiteTotalUnique(ctx context.Context) error

SiteTotalUnique gets the total_unique for all paths.

type Hits

type Hits []Hit

func (*Hits) Count

func (h *Hits) Count(ctx context.Context) (int64, error)

Count the number of pageviews.

func (*Hits) List

func (h *Hits) List(ctx context.Context, limit, paginate int64) (int64, error)

List all hits for a site, including bot requests.

func (*Hits) Purge

func (h *Hits) Purge(ctx context.Context, path string, matchTitle bool) error

Purge all paths matching the like pattern.

type Site

type Site struct {
	ID     int64  `db:"id" json:"id,readonly"`
	Parent *int64 `db:"parent" json:"parent,readonly"`

	// Custom domain, e.g. "stats.example.com"
	Cname *string `db:"cname" json:"cname"`

	// When the CNAME was verified.
	CnameSetupAt *time.Time `db:"cname_setup_at" json:"cname_setup_at,readonly"`

	// Domain code (arp242, which makes arp242.goatcounter.com)
	Code string `db:"code" json:"code"`

	// Site domain for linking (www.arp242.net).
	LinkDomain    string       `db:"link_domain" json:"link_domain"`
	Plan          string       `db:"plan" json:"plan"`
	Stripe        *string      `db:"stripe" json:"-"`
	BillingAmount *string      `db:"billing_amount" json:"-"`
	Settings      SiteSettings `db:"settings" json:"setttings"`

	// Whether this site has received any data; will be true after the first
	// pageview.
	ReceivedData bool `db:"received_data" json:"received_data"`

	State     string     `db:"state" json:"state"`
	CreatedAt time.Time  `db:"created_at" json:"created_at"`
	UpdatedAt *time.Time `db:"updated_at" json:"updated_at"`
}

func GetSite

func GetSite(ctx context.Context) *Site

GetSite gets the current site.

func MustGetSite

func MustGetSite(ctx context.Context) *Site

MustGetSite behaves as GetSite(), panicking if this fails.

func (Site) Admin

func (s Site) Admin() bool

Admin reports if this site is an admin.

func (*Site) ByCode

func (s *Site) ByCode(ctx context.Context, code string) error

ByCode gets a site by code.

func (*Site) ByHost

func (s *Site) ByHost(ctx context.Context, host string) error

ByHost gets a site by host name.

func (*Site) ByID

func (s *Site) ByID(ctx context.Context, id int64) error

ByID gets a site by ID.

func (*Site) Defaults

func (s *Site) Defaults(ctx context.Context)

Defaults sets fields to default values, unless they're already set.

func (*Site) Delete

func (s *Site) Delete(ctx context.Context) error

Delete a site.

func (Site) DeleteAll

func (s Site) DeleteAll(ctx context.Context) error

DeleteAll deletes all pageviews for this site, keeping the site itself and user intact.

func (Site) DeleteOlderThan

func (s Site) DeleteOlderThan(ctx context.Context, days int) error

func (Site) Display

func (s Site) Display() string

Display format: just the domain (cname or code+domain).

func (Site) Domain

func (s Site) Domain() string

Domain gets the global default domain, or this site's configured custom domain.

func (Site) FreePlan

func (s Site) FreePlan() bool

func (Site) IDOrParent

func (s Site) IDOrParent() int64

IDOrParent gets this site's ID or the parent ID if that's set.

func (*Site) Insert

func (s *Site) Insert(ctx context.Context) error

Insert a new row.

func (*Site) ListSubs

func (s *Site) ListSubs(ctx context.Context) ([]string, error)

ListSubs lists all subsites, including the current site and parent.

func (Site) PayExternal

func (s Site) PayExternal() string

func (Site) PlanCustomDomain

func (s Site) PlanCustomDomain(ctx context.Context) bool

PlanCustomDomain reports if this site's plan allows custom domains.

func (Site) ShowPayBanner

func (s Site) ShowPayBanner(ctx context.Context) bool

ShowPayBanner determines if we should show a "please pay" banner for the customer.

func (Site) URL

func (s Site) URL() string

URL to this site.

func (*Site) Update

func (s *Site) Update(ctx context.Context) error

Update existing.

func (*Site) UpdateCnameSetupAt

func (s *Site) UpdateCnameSetupAt(ctx context.Context) error

UpdateCnameSetupAt confirms the custom domain was setup correct.

func (*Site) UpdateCode

func (s *Site) UpdateCode(ctx context.Context, code string) error

UpdateCode changes the site's domain code (e.g. "test" in "test.goatcounter.com").

func (*Site) UpdateReceivedData

func (s *Site) UpdateReceivedData(ctx context.Context) error

func (*Site) UpdateStripe

func (s *Site) UpdateStripe(ctx context.Context, stripeID, plan, amount string) error

UpdateStripe sets the Stripe customer ID.

func (*Site) Validate

func (s *Site) Validate(ctx context.Context) error

Validate the object.

type SiteSettings

type SiteSettings struct {
	Public           bool        `json:"public"`
	AllowCounter     bool        `json:"allow_counter"`
	TwentyFourHours  bool        `json:"twenty_four_hours"`
	SundayStartsWeek bool        `json:"sunday_starts_week"`
	DateFormat       string      `json:"date_format"`
	NumberFormat     rune        `json:"number_format"`
	DataRetention    int         `json:"data_retention"`
	IgnoreIPs        zdb.Strings `json:"ignore_ips"`
	Timezone         *tz.Zone    `json:"timezone"`
	Campaigns        zdb.Strings `json:"campaigns"`
	AllowAdmin       bool        `json:"allow_admin"`
	Limits           struct {
		Page   int `json:"page"`
		Ref    int `json:"ref"`
		Hchart int `json:"hchart"`
	} `json:"limits"`
}

func (*SiteSettings) Scan

func (ss *SiteSettings) Scan(v interface{}) error

Scan converts the data returned from the DB into the struct.

func (SiteSettings) String

func (ss SiteSettings) String() string

func (SiteSettings) Value

func (ss SiteSettings) Value() (driver.Value, error)

Value implements the SQL Value function to determine what to store in the DB.

type Sites

type Sites []Site

Sites is a list of sites.

func (*Sites) ContainsCNAME

func (s *Sites) ContainsCNAME(ctx context.Context, cname string) (bool, error)

ContainsCNAME reports if there is a site with this CNAME set.

func (*Sites) ListSubs

func (s *Sites) ListSubs(ctx context.Context) error

ListSubs lists all subsites for the current site.

func (*Sites) OldSoftDeleted

func (s *Sites) OldSoftDeleted(ctx context.Context) error

OldSoftDeleted finds all sites which have been soft-deleted more than a week ago.

func (*Sites) UnscopedList

func (s *Sites) UnscopedList(ctx context.Context) error

UnscopedList lists all sites, not scoped to the current user.

func (*Sites) UnscopedListCnames

func (s *Sites) UnscopedListCnames(ctx context.Context) error

UnscopedListCnames all sites that have CNAME set, not scoped to the current user.

type Stat

type Stat struct {
	Day          string
	Hourly       []int
	HourlyUnique []int
	Daily        int
	DailyUnique  int
}

type StatT

type StatT struct {
	// TODO: should be Stat, but that's already taken and don't want to rename
	// everything right now.
	Name        string  `db:"name"`
	Count       int     `db:"count"`
	CountUnique int     `db:"count_unique"`
	RefScheme   *string `db:"ref_scheme"`
}

type Stats

type Stats struct {
	More  bool
	Stats []StatT
}

func (*Stats) ByRef

func (h *Stats) ByRef(ctx context.Context, start, end time.Time, ref string) error

ByRef lists all paths by reference.

func (*Stats) ListBrowser

func (h *Stats) ListBrowser(ctx context.Context, browser string, start, end time.Time) error

ListBrowser lists all the versions for one browser.

func (*Stats) ListBrowsers

func (h *Stats) ListBrowsers(ctx context.Context, start, end time.Time, limit, offset int) error

ListBrowsers lists all browser statistics for the given time period.

func (*Stats) ListLocations

func (h *Stats) ListLocations(ctx context.Context, start, end time.Time, limit, offset int) error

ListLocations lists all location statistics for the given time period.

func (*Stats) ListRefsByPath

func (h *Stats) ListRefsByPath(ctx context.Context, path string, start, end time.Time, offset int) error

ListRefsByPath lists all references for a path.

func (*Stats) ListSize

func (h *Stats) ListSize(ctx context.Context, name string, start, end time.Time) error

ListSize lists all sizes for one grouping.

func (*Stats) ListSizes

func (h *Stats) ListSizes(ctx context.Context, start, end time.Time) error

ListSizes lists all device sizes.

func (*Stats) ListSystem

func (h *Stats) ListSystem(ctx context.Context, system string, start, end time.Time) error

ListSystem lists all the versions for one system.

func (*Stats) ListSystems

func (h *Stats) ListSystems(ctx context.Context, start, end time.Time, limit, offset int) error

ListSystems lists OS statistics for the given time period.

func (*Stats) ListTopRefs

func (h *Stats) ListTopRefs(ctx context.Context, start, end time.Time, offset int) error

ListTopRefs lists all ref statistics for the given time period, excluding referrals from the configured LinkDomain.

The returned count is the count without LinkDomain, and is different from the total number of hits.

type Update

type Update struct {
	ID        int64     `db:"id"`
	Subject   string    `db:"subject"`
	Body      string    `db:"body"`
	CreatedAt time.Time `db:"created_at"`
	ShowAt    time.Time `db:"show_at"`
	Seen      bool      `db:"-"`
}

type Updates

type Updates []Update

func (*Updates) HasSince

func (u *Updates) HasSince(ctx context.Context, since time.Time) (bool, error)

HasSince reports if there are any updates since the given date.

func (*Updates) List

func (u *Updates) List(ctx context.Context, since time.Time) error

List all updates.

type User

type User struct {
	ID   int64 `db:"id" json:"id,readonly"`
	Site int64 `db:"site" json:"site,readonly"`

	Email         string     `db:"email" json:"email"`
	EmailVerified zdb.Bool   `db:"email_verified" json:"email_verified,readonly"`
	Password      []byte     `db:"password" json:"-"`
	TOTPEnabled   zdb.Bool   `db:"totp_enabled" json:"totp_enabled,readonly"`
	TOTPSecret    []byte     `db:"totp_secret" json:"-"`
	Role          string     `db:"role" json:"role,readonly"`
	LoginAt       *time.Time `db:"login_at" json:"login_at,readonly"`
	ResetAt       *time.Time `db:"reset_at" json:"reset_at,readonly"`
	LoginRequest  *string    `db:"login_request" json:"-"`
	LoginToken    *string    `db:"login_token" json:"-"`
	CSRFToken     *string    `db:"csrf_token" json:"-"`
	EmailToken    *string    `db:"email_token" json:"-"`
	SeenUpdatesAt time.Time  `db:"seen_updates_at" json:"-"`

	CreatedAt time.Time  `db:"created_at" json:"created_at,readonly"`
	UpdatedAt *time.Time `db:"updated_at" json:"updated_at,readonly"`
}

User entry.

func GetUser

func GetUser(ctx context.Context) *User

GetUser gets the currently logged in user.

func (*User) ByEmail

func (u *User) ByEmail(ctx context.Context, email string) error

ByEmail gets a user by email address.

func (*User) ByEmailToken

func (u *User) ByEmailToken(ctx context.Context, key string) error

ByEmailToken gets a user by email verification token.

func (*User) ByResetToken

func (u *User) ByResetToken(ctx context.Context, key string) error

ByResetToken gets a user by login request key.

func (*User) BySite

func (u *User) BySite(ctx context.Context, id int64) error

BySite gets a user by site.

func (*User) ByToken

func (u *User) ByToken(ctx context.Context, token string) error

ByToken gets a user by login token.

func (*User) ByTokenAndSite

func (u *User) ByTokenAndSite(ctx context.Context, token string) error

ByTokenAndSite gets a user by login token.

func (User) CorrectPassword

func (u User) CorrectPassword(pwd string) (bool, error)

CorrectPassword verifies that this password is correct.

func (*User) Defaults

func (u *User) Defaults(ctx context.Context)

Defaults sets fields to default values, unless they're already set.

func (*User) DisableTOTP

func (u *User) DisableTOTP(ctx context.Context) error

func (*User) EnableTOTP

func (u *User) EnableTOTP(ctx context.Context) error

func (*User) GetToken

func (u *User) GetToken() string

GetToken gets the CSRF token.

func (*User) Insert

func (u *User) Insert(ctx context.Context) error

Insert a new row.

func (*User) Login

func (u *User) Login(ctx context.Context) error

Login a user; create a new key, CSRF token, and reset the request date.

func (*User) Logout

func (u *User) Logout(ctx context.Context) error

Logout a user.

func (*User) RequestReset

func (u *User) RequestReset(ctx context.Context) error

RequestReset generates a new password reset key.

func (*User) SeenUpdates

func (u *User) SeenUpdates(ctx context.Context) error

SeenUpdates marks this user as having seen all updates up until now.

func (*User) Update

func (u *User) Update(ctx context.Context, emailChanged bool) error

Update this user's name, email.

func (*User) UpdatePassword

func (u *User) UpdatePassword(ctx context.Context, pwd string) error

UpdatePassword updates this user's password.

func (*User) Validate

func (u *User) Validate(ctx context.Context, validatePassword bool) error

Validate the object.

func (*User) VerifyEmail

func (u *User) VerifyEmail(ctx context.Context) error

func (User) Widgets

func (u User) Widgets() []string

type Users

type Users []User

func (*Users) ByEmail

func (u *Users) ByEmail(ctx context.Context, email string) error

ByEmail gets all users with this email address.

Directories

Path Synopsis
Package bgrun allows simple synchronisation of goroutines.
Package bgrun allows simple synchronisation of goroutines.
Package cfg contains global application configuration settings.
Package cfg contains global application configuration settings.
cmd
Package cron schedules jobs.
Package cron schedules jobs.
db
Package gctest contains testing helpers.
Package gctest contains testing helpers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL