From c08dc0937b780cdc8e53e71134fb5e5b70e1c3ee Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Tue, 22 Jun 2021 13:06:44 +0200 Subject: [PATCH] fix up stats, allow more instance settings --- internal/api/model/admin.go | 23 +++++++++++ internal/api/model/instance.go | 2 +- internal/db/db.go | 8 ++++ internal/db/pg/instancestats.go | 52 ++++++++++++++++++++++++ internal/typeutils/internaltofrontend.go | 21 ++++++++++ web/template/index.tmpl | 9 ++-- 6 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 internal/db/pg/instancestats.go diff --git a/internal/api/model/admin.go b/internal/api/model/admin.go index 036218f..c8d4c0c 100644 --- a/internal/api/model/admin.go +++ b/internal/api/model/admin.go @@ -18,6 +18,8 @@ package model +import "mime/multipart" + // AdminAccountInfo represents the *admin* view of an account's details. See here: https://docs.joinmastodon.org/entities/admin-account/ type AdminAccountInfo struct { // The ID of the account in the database. @@ -79,3 +81,24 @@ type AdminReportInfo struct { // Statuses attached to the report, for context. Statuses []Status `json:"statuses"` } + +// AdminSiteSettings is the form to be parsed on a POST or PATCH to /admin/settings +type AdminSiteSettings struct { + FormAdminSettings FormAdminSettings `form:"form_admin_settings" json:"form_admin_settings" xml:"form_admin_settings"` +} + +// FormAdminSettings wraps a whole bunch of instance settings +type FormAdminSettings struct { + SiteTitle *string `form:"site_title" json:"site_title" xml:"site_title"` + RegistrationsMode *string `form:"registrations_mode" json:"registrations_mode" xml:"registrations_mode"` + SiteContactUsername *string `form:"site_contact_username" json:"site_contact_username" xml:"site_contact_username"` + SiteContactEmail *string `form:"site_contact_email" json:"site_contact_email" xml:"site_contact_email"` + SiteShortDescription *string `form:"site_short_description" json:"site_short_description" xml:"site_short_description"` + SiteDescription *string `form:"site_description" json:"site_description" xml:"site_description"` + SiteExtendedDescription *string `form:"site_extended_description" json:"site_extended_description" xml:"site_extended_description"` + SiteTerms *string `form:"site_terms" json:"site_terms" xml:"site_terms"` + Thumbnail *multipart.FileHeader `form:"thumbnail" json:"thumbnail" xml:"thumbnail"` + Hero *multipart.FileHeader `form:"hero" json:"hero" xml:"hero"` + Mascot *multipart.FileHeader `form:"mascot" json:"mascot" xml:"mascot"` + RequireInviteText *bool `form:"require_invite_text" json:"require_invite_text" xml:"require_invite_text"` +} diff --git a/internal/api/model/instance.go b/internal/api/model/instance.go index e4dad35..3552ead 100644 --- a/internal/api/model/instance.go +++ b/internal/api/model/instance.go @@ -45,7 +45,7 @@ type Instance struct { // URLs of interest for clients apps. URLS *InstanceURLs `json:"urls,omitempty"` // Statistics about how much information the instance contains. - Stats *InstanceStats `json:"stats,omitempty"` + Stats map[string]int `json:"stats,omitempty"` // Banner image for the website. Thumbnail string `json:"thumbnail"` // A user that can be contacted, as an alternative to email. diff --git a/internal/db/db.go b/internal/db/db.go index 4e21358..204f04c 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -253,6 +253,14 @@ type DB interface { // GetNotificationsForAccount returns a list of notifications that pertain to the given accountID. GetNotificationsForAccount(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, error) + // GetUserCountForInstance returns the number of known accounts registered with the given domain. + GetUserCountForInstance(domain string) (int, error) + + // GetStatusCountForInstance returns the number of known statuses posted from the given domain. + GetStatusCountForInstance(domain string) (int, error) + + // GetDomainCountForInstance returns the number of known instances known that the given domain federates with. + GetDomainCountForInstance(domain string) (int, error) /* USEFUL CONVERSION FUNCTIONS */ diff --git a/internal/db/pg/instancestats.go b/internal/db/pg/instancestats.go new file mode 100644 index 0000000..7bb9630 --- /dev/null +++ b/internal/db/pg/instancestats.go @@ -0,0 +1,52 @@ +package pg + +import ( + "github.com/go-pg/pg/v10" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (ps *postgresService) GetUserCountForInstance(domain string) (int, error) { + q := ps.conn.Model(&[]*gtsmodel.Account{}) + + if domain == ps.config.Host { + // if the domain is *this* domain, just count where the domain field is null + q = q.Where("? IS NULL", pg.Ident("domain")) + } else { + q = q.Where("domain = ?", domain) + } + + // don't count the instance account or suspended users + q = q.Where("username != ?", domain).Where("? IS NULL", pg.Ident("suspended_at")) + + return q.Count() +} + +func (ps *postgresService) GetStatusCountForInstance(domain string) (int, error) { + q := ps.conn.Model(&[]*gtsmodel.Status{}) + + if domain == ps.config.Host { + // if the domain is *this* domain, just count where local is true + q = q.Where("local = ?", true) + } else { + // join on the domain of the account + q = q.Join("JOIN accounts AS account ON account.id = status.account_id"). + Where("account.domain = ?", domain) + } + + return q.Count() +} + +func (ps *postgresService) GetDomainCountForInstance(domain string) (int, error) { + q := ps.conn.Model(&[]*gtsmodel.Instance{}) + + if domain == ps.config.Host { + // if the domain is *this* domain, just count other instances it knows about + // TODO: exclude domains that are blocked or silenced + q = q.Where("domain != ?", domain) + } else { + // TODO: implement federated domain counting properly for remote domains + return 0, nil + } + + return q.Count() +} diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 90460ec..cd27ead 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -511,9 +511,30 @@ func (c *converter) InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, erro ShortDescription: i.ShortDescription, Email: i.ContactEmail, Version: i.Version, + Stats: make(map[string]int), } + // if the requested instance is *this* instance, we can add some extra information if i.Domain == c.config.Host { + userCountKey := "user_count" + statusCountKey := "status_count" + domainCountKey := "domain_count" + + userCount, err := c.db.GetUserCountForInstance(c.config.Host) + if err == nil { + mi.Stats[userCountKey] = userCount + } + + statusCount, err := c.db.GetStatusCountForInstance(c.config.Host) + if err == nil { + mi.Stats[statusCountKey] = statusCount + } + + domainCount, err := c.db.GetDomainCountForInstance(c.config.Host) + if err == nil { + mi.Stats[domainCountKey] = domainCount + } + mi.Registrations = c.config.AccountsConfig.OpenRegistration mi.ApprovalRequired = c.config.AccountsConfig.RequireApproval mi.InvitesEnabled = false // TODO diff --git a/web/template/index.tmpl b/web/template/index.tmpl index 8d431b8..e898332 100644 --- a/web/template/index.tmpl +++ b/web/template/index.tmpl @@ -3,12 +3,9 @@ Clipart styled sloth logo
- -

Home to 3 users - who posted 42069 statuses, - federating with 9001 other instances.

+

Home to {{.instance.Stats.user_count}} users + who posted {{.instance.Stats.status_count}} statuses, + federating with {{.instance.Stats.domain_count}} other instances.

This is the default landing page, you can edit it from ./web/template/index.tmpl