goatcounter

package module
v2.7.0 Latest Latest
Warning

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

Go to latest
Published: Dec 15, 2025 License: EUPL-1.2, ISC, MIT, + 1 more Imports: 68 Imported by: 0

README

GoatCounter is an open source web analytics platform available as a (free) hosted service 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, 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.md for some more details on the "why?" of this project.

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

Please consider contributing financially.

Features

  • Privacy-aware; doesn’t track users with unique identifiers and doesn't need a GDPR notice. Fine-grained control over which data is collected. Also see the privacy policy and GDPR consent notices.

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

  • 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.

  • 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.

  • Accessibility is a high-priority feature, and the interface works well with assistive technology such as screen readers.

  • 100% committed to open source; you can see exactly what the code does and make improvements, or self-host it for any purpose.

  • 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, integrate it in your backend middleware, or parse log files.

Getting data in to GoatCounter

There are three ways:

  1. Add the JavaScript code on your site; this is the easiest and most common method. Detailed documentation for this is available at https://www.goatcounter.com/code

  2. Use the HTTP/REST API, for example from your backend server middleware. Detailed documentation for this is available at https://www.goatcounter.com/api#backend-integration

  3. Parse logfiles of nginx, Apache, Caddy, CloudFront, or any other HTTP server or proxy. See goatcounter help import for detailed documentation on this (this works both for the self-hosted version and goatcounter.com).

Self-hosting GoatCounter

The release page has binaries for several platforms. These are statically compiled and contain everything you need. These should work in pretty much any environment. The only dependency is somewhere to store a SQLite database file or a PostgreSQL connection. Alternatively you can use Docker, as documented in the section below.

Running

You can start a server with:

% goatcounter serve

This will start a server on *:8080. The default is to use an SQLite database at ./goatcounter-data/db.sqlite3, which will be created if it doesn't exist yet.

Both SQLite and PostgreSQL are supported. SQLite should work well for most smaller sites, but PostgreSQL gives better performance especially for larger sites. There are some benchmarks over here to give some indication of what performance to expect from SQLite and PostgreSQL.

To create the first site, use the wizard on http://localhost:8080 or the CLI with:

% goatcounter db create site -vhost=stats.example.com -user.email=me@example.com

This will ask for a password; you can also add a password on the commandline with -password. You must also pass the -db flag here if you use something other than the default.

GoatCounter includes TLS and automatic ACME certificate generation; to run in production you probably want something like:

% goatcounter serve -listen=:443 -tls=tls,rdr,acme

See goatcounter help serve for details.

PostgreSQL

To use PostgreSQL, run GoatCounter with a custom -db flag. For example:

% goatcounter serve -db 'postgresql+dbname=goatcounter'
% goatcounter serve -db 'postgresql+host=/run/postgresql dbname=goatcounter sslmode=disable'

This follows the format in the psql CLI; you can also use the PG* environment variables:

% PGDATABASE=goatcounter PGHOST=/run/postgresql goatcounter serve -db 'postgresql'

The database will be created automatically if possible; if you want to create it for a specific user you can use:

% createuser --interactive --pwprompt goatcounter
% createdb --owner goatcounter goatcounter

You can manually import the schema with:

% goatcounter db schema-pgsql | psql --user=goatcounter --dbname=goatcounter

See goatcounter help db and the pq docs for more details.

Running with Docker

GoatCounter is available on DockerHub at arp242/goatcounter.

Example to run a new container:

% docker run \
    -p 8080:8080 \
    -v goatcounter-data:/home/goatcounter/goatcounter-data \
    arp242/goatcounter

This uses a named volume, which is recommended as this stores the SQLite database and ACME certificates (when using ACME) and anonymous volumes can be easy to accidentally delete.

To create the first site, use the wizard on http://localhost:8080 or the CLI with:

% docker exec -it [..] goatcounter db create site -vhost=stats.example.com -user.email=me@example.com

To set options you can use GOATCOUNTER_.. environment variables. For example to enable TLS and automatic certificate generation:

% docker run \
    -p 80:80 \
    -p 443:443 \
    -v goatcounter-data:/home/goatcounter/goatcounter-data \
    -e GOATCOUNTER_LISTEN=:443 \
    -e GOATCOUNTER_TLS=tls,rdr,acme \
    arp242/goatcounter

Set GOATCOUNTER_DB to use PostgreSQL. For example:

% docker run \
    -p 8080:8080 \
    -v goatcounter-data:/home/goatcounter/goatcounter-data \
    -e GOATCOUNTER_DB='postgresql+postgresql://goatcounter:goatcounter@postgres:5432/goatcounter?sslmode=disable' \
    arp242/goatcounter

See goatcounter help serve (or: docker run --rm arp242/goatcounter help serve) for all options.

All of the above should also work with Podman.

You can also run GoatCounter from compose.yaml with docker compose. For a basic SQLite setup:

% docker compose up -d goatcounter-sqlite

Or PostgreSQL (also starts PostgreSQL from compose.yaml):

% docker compose up -d goatcounter-postgres

Updating

You may need to run the database migrations when updating. Use goatcounter serve -automigrate to always run all pending migrations on startup.

Use goatcounter db migrate <file> or goatcounter db migrate all to manually run migrations.

Use goatcounter db migrate pending to get a list of pending migrations, or goatcounter db migrate list to show all migrations.

Building from source

You need Go 1.21 or newer and a C compiler. If you compile it with CGO_ENABLED=0 you don't need a C compiler but can only use PostgreSQL.

You can build from source with:

% git clone --branch=release-2.7 https://github.com/arp242/goatcounter
% cd goatcounter
% go build ./cmd/goatcounter

Which will produce a goatcounter binary in the current directory.

To use the latest development version switch to the main branch.

To build a fully statically linked binary:

% go build -trimpath -ldflags='-s -w -extldflags=-static' \
    -tags='osusergo,netgo,sqlite_omit_load_extension' \
    ./cmd/goatcounter

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

You can compile goatcounter without cgo if you're planning to use PostgreSQL and don't use SQLite:

% CGO_ENABLED=0 go build ./cmd/goatcounter

This will create a statically linked binary by default; no extra flags needed. Functionally it doesn't matter too much, but builds will be a bit easier and faster as you won't need 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: the application will automatically restart on recompiles, templates and static files will be read directly from the filesystem, and a few other minor changes.

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

Documentation

