goatcounter

package module
v2.5.0 Latest Latest
Warning

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

Go to latest
Published: Dec 14, 2023 License: EUPL-1.2, ISC, MIT, + 1 more Imports: 71 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 if you're using goatcounter.com to pay for the server costs.

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. Integrate in your middleware; send data to GoatCounter by calling the API from your backend server middleware. Detailed documentation for this is available at https://www.goatcounter.com/api#backend-integration

  3. Parse logfiles. GoatCounter can parse logfiles from nginx, Apache, CloudFront, or any other HTTP middleware or proxy. See goatcounter help import for detailed documentation on this.

Running your own

Note this README is for the latest master and may be inaccurate for the latest released version; use the release-2.5 branch for the 2.5 README.

The release page has binaries for Linux amd64, arm, and arm64. These are statically compiled, contain everything you need, and should work in pretty much any Linux environment. The only other thing you need is somewhere to store a SQLite database file or a PostgreSQL connection.

GoatCounter should run on any platform supported by Go, but there are no binaries for them (yet) as cross-compiling SQLite is somewhat complex. You'll have to build from source if you want to run it on e.g. FreeBSD or macOS.

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

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

You can install from source to $GOBIN (go env GOBIN) with:

% git clone --branch=release-2.5 https://github.com/arp242/goatcounter.git
% cd goatcounter
% go build -ldflags="-X zgo.at/goatcounter/v2.Version=$(git log -n1 --format='%h_%cI')" ./cmd/goatcounter

Which will produce a goatcounter binary in the current directory.

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.

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

To build a fully statically linked binary:

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

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 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 \
    -ldflags="-X zgo.at/goatcounter.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.

Running

You can start a server with:

% goatcounter serve

The default is to use an 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.

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

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 db create site command:

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

This will ask for a password for your new account; 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.

Updating

You may need to run the database migrations when updating. Use goatcounter serve -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 pending to get a list of pending migrations, or goatcounter migrate list to show all migrations.

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.

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, 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 (
	CollectNothing        zint.Bitflag16 = 1 << iota
	CollectReferrer                      // 2
	CollectUserAgent                     // 4
	CollectScreenSize                    // 8
	CollectLocation                      // 16
	CollectLocationRegion                // 32
	CollectLanguage                      // 64
	CollectSession                       // 128
)

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 = iota // Email once after 2 weeks; for new sites.
	EmailReportDaily
	EmailReportWeekly
	EmailReportBiWeekly
	EmailReportMonthly
)

UserSettings.EmailReport values.

View Source
const ExportVersion = "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      = ztype.Ptr("h")
	RefSchemeOther     = ztype.Ptr("o")
	RefSchemeGenerated = ztype.Ptr("g")
	RefSchemeCampaign  = ztype.Ptr("c")
)

ref_scheme column

