mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09: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. | ||||
| 	// 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 | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -2240,6 +2240,9 @@ PATH = | ||||
| ;; | ||||
| ;; Enable/Disable federation capabilities | ||||
| ; 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`) | ||||
|  | ||||
| - `ENABLED`: **true**: Enable/Disable federation capabilities | ||||
| - `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled | ||||
|  | ||||
| ## Packages (`packages`) | ||||
|  | ||||
|   | ||||
| @@ -26,6 +26,10 @@ func TestNodeinfo(t *testing.T) { | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
| 		var nodeinfo api.NodeInfo | ||||
| 		DecodeJSON(t, resp, &nodeinfo) | ||||
| 		assert.True(t, nodeinfo.OpenRegistrations) | ||||
| 		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) | ||||
| } | ||||
|  | ||||
| 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 | ||||
| func GetStatistic() (stats Statistic) { | ||||
| 	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.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) | ||||
| 	stats.Counter.Repo = repo_model.CountRepositories(true) | ||||
|   | ||||
| @@ -744,16 +744,25 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e | ||||
| 	return committer.Commit() | ||||
| } | ||||
|  | ||||
| func countUsers(e db.Engine) int64 { | ||||
| 	count, _ := e. | ||||
| 		Where("type=0"). | ||||
| 		Count(new(User)) | ||||
| 	return count | ||||
| // CountUserFilter represent optional filters for CountUsers | ||||
| type CountUserFilter struct { | ||||
| 	LastLoginSince *int64 | ||||
| } | ||||
|  | ||||
| // CountUsers returns number of users. | ||||
| func CountUsers() int64 { | ||||
| 	return countUsers(db.GetEngine(db.DefaultContext)) | ||||
| func CountUsers(opts *CountUserFilter) int64 { | ||||
| 	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 | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/auth" | ||||
| 	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/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| @@ -247,6 +248,7 @@ func APIContexter() func(http.Handler) http.Handler { | ||||
| 					Resp:   NewResponse(w), | ||||
| 					Data:   map[string]interface{}{}, | ||||
| 					Locale: locale, | ||||
| 					Cache:  cache.GetCache(), | ||||
| 					Repo: &Repository{ | ||||
| 						PullRequest: &PullRequest{}, | ||||
| 					}, | ||||
|   | ||||
| @@ -9,9 +9,11 @@ import "code.gitea.io/gitea/modules/log" | ||||
| // Federation settings | ||||
| var ( | ||||
| 	Federation = struct { | ||||
| 		Enabled bool | ||||
| 		Enabled             bool | ||||
| 		ShareUserStatistics bool | ||||
| 	}{ | ||||
| 		Enabled: true, | ||||
| 		Enabled:             true, | ||||
| 		ShareUserStatistics: true, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -6,12 +6,17 @@ package misc | ||||
|  | ||||
| import ( | ||||
| 	"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/setting" | ||||
| 	"code.gitea.io/gitea/modules/structs" | ||||
| ) | ||||
|  | ||||
| const cacheKeyNodeInfoUsage = "API_NodeInfoUsage" | ||||
|  | ||||
| // NodeInfo returns the NodeInfo for the Gitea instance to allow for federation | ||||
| func NodeInfo(ctx *context.APIContext) { | ||||
| 	// swagger:operation GET /nodeinfo miscellaneous getNodeInfo | ||||
| @@ -23,6 +28,37 @@ func NodeInfo(ctx *context.APIContext) { | ||||
| 	//   "200": | ||||
| 	//     "$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{ | ||||
| 		Version: "2.1", | ||||
| 		Software: structs.NodeInfoSoftware{ | ||||
| @@ -34,12 +70,10 @@ func NodeInfo(ctx *context.APIContext) { | ||||
| 		Protocols: []string{"activitypub"}, | ||||
| 		Services: structs.NodeInfoServices{ | ||||
| 			Inbound:  []string{}, | ||||
| 			Outbound: []string{}, | ||||
| 			Outbound: []string{"rss2.0"}, | ||||
| 		}, | ||||
| 		OpenRegistrations: setting.Service.ShowRegistrationButton, | ||||
| 		Usage: structs.NodeInfoUsage{ | ||||
| 			Users: structs.NodeInfoUsageUsers{}, | ||||
| 		}, | ||||
| 		Usage:             nodeInfoUsage, | ||||
| 	} | ||||
| 	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. | ||||
| func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) { | ||||
| 	// Auto-set admin for the only user. | ||||
| 	if user_model.CountUsers() == 1 { | ||||
| 	if user_model.CountUsers(nil) == 1 { | ||||
| 		u.IsAdmin = true | ||||
| 		u.IsActive = true | ||||
| 		u.SetLastLogin() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user