Index

Constants

View Source
const (
	APIPermNothing    zint.Bitflag64 = 1 << iota
	APIPermCount                     // 2
	APIPermExport                    // 4
	APIPermSiteRead                  // 8
	APIPermSiteCreate                // 16
	APIPermSiteUpdate                // 32
	APIPermStats                     // 64
)

APIToken permissions.

DO NOT change the values of these constants; they're stored in the database.

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

State column values.

View Source
const (
	GroupHourly = Group(iota)
	GroupDaily
)
View Source
const (
	CollectNothing        zint.Bitflag16 = 1 << iota
	CollectReferrer                      // 2
	CollectUserAgent                     // 4
	CollectScreenSize                    // 8
	CollectLocation                      // 16
	CollectLocationRegion                // 32
	CollectLanguage                      // 64
	CollectSession                       // 128
	CollectHits                          // 256
)

Settings.Collect values (bitmask)

DO NOT change the values of these constants; they're stored in the database.

Note: also update CollectFlags() method below.

Nothing is 1 and 0 is "unset". This is so we can distinguish between "this field was never sent in the form" vs. "user unchecked all boxes".

View Source
const (
	EmailReportNever = EmailReport(iota) // Email once after 2 weeks; for new sites.
	EmailReportDaily
	EmailReportWeekly
	EmailReportBiWeekly
	EmailReportMonthly
)

UserSettings.EmailReport values.

View Source
const ExportCSVVersion = "2"
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      = "h"
	RefSchemeOther     = "o"
	RefSchemeGenerated = "g"
	RefSchemeCampaign  = "c"
)

ref_scheme column

View Source
var Bundle = func() *z18n.Bundle {
	b, err := newBundle(TranslationFiles)
	if err != nil {
		panic(err)
	}
	return b
}()

