mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Federation: return useful statistic information for nodeinfo (#19561)
Add statistic information for total user count, active user count, issue count and comment count for `/nodeinfo`
This commit is contained in:
		@@ -556,7 +556,7 @@ func runCreateUser(c *cli.Context) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// If this is the first user being created.
 | 
						// If this is the first user being created.
 | 
				
			||||||
	// Take it as the admin and don't force a password update.
 | 
						// Take it as the admin and don't force a password update.
 | 
				
			||||||
	if n := user_model.CountUsers(); n == 0 {
 | 
						if n := user_model.CountUsers(nil); n == 0 {
 | 
				
			||||||
		changePassword = false
 | 
							changePassword = false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2240,6 +2240,9 @@ PATH =
 | 
				
			|||||||
;;
 | 
					;;
 | 
				
			||||||
;; Enable/Disable federation capabilities
 | 
					;; Enable/Disable federation capabilities
 | 
				
			||||||
; ENABLED = true
 | 
					; ENABLED = true
 | 
				
			||||||
 | 
					;;
 | 
				
			||||||
 | 
					;; Enable/Disable user statistics for nodeinfo if federation is enabled
 | 
				
			||||||
 | 
					; SHARE_USER_STATISTICS = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
					;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1085,6 +1085,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
 | 
				
			|||||||
## Federation (`federation`)
 | 
					## Federation (`federation`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `ENABLED`: **true**: Enable/Disable federation capabilities
 | 
					- `ENABLED`: **true**: Enable/Disable federation capabilities
 | 
				
			||||||
 | 
					- `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Packages (`packages`)
 | 
					## Packages (`packages`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,10 @@ func TestNodeinfo(t *testing.T) {
 | 
				
			|||||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
							resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
		var nodeinfo api.NodeInfo
 | 
							var nodeinfo api.NodeInfo
 | 
				
			||||||
		DecodeJSON(t, resp, &nodeinfo)
 | 
							DecodeJSON(t, resp, &nodeinfo)
 | 
				
			||||||
 | 
							assert.True(t, nodeinfo.OpenRegistrations)
 | 
				
			||||||
		assert.Equal(t, "gitea", nodeinfo.Software.Name)
 | 
							assert.Equal(t, "gitea", nodeinfo.Software.Name)
 | 
				
			||||||
 | 
							assert.Equal(t, 23, nodeinfo.Usage.Users.Total)
 | 
				
			||||||
 | 
							assert.Equal(t, 15, nodeinfo.Usage.LocalPosts)
 | 
				
			||||||
 | 
							assert.Equal(t, 2, nodeinfo.Usage.LocalComments)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -590,3 +590,10 @@ func TestLoadTotalTrackedTime(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
 | 
						assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCountIssues(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
						count, err := CountIssues(&IssuesOptions{})
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						assert.EqualValues(t, 15, count)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ type IssueByRepositoryCount struct {
 | 
				
			|||||||
// GetStatistic returns the database statistics
 | 
					// GetStatistic returns the database statistics
 | 
				
			||||||
func GetStatistic() (stats Statistic) {
 | 
					func GetStatistic() (stats Statistic) {
 | 
				
			||||||
	e := db.GetEngine(db.DefaultContext)
 | 
						e := db.GetEngine(db.DefaultContext)
 | 
				
			||||||
	stats.Counter.User = user_model.CountUsers()
 | 
						stats.Counter.User = user_model.CountUsers(nil)
 | 
				
			||||||
	stats.Counter.Org = organization.CountOrganizations()
 | 
						stats.Counter.Org = organization.CountOrganizations()
 | 
				
			||||||
	stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
 | 
						stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
 | 
				
			||||||
	stats.Counter.Repo = repo_model.CountRepositories(true)
 | 
						stats.Counter.Repo = repo_model.CountRepositories(true)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -744,16 +744,25 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 | 
				
			|||||||
	return committer.Commit()
 | 
						return committer.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func countUsers(e db.Engine) int64 {
 | 
					// CountUserFilter represent optional filters for CountUsers
 | 
				
			||||||
	count, _ := e.
 | 
					type CountUserFilter struct {
 | 
				
			||||||
		Where("type=0").
 | 
						LastLoginSince *int64
 | 
				
			||||||
		Count(new(User))
 | 
					 | 
				
			||||||
	return count
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CountUsers returns number of users.
 | 
					// CountUsers returns number of users.
 | 
				
			||||||
func CountUsers() int64 {
 | 
					func CountUsers(opts *CountUserFilter) int64 {
 | 
				
			||||||
	return countUsers(db.GetEngine(db.DefaultContext))
 | 
						return countUsers(db.DefaultContext, opts)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
 | 
				
			||||||
 | 
						sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if opts != nil && opts.LastLoginSince != nil {
 | 
				
			||||||
 | 
							sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						count, _ := sess.Count(new(User))
 | 
				
			||||||
 | 
						return count
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetVerifyUser get user by verify code
 | 
					// GetVerifyUser get user by verify code
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/auth"
 | 
						"code.gitea.io/gitea/models/auth"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/cache"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
@@ -247,6 +248,7 @@ func APIContexter() func(http.Handler) http.Handler {
 | 
				
			|||||||
					Resp:   NewResponse(w),
 | 
										Resp:   NewResponse(w),
 | 
				
			||||||
					Data:   map[string]interface{}{},
 | 
										Data:   map[string]interface{}{},
 | 
				
			||||||
					Locale: locale,
 | 
										Locale: locale,
 | 
				
			||||||
 | 
										Cache:  cache.GetCache(),
 | 
				
			||||||
					Repo: &Repository{
 | 
										Repo: &Repository{
 | 
				
			||||||
						PullRequest: &PullRequest{},
 | 
											PullRequest: &PullRequest{},
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,10 @@ import "code.gitea.io/gitea/modules/log"
 | 
				
			|||||||
var (
 | 
					var (
 | 
				
			||||||
	Federation = struct {
 | 
						Federation = struct {
 | 
				
			||||||
		Enabled             bool
 | 
							Enabled             bool
 | 
				
			||||||
 | 
							ShareUserStatistics bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		Enabled:             true,
 | 
							Enabled:             true,
 | 
				
			||||||
 | 
							ShareUserStatistics: true,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,12 +6,17 @@ package misc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/structs"
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
 | 
					// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
 | 
				
			||||||
func NodeInfo(ctx *context.APIContext) {
 | 
					func NodeInfo(ctx *context.APIContext) {
 | 
				
			||||||
	// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
 | 
						// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
 | 
				
			||||||
@@ -23,6 +28,37 @@ func NodeInfo(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/NodeInfo"
 | 
						//     "$ref": "#/responses/NodeInfo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodeInfoUsage := structs.NodeInfoUsage{}
 | 
				
			||||||
 | 
						if setting.Federation.ShareUserStatistics {
 | 
				
			||||||
 | 
							info, ok := ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								usersTotal := int(user_model.CountUsers(nil))
 | 
				
			||||||
 | 
								now := time.Now()
 | 
				
			||||||
 | 
								timeOneMonthAgo := now.AddDate(0, -1, 0).Unix()
 | 
				
			||||||
 | 
								timeHaveYearAgo := now.AddDate(0, -6, 0).Unix()
 | 
				
			||||||
 | 
								usersActiveMonth := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo}))
 | 
				
			||||||
 | 
								usersActiveHalfyear := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								allIssues, _ := models.CountIssues(&models.IssuesOptions{})
 | 
				
			||||||
 | 
								allComments, _ := models.CountComments(&models.FindCommentsOptions{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								info = structs.NodeInfoUsage{
 | 
				
			||||||
 | 
									Users: structs.NodeInfoUsageUsers{
 | 
				
			||||||
 | 
										Total:          usersTotal,
 | 
				
			||||||
 | 
										ActiveMonth:    usersActiveMonth,
 | 
				
			||||||
 | 
										ActiveHalfyear: usersActiveHalfyear,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									LocalPosts:    int(allIssues),
 | 
				
			||||||
 | 
									LocalComments: int(allComments),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
 | 
				
			||||||
 | 
									ctx.InternalServerError(err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							nodeInfoUsage = info
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nodeInfo := &structs.NodeInfo{
 | 
						nodeInfo := &structs.NodeInfo{
 | 
				
			||||||
		Version: "2.1",
 | 
							Version: "2.1",
 | 
				
			||||||
		Software: structs.NodeInfoSoftware{
 | 
							Software: structs.NodeInfoSoftware{
 | 
				
			||||||
@@ -34,12 +70,10 @@ func NodeInfo(ctx *context.APIContext) {
 | 
				
			|||||||
		Protocols: []string{"activitypub"},
 | 
							Protocols: []string{"activitypub"},
 | 
				
			||||||
		Services: structs.NodeInfoServices{
 | 
							Services: structs.NodeInfoServices{
 | 
				
			||||||
			Inbound:  []string{},
 | 
								Inbound:  []string{},
 | 
				
			||||||
			Outbound: []string{},
 | 
								Outbound: []string{"rss2.0"},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		OpenRegistrations: setting.Service.ShowRegistrationButton,
 | 
							OpenRegistrations: setting.Service.ShowRegistrationButton,
 | 
				
			||||||
		Usage: structs.NodeInfoUsage{
 | 
							Usage:             nodeInfoUsage,
 | 
				
			||||||
			Users: structs.NodeInfoUsageUsers{},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.JSON(http.StatusOK, nodeInfo)
 | 
						ctx.JSON(http.StatusOK, nodeInfo)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -600,7 +600,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
 | 
				
			|||||||
// sends a confirmation email if required.
 | 
					// sends a confirmation email if required.
 | 
				
			||||||
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
 | 
					func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
 | 
				
			||||||
	// Auto-set admin for the only user.
 | 
						// Auto-set admin for the only user.
 | 
				
			||||||
	if user_model.CountUsers() == 1 {
 | 
						if user_model.CountUsers(nil) == 1 {
 | 
				
			||||||
		u.IsAdmin = true
 | 
							u.IsAdmin = true
 | 
				
			||||||
		u.IsActive = true
 | 
							u.IsActive = true
 | 
				
			||||||
		u.SetLastLogin()
 | 
							u.SetLastLogin()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user