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 @@
-
- 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