DB contains all files in db/*

View Source
var Memstore ms
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(final - start)) / float64(start) * 100.0
	}, true)
}
View Source
var SessionTime = 8 * time.Hour

SessionTime is the maximum length of sessions; exported here for tests.

View Source
var Static embed.FS

Static contains all the static files to serve.

View Source
var Tables = struct {
	HitCounts, RefCounts, HitStats              tbl
	BrowserStats, SystemStats, SizeStats        tbl
	LocationStats, LanguageStats, CampaignStats tbl
}{
	HitCounts: tbl{
		Table:      "hit_counts",
		Columns:    []string{"site_id", "path_id", "hour", "total"},
		Constraint: "site_id#path_id#hour",
		Update:     `total = hit_counts.total + excluded.total`,
	},
	RefCounts: tbl{
		Table:      "ref_counts",
		Columns:    []string{"site_id", "path_id", "hour", "ref_id", "total"},
		Constraint: "site_id#path_id#ref_id#hour",
		Update:     `total = ref_counts.total + excluded.total`,
	},
	BrowserStats: tbl{
		Table:      "browser_stats",
		Columns:    []string{"site_id", "path_id", "day", "browser_id", "count"},
		Constraint: "site_id#path_id#day#browser_id",
		Update:     `count = browser_stats.count + excluded.count`,
	},
	SystemStats: tbl{
		Table:      "system_stats",
		Columns:    []string{"site_id", "path_id", "day", "system_id", "count"},
		Constraint: "site_id#path_id#day#system_id",
		Update:     `count = system_stats.count + excluded.count`,
	},
	LocationStats: tbl{
		Table:      "location_stats",
		Columns:    []string{"site_id", "path_id", "day", "location", "count"},
		Constraint: "site_id#path_id#day#location",
		Update:     `count = location_stats.count + excluded.count`,
	},
	SizeStats: tbl{
		Table:      "size_stats",
		Columns:    []string{"site_id", "path_id", "day", "width", "count"},
		Constraint: "site_id#path_id#day#width",
		Update:     `count = size_stats.count + excluded.count`,
	},
	LanguageStats: tbl{
		Table:      "language_stats",
		Columns:    []string{"site_id", "path_id", "day", "language", "count"},
		Constraint: "site_id#path_id#day#language",
		Update:     `count = language_stats.count + excluded.count`,
	},
	CampaignStats: tbl{
		Table:      "campaign_stats",
		Columns:    []string{"site_id", "path_id", "day", "campaign_id", "ref", "count"},
		Constraint: "site_id#path_id#campaign_id#ref#day",
		Update:     `count = campaign_stats.count + excluded.count`,
	},
	HitStats: tbl{
		Table:      "hit_stats",
		Columns:    []string{"site_id", "path_id", "day", "stats"},
		Constraint: "site_id#path_id#day",
		Update: `
			stats = (
				with x as (
					select
						unnest(string_to_array(trim(hit_stats.stats, '[]'), ',')::int[]) as orig,
						unnest(string_to_array(trim(excluded.stats,  '[]'), ',')::int[]) as new
				)
				select '[' || array_to_string(array_agg(orig + new), ',') || ']' from x
			) `,
	},
}
View Source
var Templates embed.FS

Templates contains all templates.

View Source
var TranslationFiles = func() fs.FS {
	fsys, _ := fs.Sub(translations, "i18n")
	return fsys
}()

TranslationFiles gets the translation messages.

View Source
var Version = "dev"

Version of GoatCounter; set at compile-time with:

-ldflags="-X zgo.at/goatcounter/v2.Version=…"

Functions

func DefaultLocale added in v2.5.0

func DefaultLocale() *z18n.Locale

func HorizontalChart

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

func ImportCSV added in v2.7.0

func ImportCSV(
	ctx context.Context, fp io.Reader, replace, email bool,
	persist func(Hit, bool),
) (*time.Time, error)

ImportCSV imports data from a CSV export.

The persist() callback will be called for every hit; you usually want to collect a bunch of them and then persist them.

After everything is done, this will be called once more with an empty hit and final set to true, to persist the last batch.

func ImportGA added in v2.6.0

func ImportGA(ctx context.Context, fp io.Reader) error

func Interval added in v2.6.0

func Interval(ctx context.Context, days int) string

TODO: Move to zdb

func ListCache

func ListCache(ctx context.Context) map[string]struct {
	Size  int64
	Items map[string]string
}

func LoadBufferKey

func LoadBufferKey(ctx context.Context) ([]byte, error)

func NewBufferKey

func NewBufferKey(ctx context.Context) (string, error)

func NewCache

func NewCache(ctx context.Context) context.Context

func NewConfig

func NewConfig(ctx context.Context) context.Context

func NewContext

func NewContext(ctx context.Context, db zdb.DB) context.Context

NewContext creates a new context with all values set.

func NewValidate

func NewValidate(ctx context.Context) zvalidate.Validator

func UUID added in v2.2.0

func UUID() zint.Uint128

UUID created a new UUID v4.

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     APITokenID `db:"api_token_id" json:"-"`
	SiteID SiteID     `db:"site_id" json:"-"`
	UserID UserID     `db:"user_id" json:"-"`

	Name        string         `db:"name" json:"name"`
	Token       string         `db:"token" json:"-"`
	Permissions zint.Bitflag64 `db:"permissions" json:"permissions"`
	Sites       SiteIDs        `db:"sites" json:"sites"`

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

func (*APIToken) ByID

func (t *APIToken) ByID(ctx context.Context, id APITokenID) 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) FormatPermissions

func (t APIToken) FormatPermissions() string

func (*APIToken) Insert

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

Insert a new row.

func (APIToken) PermissionFlags

func (t APIToken) PermissionFlags(only ...zint.Bitflag64) []PermissionFlag

PermissionFlags returns a list of all flags we know for the Permissions settings.

func (*APIToken) Update

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

Update the name and permissions.

func (*APIToken) UpdateLastUsed added in v2.4.0

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

UpdateLastUsed sets the last used time to the current time.

func (*APIToken) Validate

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

type APITokenID added in v2.7.0

type APITokenID int32

type APITokens

type APITokens []APIToken

func (*APITokens) Delete

func (t *APITokens) Delete(ctx context.Context, _ bool) error

Delete all API tokens in this selection.

func (*APITokens) Find

func (t *APITokens) Find(ctx context.Context, ident []string) error

Find API tokens: by ID if ident is a number, or by token if it's not.

func (*APITokens) IDs

func (t *APITokens) IDs() []int32

IDs gets a list of all IDs for these API tokens.

func (*APITokens) List

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

type Browser

type Browser struct {
	ID      BrowserID `db:"browser_id"`
	Name    string    `db:"name"`
	Version string    `db:"version"`
}

func (*Browser) GetOrInsert

func (b *Browser) GetOrInsert(ctx context.Context, name, version string) error

type BrowserID added in v2.7.0

type BrowserID int32

type Campaign added in v2.3.0

type Campaign struct {
	ID     CampaignID `db:"campaign_id" json:"campaign_id"`
	SiteID SiteID     `db:"site_id" json:"site_id"`
	Name   string     `db:"name" json:"name"`
}

func (*Campaign) ByName added in v2.3.0

func (c *Campaign) ByName(ctx context.Context, name string) error

func (*Campaign) Defaults added in v2.3.0

func (c *Campaign) Defaults(ctx context.Context)

func (*Campaign) Insert added in v2.3.0

func (c *Campaign) Insert(ctx context.Context) error

func (*Campaign) Validate added in v2.3.0

func (c *Campaign) Validate() error

type CampaignID added in v2.7.0

type CampaignID int32

type CollectFlag

type CollectFlag struct {
	Label, Help string
	Flag        zint.Bitflag16
}

type EmailReport added in v2.7.0

type EmailReport uint8

type Export

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

	// The hit ID this export was started from.
	StartFromHitID HitID `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 *HitID `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 ExportID) error

func (*Export) CreateCSV added in v2.7.0

func (e *Export) CreateCSV(ctx context.Context, startFrom HitID) (*os.File, error)

CreateCSV creates a new CSV export.

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

func (Export) Exists

func (e Export) Exists() bool

Exists reports whether this export exists on the filesystem.

func (*Export) RunCSV added in v2.7.0

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

Export all data to a CSV file.

type ExportCSVRow added in v2.7.0

type ExportCSVRow struct {
	ID     HitID  `db:"hit_id"`
	SiteID SiteID `db:"site_id"`

	Path  string `db:"path"`
	Title string `db:"title"`
	Event string `db:"event"`

	UserAgent string `db:"ua"`
	Browser   string `db:"browser"`
	System    string `db:"system"`

	Session    zint.Uint128 `db:"session"`
	Bot        string       `db:"bot"`
	Ref        string       `db:"ref"`
	RefScheme  string       `db:"ref_s"`
	Size       string       `db:"size"`
	Location   string       `db:"loc"`
	FirstVisit string       `db:"first"`
	CreatedAt  string       `db:"created_at"`
}

func (ExportCSVRow) Hit added in v2.7.0

func (row ExportCSVRow) Hit(ctx context.Context, siteID SiteID) (Hit, error)

func (*ExportCSVRow) Read added in v2.7.0

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

type ExportCSVRows added in v2.7.0

type ExportCSVRows []ExportCSVRow

func (*ExportCSVRows) Export added in v2.7.0

func (h *ExportCSVRows) Export(ctx context.Context, limit, paginate HitID) (HitID, error)

Export all hits for a site, including bot requests.

type ExportID added in v2.7.0

type ExportID int32

type Exports

type Exports []Export

func (*Exports) List

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

type Floats

type Floats []float64

Floats stores a slice of []float64 as a comma-separated string.

func (Floats) MarshalText

func (l Floats) MarshalText() ([]byte, error)

func (*Floats) Scan

func (l *Floats) Scan(v any) error

func (Floats) String

func (l Floats) String() string

func (*Floats) UnmarshalText

func (l *Floats) UnmarshalText(v []byte) error

func (Floats) Value

func (l Floats) Value() (driver.Value, error)

type GlobalConfig

type GlobalConfig struct {
	Domain         string
	DomainStatic   string
	DomainCount    string
	BasePath       string
	URLStatic      string
	Dev            bool
	GoatcounterCom bool
	Port           string
	EmailFrom      string
	BcryptMinCost  bool
}

func Config

func Config(ctx context.Context) *GlobalConfig

type Group added in v2.7.0

type Group uint8

func (Group) Daily added in v2.7.0

func (g Group) Daily() bool

func (Group) Hourly added in v2.7.0

func (g Group) Hourly() bool

func (Group) String added in v2.7.0

func (g Group) String() string

func (*Group) UnmarshalJSON added in v2.7.0

func (g *Group) UnmarshalJSON(v []byte) error

func (*Group) UnmarshalText added in v2.7.0

func (g *Group) UnmarshalText(v []byte) error

type Hit

type Hit struct {
	ID         HitID        `db:"hit_id" json:"-"`
	Site       SiteID       `db:"site_id" json:"-"`
	PathID     PathID       `db:"path_id" json:"-"`
	RefID      RefID        `db:"ref_id" json:"-"`
	BrowserID  BrowserID    `db:"browser_id" json:"-"`
	SystemID   SystemID     `db:"system_id" json:"-"`
	CampaignID *CampaignID  `db:"campaign" json:"-"`
	Session    zint.Uint128 `db:"session" json:"-"`
	Width      *int16       `db:"width" json:"width"`

	Path  string     `db:"-" json:"p,omitempty"`
	Title string     `db:"-" json:"t,omitempty"`
	Ref   string     `db:"-" json:"r,omitempty"`
	Event zbool.Bool `db:"-" json:"e,omitempty"`
	Size  Floats     `db:"-" json:"s,omitempty"`
	Query string     `db:"-" json:"q,omitempty"`
	Bot   int        `db:"-" json:"b,omitempty"`

	RefScheme       string     `db:"ref_scheme" json:"-"`
	UserAgentHeader string     `db:"-" json:"-"`
	Location        string     `db:"location" json:"-"`
	Language        *string    `db:"language" json:"-"`
	FirstVisit      zbool.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:"-"`

	NoStore bool `db:"-" json:"-"` // Don't store in hits (still store in stats).
	// contains filtered or unexported fields
}

func (*Hit) Defaults

func (h *Hit) Defaults(ctx context.Context, initial bool) error

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

func (*Hit) Ignore

func (h *Hit) Ignore() bool

func (*Hit) Validate

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

Validate the object.

type HitID added in v2.7.0

type HitID int64

type HitList

type HitList struct {
	// Number of visitors for the selected date range.
	Count int `db:"count" json:"count"`

	// Path ID
	PathID PathID `db:"path_id" json:"path_id"`

	// Path name (e.g. /hello.html).
	Path string `db:"path" json:"path"`

	// Is this an event?
	Event zbool.Bool `db:"event" json:"event"`

	// Page title.
	Title string `db:"title" json:"title"`

	// Highest visitors per hour or day (depending on daily being set).
	Max int `json:"max"`

	// Statistics by day and hour.
	Stats []HitListStat `json:"stats"`

	// What kind of referral this is; only set when retrieving referrals {enum: h g c o}.
	//
	//  h   HTTP Referal header.
	//  g   Generated; for example are Google domains (google.com, google.nl,
	//      google.co.nz, etc.) are grouped as the generated referral "Google".
	//  c   Campaign (via query parameter)
	//  o   Other
	RefScheme *string `db:"ref_scheme" json:"ref_scheme,omitempty"`
}

func (*HitList) PathCount

func (h *HitList) PathCount(ctx context.Context, path string, rng ztime.Range) error

PathCount gets the visit count for one path.

func (*HitList) SiteTotalUTC

func (h *HitList) SiteTotalUTC(ctx context.Context, rng ztime.Range) error

SiteTotal gets the total counts for all paths. This always uses UTC.

func (*HitList) Totals

func (h *HitList) Totals(ctx context.Context, rng ztime.Range, pathFilter []PathID, group Group, noEvents bool) (int, error)

Totals gets the data for the "Totals" chart/widget.

type HitListStat

type HitListStat struct {
	Day    string `json:"day"`    // Day these statistics are for {date}.
	Hourly []int  `json:"hourly"` // Visitors per hour.
	Daily  int    `json:"daily"`  // Total visitors for this day.
}

type HitLists

type HitLists []HitList

func (HitLists) Diff added in v2.3.0

func (h HitLists) Diff(ctx context.Context, rng, prev ztime.Range) ([]float64, error)

Diff gets the difference in percentage of all paths in this HitList.

e.g. if called with start=2020-01-20; end=2020-01-2020-01-27, then it will compare this to start=2020-01-12; end=2020-01-19

The return value is in the same order as paths.

func (*HitLists) List

func (h *HitLists) List(
	ctx context.Context, rng ztime.Range, pathFilter, exclude []PathID, limit int, group Group,
) (int, bool, error)

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

func (*HitLists) ListPathsLike

func (h *HitLists) ListPathsLike(ctx context.Context, search string, matchTitle, matchCase bool) error

ListPathsLike lists all paths matching the like pattern.

type HitStat

type HitStat struct {
	// ID for selecting more details; not present in the detail view.
	ID    string `db:"id" json:"id,omitempty"`
	Name  string `db:"name" json:"name"`   // Display name.
	Count int    `db:"count" json:"count"` // Number of visitors.

	// What kind of referral this is; only set when retrieving referrals {enum: h g c o}.
	//
	//  h   HTTP Referal header.
	//  g   Generated; for example are Google domains (google.com, google.nl,
	//      google.co.nz, etc.) are grouped as the generated referral "Google".
	//  c   Campaign (via query parameter)
	//  o   Other
	RefScheme *string `db:"ref_scheme" json:"ref_scheme,omitempty"`
}

type HitStats

type HitStats struct {
	More  bool      `json:"more"`
	Stats []HitStat `json:"stats"`
}

func (*HitStats) ListBrowser

func (h *HitStats) ListBrowser(ctx context.Context, browser string, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListBrowser lists all the versions for one browser.

func (*HitStats) ListBrowsers

func (h *HitStats) ListBrowsers(ctx context.Context, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListBrowsers lists all browser statistics for the given time period.

func (*HitStats) ListCampaign added in v2.3.0

func (h *HitStats) ListCampaign(ctx context.Context, campaign CampaignID, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListCampaign lists all statistics for a campaign.

func (*HitStats) ListCampaigns added in v2.3.0

func (h *HitStats) ListCampaigns(ctx context.Context, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListCampaigns lists all campaigns statistics for the given time period.

func (*HitStats) ListLanguages added in v2.2.0

func (h *HitStats) ListLanguages(ctx context.Context, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListLanguages lists all language statistics for the given time period.

func (*HitStats) ListLocation

func (h *HitStats) ListLocation(ctx context.Context, country string, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListLocation lists all divisions for a location

func (*HitStats) ListLocations

func (h *HitStats) ListLocations(ctx context.Context, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListLocations lists all location statistics for the given time period.

func (*HitStats) ListRefsByPathID added in v2.4.0

func (h *HitStats) ListRefsByPathID(ctx context.Context, pathID PathID, rng ztime.Range, limit, offset int) error

ListRefsByPath lists all references for a pathID.

func (*HitStats) ListSize

func (h *HitStats) ListSize(ctx context.Context, id string, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListSize lists all sizes for one grouping.

func (*HitStats) ListSizes

func (h *HitStats) ListSizes(ctx context.Context, rng ztime.Range, pathFilter []PathID) error

ListSizes lists all device sizes.

func (*HitStats) ListSystem

func (h *HitStats) ListSystem(ctx context.Context, system string, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListSystem lists all the versions for one system.

func (*HitStats) ListSystems

func (h *HitStats) ListSystems(ctx context.Context, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListSystems lists OS statistics for the given time period.

func (*HitStats) ListTopRef

func (h *HitStats) ListTopRef(ctx context.Context, ref string, rng ztime.Range, pathFilter []PathID, limit, offset int) error

ListTopRef lists all paths by referrer.

func (*HitStats) ListTopRefs

func (h *HitStats) ListTopRefs(ctx context.Context, rng ztime.Range, pathFilter []PathID, limit, 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 Hits

type Hits []Hit

func (*Hits) Purge

func (h *Hits) Purge(ctx context.Context, pathIDs []PathID) error

Purge the given paths.

func (*Hits) TestList

func (h *Hits) TestList(ctx context.Context, siteOnly bool) error

TestList lists all hits, for all sites, with browser_id, system_id, and paths set.

This is intended for tests.

type Ints

type Ints []int64

Ints stores a slice of []int64 as a comma-separated string.

func (Ints) MarshalText

func (l Ints) MarshalText() ([]byte, error)

func (*Ints) Scan

func (l *Ints) Scan(v any) error

func (Ints) String

func (l Ints) String() string

func (*Ints) UnmarshalText

func (l *Ints) UnmarshalText(v []byte) error

func (Ints) Value

func (l Ints) Value() (driver.Value, error)

type Location

type Location struct {
	ID LocationID `db:"location_id"`

	Country     string `db:"country"`
	Region      string `db:"region"`
	CountryName string `db:"country_name"`
	RegionName  string `db:"region_name"`

	// TODO: send patch to staticcheck to deal with this better. This shouldn't
	// errror since "ISO" is an initialism.
	ISO3166_2 string `db:"iso_3166_2"` //lint:ignore ST1003 staticcheck bug
}

func (*Location) ByCode

func (l *Location) ByCode(ctx context.Context, code string) error

ByCode gets a location by ISO-3166-2 code; e.g. "US" or "US-TX".

func (*Location) Lookup

func (l *Location) Lookup(ctx context.Context, ip string) error

Lookup a location by IPv4 or IPv6 address.

This will insert a row in the locations table if one doesn't exist yet.

func (Location) LookupIP

func (l Location) LookupIP(ctx context.Context, ip string) string

LookupIP is a shorthand for Lookup(); returns id 1 on errors ("unknown").

type LocationID added in v2.7.0

type LocationID int32

type Locations

type Locations []Location

func (*Locations) ListCountries

func (l *Locations) ListCountries(ctx context.Context) error

ListCountries lists all counties. The region code/name will always be blank.

type Path

type Path struct {
	ID    PathID     `db:"path_id" json:"id"` // Path ID
	Site  SiteID     `db:"site_id" json:"-"`
	Path  string     `db:"path" json:"path"`   // Path name
	Title string     `db:"title" json:"title"` // Page title
	Event zbool.Bool `db:"event" json:"event"` // Is this an event?
}

func (*Path) ByID added in v2.4.0

func (p *Path) ByID(ctx context.Context, id PathID) error

func (*Path) ByPath added in v2.7.0

func (p *Path) ByPath(ctx context.Context, path string) error

func (*Path) Defaults

func (p *Path) Defaults(ctx context.Context)

func (*Path) GetOrInsert

func (p *Path) GetOrInsert(ctx context.Context) error

func (Path) Merge added in v2.6.0

func (p Path) Merge(ctx context.Context, paths Paths) error

Merge the given paths in to this one.

func (*Path) Validate

func (p *Path) Validate(ctx context.Context) error

type PathID added in v2.7.0

type PathID int32

func FindPathIDs added in v2.7.0

func FindPathIDs(ctx context.Context, list []string) ([]PathID, error)

FindPathsIDs finds path IDs by exact matches on the name.

func PathFilter

func PathFilter(ctx context.Context, filter string) ([]PathID, error)

PathFilter returns a list of IDs matching the path name.

If matchTitle is true it will match the title as well.

type Paths added in v2.4.0

type Paths []Path

func (*Paths) List added in v2.4.0

func (p *Paths) List(ctx context.Context, siteID SiteID, after PathID, limit int) (bool, error)

List all paths for a site.

type PermissionFlag

type PermissionFlag struct {
	Label, Help string
	Flag        zint.Bitflag64
}

type Ref added in v2.5.0

type Ref struct {
	ID        RefID  `db:"ref_id"`
	Ref       string `db:"ref"`
	RefScheme string `db:"ref_scheme"`
}

func (*Ref) Defaults added in v2.5.0

func (r *Ref) Defaults(ctx context.Context)

func (*Ref) GetOrInsert added in v2.5.0

func (r *Ref) GetOrInsert(ctx context.Context) error

func (*Ref) Validate added in v2.5.0

func (r *Ref) Validate(ctx context.Context) error

type RefID added in v2.7.0

type RefID int32

type Site

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

	// Custom domain, e.g. "stats.example.com".
	//
	// When self-hosting this is the domain/vhost your site is accessible at.
	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 (e.g. "arp242", which makes arp242.goatcounter.com). Only
	// used for goatcounter.com and not when self-hosting.
	Code string `db:"code" json:"code"`

	// Site domain for linking (www.arp242.net). Note this can be a full URL and
	// is a bit misnamed.
	LinkDomain string `db:"link_domain" json:"link_domain"`

	Settings     SiteSettings `db:"settings" json:"setttings"`
	UserDefaults UserSettings `db:"user_defaults" json:"user_defaults"`

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

	// {omitdoc}
	Notes string `db:"notes" json:"-"`

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

func GetAccount

func GetAccount(ctx context.Context) (*Site, error)

GetAccount gets this site's "account site" on which the users, etc. are stored.

func GetSite

func GetSite(ctx context.Context) *Site

GetSite gets the current site.

func MustGetAccount

func MustGetAccount(ctx context.Context) *Site

func MustGetSite

func MustGetSite(ctx context.Context) *Site

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

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 SiteID) error

ByID gets a site by ID.

func (*Site) ByIDState

func (s *Site) ByIDState(ctx context.Context, id SiteID, state string) error

ByIDState gets a site by ID and state. This may return deleted sites.

func (Site) ClearCache

func (s Site) ClearCache(ctx context.Context, full bool)

ClearCache clears the cache for this site.

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, deleteChildren bool) error

Delete a site and all child sites.

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(ctx context.Context) string

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

func (Site) Domain

func (s Site) Domain(ctx context.Context) string

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

func (Site) Exists

func (s Site) Exists(ctx context.Context) (SiteID, error)

Exists checks if this site already exists, based on either the Cname or Code field.

func (*Site) Find

func (s *Site) Find(ctx context.Context, ident string) error

Find a site: by ID if ident is a number, or by host if it's not.

func (Site) IDOrParent

func (s Site) IDOrParent() SiteID

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) LinkDomainURL

func (s Site) LinkDomainURL(withProto bool, paths ...string) string

LinkDomainURL creates a valid url to the configured LinkDomain.

func (*Site) ListSubs

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

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

func (Site) SchemelessURL added in v2.6.0

func (s Site) SchemelessURL(ctx context.Context) string

URL to this site, without the scheme.

func (Site) URL

func (s Site) URL(ctx context.Context) string

URL to this site.

func (Site) Undelete

func (s Site) Undelete(ctx context.Context, id SiteID) error

func (*Site) Update

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

Update existing site. Sets settings, cname, link_domain.

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) UpdateFirstHitAt

func (s *Site) UpdateFirstHitAt(ctx context.Context, f time.Time) error

func (*Site) UpdateParent

func (s *Site) UpdateParent(ctx context.Context, newParent *SiteID) error

func (*Site) UpdateReceivedData

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

func (*Site) Validate

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

Validate the object.

type SiteID added in v2.7.0

type SiteID int32

type SiteIDs added in v2.7.0

type SiteIDs []SiteID

func (SiteIDs) All added in v2.7.0

func (s SiteIDs) All() bool

func (SiteIDs) Has added in v2.7.0

func (s SiteIDs) Has(siteID SiteID) bool

func (SiteIDs) List added in v2.7.0

func (s SiteIDs) List(ctx context.Context) Sites

func (*SiteIDs) Scan added in v2.7.0

func (s *SiteIDs) Scan(v any) error

func (*SiteIDs) UnmarshalJSON added in v2.7.0

func (s *SiteIDs) UnmarshalJSON(data []byte) error

func (*SiteIDs) UnmarshalText added in v2.7.0

func (s *SiteIDs) UnmarshalText(v []byte) error

func (s SiteIDs) MarshalText() ([]byte, error) { return json.Marshal(s) }

func (SiteIDs) Value added in v2.7.0

func (s SiteIDs) Value() (driver.Value, error)

type SiteSettings

type SiteSettings struct {
	Public         string         `json:"public"`
	Secret         string         `json:"secret"`
	AllowCounter   bool           `json:"allow_counter"`
	AllowBosmang   bool           `json:"allow_bosmang"`
	DataRetention  int            `json:"data_retention"`
	Campaigns      Strings        `json:"-"`
	IgnoreIPs      Strings        `json:"ignore_ips"`
	Collect        zint.Bitflag16 `json:"collect"`
	CollectRegions Strings        `json:"collect_regions"`
	AllowEmbed     Strings        `json:"allow_embed"`
}

SiteSettings contains all the user-configurable settings for a site, with the exception of the domain settings.

This is stored as JSON in the database.

func (SiteSettings) CanView

func (ss SiteSettings) CanView(token string) bool

func (SiteSettings) CollectFlags

func (ss SiteSettings) CollectFlags(ctx context.Context) []CollectFlag

CollectFlags returns a list of all flags we know for the Collect settings.

func (*SiteSettings) Defaults

func (ss *SiteSettings) Defaults(ctx context.Context)

func (SiteSettings) IsPublic

func (ss SiteSettings) IsPublic() bool

func (*SiteSettings) Scan

func (ss *SiteSettings) Scan(v any) error

func (SiteSettings) String

func (ss SiteSettings) String() string

func (*SiteSettings) Validate

func (ss *SiteSettings) Validate(ctx context.Context) error

func (SiteSettings) Value

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

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) Delete

func (s *Sites) Delete(ctx context.Context, deleteChildren bool) error

Delete all sites in this selection.

func (*Sites) Find

func (s *Sites) Find(ctx context.Context, ident []string) error

Find sites: by ID if ident is a number, or by host if it's not.

func (*Sites) ForAccount added in v2.7.0

func (s *Sites) ForAccount(ctx context.Context, accountID SiteID) error

ForAccount gets all sites associated with an account.

func (*Sites) ForThisAccount

func (s *Sites) ForThisAccount(ctx context.Context, excludeCurrent bool) error

ForThisAccount gets all sites associated with this account.

func (*Sites) IDs

func (s *Sites) IDs() []int32

IDs gets a list of all IDs for these sites.

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 Strings

type Strings []string

Strings stores a slice of []string as a comma-separated string.

func (Strings) MarshalText

func (l Strings) MarshalText() ([]byte, error)

func (*Strings) Scan

func (l *Strings) Scan(v any) error

func (Strings) String

func (l Strings) String() string

func (*Strings) UnmarshalText

func (l *Strings) UnmarshalText(v []byte) error

func (Strings) Value

func (l Strings) Value() (driver.Value, error)

type System

type System struct {
	ID      SystemID `db:"system_id"`
	Name    string   `db:"name"`
	Version string   `db:"version"`
}

func (*System) GetOrInsert

func (s *System) GetOrInsert(ctx context.Context, name, version string) error

type SystemID added in v2.7.0

type SystemID int32

type TotalCount

type TotalCount struct {
	// Total number of visitors (including events).
	Total int `db:"total" json:"total"`
	// Total number of visitors for events.
	TotalEvents int `db:"total_events" json:"total_events"`
	// Total number of visitors in UTC. The browser, system, etc, stats are
	// always in UTC.
	TotalUTC int `db:"total_utc" json:"total_utc"`
}

func GetTotalCount

func GetTotalCount(ctx context.Context, rng ztime.Range, pathFilter []PathID, noEvents bool) (TotalCount, error)

GetTotalCount gets the total number of pageviews for the selected timeview in the timezone the user configured.

This also gets the total number of pageviews for the selected time period in UTC. This is needed since the _stats tables are per day, rather than per-hour, so we need to use the correct totals to make sure the percentage calculations are accurate.

type TplEmailAddUser

type TplEmailAddUser struct {
	Context context.Context
	Site    Site
	NewUser User
	AddedBy string
}

func (TplEmailAddUser) Render

func (t TplEmailAddUser) Render() ([]byte, error)

type TplEmailExportDone

type TplEmailExportDone struct {
	Context context.Context
	Site    Site
	User    User
	Export  Export
}

func (TplEmailExportDone) Render

func (t TplEmailExportDone) Render() ([]byte, error)

type TplEmailForgotSite

type TplEmailForgotSite struct {
	Context context.Context
	Sites   Sites
	Email   string
}

func (TplEmailForgotSite) Render

func (t TplEmailForgotSite) Render() ([]byte, error)

type TplEmailImportDone

type TplEmailImportDone struct {
	Context context.Context
	Site    Site
	Rows    int
	Errors  *errors.Group
}

func (TplEmailImportDone) Render

func (t TplEmailImportDone) Render() ([]byte, error)

type TplEmailImportError

type TplEmailImportError struct {
	Context context.Context
	Error   error
}

func (TplEmailImportError) Render

func (t TplEmailImportError) Render() ([]byte, error)

type TplEmailPasswordReset

type TplEmailPasswordReset struct {
	Context context.Context
	Site    Site
	User    User
}

func (TplEmailPasswordReset) Render

func (t TplEmailPasswordReset) Render() ([]byte, error)

type TplEmailVerify

type TplEmailVerify struct {
	Context context.Context
	Site    Site
	User    User
}

func (TplEmailVerify) Render

func (t TplEmailVerify) Render() ([]byte, error)

type TplEmailWelcome

type TplEmailWelcome struct {
	Context     context.Context
	Site        Site
	User        User
	CountDomain string
}

func (TplEmailWelcome) Render

func (t TplEmailWelcome) Render() ([]byte, error)

type Translation added in v2.7.0

type Translation msgfile.File

func (*Translation) ByFilename added in v2.7.0

func (t *Translation) ByFilename(ctx context.Context, filename string) error

func (*Translation) Store added in v2.7.0

func (t *Translation) Store(ctx context.Context, filename string) error

type Translations

type Translations []Translation

func (*Translations) List added in v2.7.0

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

type User

type User struct {
	ID   UserID `db:"user_id" json:"id,readonly"`
	Site SiteID `db:"site_id" json:"site,readonly"`

	Email         string       `db:"email" json:"email"`
	EmailVerified zbool.Bool   `db:"email_verified" json:"email_verified,readonly"`
	Password      []byte       `db:"password" json:"-"`
	TOTPEnabled   zbool.Bool   `db:"totp_enabled" json:"totp_enabled,readonly"`
	TOTPSecret    []byte       `db:"totp_secret" json:"-"`
	Access        UserAccesses `db:"access" json:"access,readonly"`
	LoginAt       *time.Time   `db:"login_at" json:"login_at,readonly"`
	OpenAt        *time.Time   `db:"open_at" json:"open_at,readonly"`
	ResetAt       *time.Time   `db:"reset_at" json:"reset_at,readonly"`
	LoginRequest  *string      `db:"login_request" json:"-"`
	LoginToken    *string      `db:"login_token" json:"-"`
	Token         *string      `db:"csrf_token" json:"-"`
	EmailToken    *string      `db:"email_token" json:"-"`
	Settings      UserSettings `db:"settings" json:"settings"`

	// Keep track when the last email report was sent, so we don't double-send them.
	LastReportAt time.Time `db:"last_report_at" json:"last_report_at"`

	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 MustGetUser

func MustGetUser(ctx context.Context) *User

MustGetUser behaves as GetUser(), panicking if this fails.

func (User) AccessAdmin

func (u User) AccessAdmin() bool

func (User) AccessSettings

func (u User) AccessSettings() bool

func (User) AccessSuperuser added in v2.2.0

func (u User) AccessSuperuser() bool

func (*User) ByEmail

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

ByEmail gets a user by email address for the current account.

func (*User) ByEmailToken

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

ByEmailToken gets a user by email verification token.

func (*User) ByID

func (u *User) ByID(ctx context.Context, id UserID) error

ByID gets a user by id.

func (*User) ByResetToken

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

ByResetToken gets a user by login request key.

This can be used in two contexts: the user requested a password reset, or the user was invited to create a new account.

func (*User) BySiteAndEmail added in v2.7.0

func (u *User) BySiteAndEmail(ctx context.Context, siteID SiteID, email string) error

ByEmail gets a user by email address for the current account.

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) CSRFToken

func (u *User) CSRFToken() string

CSRFToken gets the CSRF 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) Delete

func (u *User) Delete(ctx context.Context, lastAdmin bool) error

Delete this user.

func (*User) DisableTOTP

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

func (User) EmailReportRange added in v2.2.0

func (u User) EmailReportRange(ctx context.Context) ztime.Range

EmailReportRange gets the time range of the next report to send out.

user.LastReportAt is set when a report is sent; to get the range for the new report we take LastReportAt, go to the start and end of the period, and if the endDate > now then send out a new report and set LastReportAt.

The cronjob will send the report if the current date is after the end date.

func (User) EmailShort added in v2.4.0

func (u User) EmailShort() string

func (*User) EnableTOTP

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

func (*User) Find

func (u *User) Find(ctx context.Context, ident string) error

Find a user: by ID if ident is a number, or by email if it's not.

func (User) HasAccess

func (u User) HasAccess(check UserAccess) bool

HasAccess checks if this user has access to this site for the permission.

func (*User) Insert

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

Insert a new row.

func (*User) InviteToken

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

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) Update

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

Update this user's name, email, settings, and access.

func (*User) UpdateOpenAt added in v2.5.0

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

func (*User) UpdatePassword

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

UpdatePassword updates this user's password.

func (*User) UpdateSite

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

UpdateSite updates this user's siteID (i.e. moves it to another site).

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

type UserAccess

type UserAccess string
const (
	AccessReadOnly  UserAccess = "r"
	AccessSettings  UserAccess = "s"
	AccessAdmin     UserAccess = "a"
	AccessSuperuser UserAccess = "*"
)

func (UserAccess) String

func (u UserAccess) String() string

TODO: this is not translated.

type UserAccesses

type UserAccesses map[string]UserAccess

func (*UserAccesses) Scan

func (u *UserAccesses) Scan(v any) error

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

func (UserAccesses) Value

func (u UserAccesses) Value() (driver.Value, error)

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

type UserAgent

type UserAgent struct {
	UserAgent string
	Isbot     uint8
	BrowserID BrowserID
	SystemID  SystemID
}

func (*UserAgent) GetOrInsert

func (p *UserAgent) GetOrInsert(ctx context.Context) error

type UserID added in v2.7.0

type UserID int32

type UserSettings

type UserSettings struct {
	TwentyFourHours       bool        `json:"twenty_four_hours"`
	SundayStartsWeek      bool        `json:"sunday_starts_week"`
	Language              string      `json:"language"`
	DateFormat            string      `json:"date_format"`
	NumberFormat          rune        `json:"number_format"`
	Timezone              *tz.Zone    `json:"timezone"`
	Widgets               Widgets     `json:"widgets"`
	Views                 Views       `json:"views"`
	EmailReports          EmailReport `json:"email_reports"`
	FewerNumbers          bool        `json:"fewer_numbers"`
	FewerNumbersLockUntil time.Time   `json:"fewer_numbers_lock_until"`
	Theme                 string      `json:"theme"`
	Datepicker            bool        `json:"datepicker"`
}

UserSettings are all user preferences.

func (*UserSettings) Defaults

func (ss *UserSettings) Defaults(ctx context.Context)

func (*UserSettings) Scan

func (ss *UserSettings) Scan(v any) error

func (UserSettings) String

func (ss UserSettings) String() string

func (*UserSettings) Validate

func (ss *UserSettings) Validate(ctx context.Context) error

func (UserSettings) Value

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

type Users

type Users []User

func (Users) Admins

func (u Users) Admins() Users

Admins returns just the admins and superusers in this user list.

func (*Users) ByEmail

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

ByEmail gets all users with this email address.

func (*Users) BySite

func (u *Users) BySite(ctx context.Context, siteID SiteID) error

BySite gets all users for a site.

func (*Users) Delete

func (u *Users) Delete(ctx context.Context, force bool) error

Delete all users in this selection.

func (*Users) Find

func (u *Users) Find(ctx context.Context, ident []string) error

Find users: by ID if ident is a number, or by email if it's not.

func (*Users) IDs

func (u *Users) IDs() []int32

IDs gets a list of all IDs for these users.

func (*Users) List

func (u *Users) List(ctx context.Context, siteID SiteID) error

List all users for a site.

type View

type View struct {
	Name   string `json:"name"`
	Filter string `json:"filter"`
	Group  Group  `json:"group"`
	Period string `json:"period"` // "week", "week-cur", or n days: "8"
}

Views for the dashboard; these settings apply to all widget and are configurable in the yellow box at the top.

type Views

type Views []View

func (Views) Get

func (v Views) Get(name string) (View, int)

Get a view for this site by name and returns the view and index. Returns -1 if this view doesn't exist.

type Widget

type Widget map[string]any

Widgets is a list of widgets to be printed, in order.

func NewWidget

func NewWidget(name string) Widget

func (Widget) GetSetting

func (w Widget) GetSetting(ctx context.Context, n string) any

func (Widget) GetSettings

func (w Widget) GetSettings(ctx context.Context) WidgetSettings

GetSettings gets all setting for this widget.

func (Widget) Name

func (w Widget) Name() string

Name gets this widget's name.

func (Widget) SetSetting

func (w Widget) SetSetting(ctx context.Context, widget, setting, value string) error

SetSettings set the setting "setting" for widget "widget" to "value".

The value is converted to the correct type for this setting.

type WidgetSetting

type WidgetSetting struct {
	Type        string
	Hidden      bool
	Label       string
	Help        string
	Options     [][2]string
	OptionsFunc func(context.Context) [][2]string
	Validate    func(*zvalidate.Validator, any)
	Value       any
	Attr        template.HTMLAttr
}

type WidgetSettings

type WidgetSettings map[string]WidgetSetting

func (WidgetSettings) Display

func (s WidgetSettings) Display(ctx context.Context, wname string) string

Display all values that are different from the default.

func (WidgetSettings) HasSettings

func (s WidgetSettings) HasSettings() bool

HasSettings reports if there are any non-hidden settings.

func (*WidgetSettings) Set

func (s *WidgetSettings) Set(k string, v any)

type Widgets

type Widgets []Widget

func (Widgets) ByID added in v2.2.0

func (w Widgets) ByID(id int) Widget

ByID gets this widget by the position/ID.

func (Widgets) Get

func (w Widgets) Get(name string) Widgets

Get all widget from the list by name.

func (*Widgets) UnmarshalJSON added in v2.2.3

func (w *Widgets) UnmarshalJSON(d []byte) error

This exists as a work-around because a migration set this column wrong >_<

https://github.com/arp242/goatcounter/issues/569#issuecomment-1042013488

Directories

Path Synopsis
cmd
goatcounter command
Package cron schedules jobs.
Package cron schedules jobs.
db
Package gctest contains testing helpers.
Package gctest contains testing helpers.
pkg
bgrun
Package bgrun runs jobs in the background.
Package bgrun runs jobs in the background.
db2
geo
geo/geoip2
Package geoip2 provides an easy-to-use API for the MaxMind GeoIP2 and GeoLite2 databases; this package does not support GeoIP Legacy databases.
Package geoip2 provides an easy-to-use API for the MaxMind GeoIP2 and GeoLite2 databases; this package does not support GeoIP Legacy databases.
geo/maxminddb
Package maxminddb provides a reader for the MaxMind DB file format.
Package maxminddb provides a reader for the MaxMind DB file format.
log
Package log wraps slog.
Package log wraps slog.
metrics
Package metrics collects performance metrics.
Package metrics collects performance metrics.

Jump to

Keyboard shortcuts

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