DB contains all files in db/*

View Source
var GeoDB []byte

GeoDB contains the GeoIP countries database.

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 Static embed.FS

Static contains all the static files to serve.

View Source
var Templates embed.FS

Templates contains all templates.

View Source
var Version = "dev"

Functions

func ChunkStat

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

Compress all the data in to 12 chunks.

func CopyContextValues

func CopyContextValues(ctx context.Context) context.Context

CopyContextValues 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 DefaultLocale added in v2.5.0

func DefaultLocale() *z18n.Locale

func GetBundle

func GetBundle(ctx context.Context) *z18n.Bundle

func HorizontalChart

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

func Import

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

Import data from an 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 InitGeoDB

func InitGeoDB(path string)

InitGeoDB sets up the geoDB database located at the given path.

The database can be the "Countries" or "Cities" version.

It will use the embeded "Countries" database if path is an empty string.

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(db zdb.DB) context.Context

NewContext creates a new context with all values set.

func NewValidate

func NewValidate(ctx context.Context) zvalidate.Validator

func PathFilter

func PathFilter(ctx context.Context, filter string, matchTitle bool) ([]int64, error)

PathFilter returns a list of IDs matching the path name.

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

func Translations

func Translations(ctx context.Context) fs.FS

Translations gets the translation messages; a user can have a local override, so we need to apply that per-user.

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     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 zint.Bitflag64 `db:"permissions" json:"permissions"`

	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 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) 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 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() []int64

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

func (*APITokens) List

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

type BosmangStat

type BosmangStat struct {
	ID        int64     `db:"site_id"`
	Codes     string    `db:"codes"`
	Email     string    `db:"email"`
	CreatedAt time.Time `db:"created_at"`
	LastMonth int       `db:"last_month"`
	Total     int       `db:"total"`
	Avg       int       `db:"avg"`
}

type BosmangStats

type BosmangStats []BosmangStat

func (*BosmangStats) List

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

List stats for all sites, for all time.

type Browser

type Browser struct {
	ID      int64  `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 Campaign added in v2.3.0

type Campaign struct {
	ID     int64  `db:"campaign_id" json:"campaign_id"`
	SiteID int64  `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 CollectFlag

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

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

func (e Export) Exists() bool

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 {
	ID     int64 `db:"hit_id"`
	SiteID int64 `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 (ExportRow) Hit

func (row ExportRow) Hit(ctx context.Context, siteID int64) (Hit, error)

func (*ExportRow) Read

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

type ExportRows

type ExportRows []ExportRow

func (*ExportRows) Export

func (h *ExportRows) Export(ctx context.Context, limit, paginate int64) (int64, error)

Export all hits for a site, including bot requests.

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
	URLStatic      string
	Dev            bool
	GoatcounterCom bool
	Port           string
	Websocket      bool
	EmailFrom      string
	BcryptMinCost  bool
}

func Config

func Config(ctx context.Context) *GlobalConfig

type Hit

type Hit struct {
	ID         int64        `db:"hit_id" json:"-"`
	Site       int64        `db:"site_id" json:"-"`
	PathID     int64        `db:"path_id" json:"-"`
	RefID      int64        `db:"ref_id" json:"-"`
	SizeID     *int64       `db:"size_id" json:"-"`
	BrowserID  int64        `db:"browser_id" json:"-"`
	SystemID   int64        `db:"system_id" json:"-"`
	CampaignID *int64       `db:"campaign" json:"-"`
	Session    zint.Uint128 `db:"session" json:"-"`

	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:"bot" 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:"-"`
	// 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 HitList

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

	// Path ID
	PathID int64 `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 []int64, daily, 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 []int64, limit int, daily bool,
) (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 []int64, 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 []int64, 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 int64, rng ztime.Range, pathFilter []int64, 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 []int64, 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 []int64, 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 []int64, 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 []int64, 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 int64, 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 []int64, 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 []int64) error

ListSizes lists all device sizes.

func (*HitStats) ListSystem

func (h *HitStats) ListSystem(ctx context.Context, system string, rng ztime.Range, pathFilter []int64, 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 []int64, 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 []int64, limit, offset int) error

ListTopRef lists all paths by referrer.

func (*HitStats) ListTopRefs

func (h *HitStats) ListTopRefs(ctx context.Context, rng ztime.Range, pathFilter []int64, 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) Merge added in v2.4.0

func (h *Hits) Merge(ctx context.Context, dst int64, pathIDs []int64) error

Merge the given paths.

func (*Hits) Purge

func (h *Hits) Purge(ctx context.Context, pathIDs []int64) 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 int64 `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 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 OverrideTranslation

type OverrideTranslation struct {
	Name    string       `json:"name"`
	Updated string       `json:"updated"`
	File    msgfile.File `json:"file"`
}

type OverrideTranslations

type OverrideTranslations []OverrideTranslation

func (*OverrideTranslations) Get

func (o *OverrideTranslations) Get(ctx context.Context, insert bool) error

func (*OverrideTranslations) Insert

func (o *OverrideTranslations) Insert(ctx context.Context) error

Insert new.

func (OverrideTranslations) Key

func (*OverrideTranslations) Update

func (o *OverrideTranslations) Update(ctx context.Context) error

type Path

type Path struct {
	ID    int64      `db:"path_id" json:"id"` // Path ID
	Site  int64      `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 int64) error

func (*Path) Defaults

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

func (*Path) GetOrInsert

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

func (*Path) Validate

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

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, after int64, 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        int64   `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 Site

type Site struct {
	ID     int64  `db:"site_id" json:"id,readonly"`
	Parent *int64 `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).
	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 int64) error

ByID gets a site by ID.

func (*Site) ByIDState

func (s *Site) ByIDState(ctx context.Context, id int64, 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) (int64, 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() 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) 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) URL

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

URL to this site.

func (Site) Undelete

func (s Site) Undelete(ctx context.Context, id int64) 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 *int64) 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 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) 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() []int64

IDs gets a list of all IDs for these sites.

func (*Sites) ListIDs added in v2.2.0

func (s *Sites) ListIDs(ctx context.Context, ids ...int64) error

ListIDs lists all sites with the given IDs.

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 Size added in v2.5.0

type Size struct {
	ID     int64   `db:"size_id"`
	Width  int16   `db:"width"`
	Height int16   `db:"height"`
	Scale  float64 `db:"scale"`
	Size   string  `db:"size"`
}

func (*Size) Defaults added in v2.5.0

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

func (*Size) GetOrInsert added in v2.5.0

func (s *Size) GetOrInsert(ctx context.Context, size Floats) error

func (Size) String added in v2.5.0

func (s Size) String() string

func (*Size) Validate added in v2.5.0

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

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      int64  `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 TotalCount

type TotalCount struct {
	Total       int `db:"total" json:"total"`               // Total number of visitors (including events).
	TotalEvents int `db:"total_events" json:"total_events"` // Total number of visitors for 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 []int64, 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 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:"user_id" json:"id,readonly"`
	Site int64 `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:"-"`
	SeenUpdatesAt time.Time    `db:"seen_updates_at" 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.

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 int64) 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) 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() 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) 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, 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 int64
	SystemID  int64
}

func (*UserAgent) GetOrInsert

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

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          zint.Int  `json:"email_reports"`
	FewerNumbers          bool      `json:"fewer_numbers"`
	FewerNumbersLockUntil time.Time `json:"fewer_numbers_lock_until"`
	Theme                 string    `json:"theme"`
}

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 int64) 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() []int64

IDs gets a list of all IDs for these users.

func (*Users) List

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

List all users for a site.

type View

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

type Views

type Views []View

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

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

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
}

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

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

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
Package cron schedules jobs.
Package cron schedules jobs.
db
Package gctest contains testing helpers.
Package gctest contains testing helpers.
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