diff --git a/package/gitea/source/modules/structs/user.go b/package/gitea/source/modules/structs/user.go index 90dbcff2..67160e34 100644 --- a/package/gitea/source/modules/structs/user.go +++ b/package/gitea/source/modules/structs/user.go @@ -80,8 +80,9 @@ type UserSettings struct { Theme string `json:"theme"` DiffViewStyle string `json:"diff_view_style"` // Privacy - HideEmail bool `json:"hide_email"` - HideActivity bool `json:"hide_activity"` + HideEmail bool `json:"hide_email"` + HideActivity bool `json:"hide_activity"` + IncludePrivateContributions bool `json:"include_private_contributions"` } // UserSettingsOptions represents options to change user settings @@ -95,8 +96,9 @@ type UserSettingsOptions struct { Theme *string `json:"theme"` DiffViewStyle *string `json:"diff_view_style"` // Privacy - HideEmail *bool `json:"hide_email"` - HideActivity *bool `json:"hide_activity"` + HideEmail *bool `json:"hide_email"` + HideActivity *bool `json:"hide_activity"` + IncludePrivateContributions *bool `json:"include_private_contributions"` } // RenameUserOption options when renaming a user diff --git a/package/gitea/source/options/locale/locale_en-US.json b/package/gitea/source/options/locale/locale_en-US.json index 7c20cdc9..13f756d9 100644 --- a/package/gitea/source/options/locale/locale_en-US.json +++ b/package/gitea/source/options/locale/locale_en-US.json @@ -708,6 +708,8 @@ "settings.privacy": "Privacy", "settings.keep_activity_private": "Hide Activity from profile page", "settings.keep_activity_private_popup": "Makes the activity visible only for you and the admins", + "settings.include_private_contributions": "Show private contributions on profile heatmap", + "settings.include_private_contributions_popup": "Publicly reveals only contribution dates and counts from non-public repositories, never repository or commit details.", "settings.lookup_avatar_by_mail": "Look Up Avatar by Email Address", "settings.federated_avatar_lookup": "Federated Avatar Lookup", "settings.enable_custom_avatar": "Use Custom Avatar", diff --git a/package/gitea/source/routers/api/v1/user/settings.go b/package/gitea/source/routers/api/v1/user/settings.go index d67c54b3..9a143fd2 100644 --- a/package/gitea/source/routers/api/v1/user/settings.go +++ b/package/gitea/source/routers/api/v1/user/settings.go @@ -24,7 +24,12 @@ func GetUserSettings(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/UserSettings" - ctx.JSON(http.StatusOK, convert.User2UserSettings(ctx.Doer)) + settings, err := convert.User2UserSettings(ctx, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusOK, settings) } // UpdateUserSettings returns user settings @@ -46,20 +51,26 @@ func UpdateUserSettings(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.UserSettingsOptions) opts := &user_service.UpdateOptions{ - FullName: optional.FromPtr(form.FullName), - Description: optional.FromPtr(form.Description), - Website: optional.FromPtr(form.Website), - Location: optional.FromPtr(form.Location), - Language: optional.FromPtr(form.Language), - Theme: optional.FromPtr(form.Theme), - DiffViewStyle: optional.FromPtr(form.DiffViewStyle), - KeepEmailPrivate: optional.FromPtr(form.HideEmail), - KeepActivityPrivate: optional.FromPtr(form.HideActivity), + FullName: optional.FromPtr(form.FullName), + Description: optional.FromPtr(form.Description), + Website: optional.FromPtr(form.Website), + Location: optional.FromPtr(form.Location), + Language: optional.FromPtr(form.Language), + Theme: optional.FromPtr(form.Theme), + DiffViewStyle: optional.FromPtr(form.DiffViewStyle), + KeepEmailPrivate: optional.FromPtr(form.HideEmail), + KeepActivityPrivate: optional.FromPtr(form.HideActivity), + IncludePrivateContributions: optional.FromPtr(form.IncludePrivateContributions), } if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil { ctx.APIErrorInternal(err) return } - ctx.JSON(http.StatusOK, convert.User2UserSettings(ctx.Doer)) + settings, err := convert.User2UserSettings(ctx, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusOK, settings) } diff --git a/package/gitea/source/routers/web/user/setting/profile.go b/package/gitea/source/routers/web/user/setting/profile.go index 88d8e75d..988977ae 100644 --- a/package/gitea/source/routers/web/user/setting/profile.go +++ b/package/gitea/source/routers/web/user/setting/profile.go @@ -48,16 +48,32 @@ func Profile(ctx *context.Context) { ctx.Data["PageIsSettingsProfile"] = true ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) + if !loadProfilePrivateContributionSetting(ctx) { + return + } ctx.HTML(http.StatusOK, tplSettingsProfile) } +func loadProfilePrivateContributionSetting(ctx *context.Context) bool { + includePrivateContributions, err := user_model.GetIncludePrivateContributions(ctx, ctx.Doer.ID) + if err != nil { + ctx.ServerError("GetIncludePrivateContributions", err) + return false + } + ctx.Data["IncludePrivateContributions"] = includePrivateContributions + return true +} + // ProfilePost response for change user's profile func ProfilePost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings_title") ctx.Data["PageIsSettingsProfile"] = true ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) + if !loadProfilePrivateContributionSetting(ctx) { + return + } if ctx.HasError() { ctx.HTML(http.StatusOK, tplSettingsProfile) @@ -94,12 +110,13 @@ func ProfilePost(ctx *context.Context) { } opts := &user_service.UpdateOptions{ - KeepEmailPrivate: optional.Some(form.KeepEmailPrivate), - Description: optional.Some(form.Description), - Website: optional.Some(form.Website), - Location: optional.Some(form.Location), - Visibility: optional.Some(form.Visibility), - KeepActivityPrivate: optional.Some(form.KeepActivityPrivate), + KeepEmailPrivate: optional.Some(form.KeepEmailPrivate), + Description: optional.Some(form.Description), + Website: optional.Some(form.Website), + Location: optional.Some(form.Location), + Visibility: optional.Some(form.Visibility), + KeepActivityPrivate: optional.Some(form.KeepActivityPrivate), + IncludePrivateContributions: optional.Some(form.IncludePrivateContributions), } if form.FullName != "" { diff --git a/package/gitea/source/services/convert/user.go b/package/gitea/source/services/convert/user.go index cee4de50..1b316431 100644 --- a/package/gitea/source/services/convert/user.go +++ b/package/gitea/source/services/convert/user.go @@ -86,18 +86,23 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap } // User2UserSettings return UserSettings based on a user -func User2UserSettings(user *user_model.User) api.UserSettings { - return api.UserSettings{ - FullName: user.FullName, - Website: user.Website, - Location: user.Location, - Language: user.Language, - Description: user.Description, - Theme: user.Theme, - HideEmail: user.KeepEmailPrivate, - HideActivity: user.KeepActivityPrivate, - DiffViewStyle: user.DiffViewStyle, +func User2UserSettings(ctx context.Context, user *user_model.User) (api.UserSettings, error) { + includePrivateContributions, err := user_model.GetIncludePrivateContributions(ctx, user.ID) + if err != nil { + return api.UserSettings{}, err } + return api.UserSettings{ + FullName: user.FullName, + Website: user.Website, + Location: user.Location, + Language: user.Language, + Description: user.Description, + Theme: user.Theme, + HideEmail: user.KeepEmailPrivate, + HideActivity: user.KeepActivityPrivate, + IncludePrivateContributions: includePrivateContributions, + DiffViewStyle: user.DiffViewStyle, + }, nil } // ToUserAndPermission return User and its collaboration permission for a repository diff --git a/package/gitea/source/services/forms/user_form.go b/package/gitea/source/services/forms/user_form.go index cc514a2e..e0979cfa 100644 --- a/package/gitea/source/services/forms/user_form.go +++ b/package/gitea/source/services/forms/user_form.go @@ -208,14 +208,15 @@ func (f *IntrospectTokenForm) Validate(req *http.Request, errs binding.Errors) b // UpdateProfileForm form for updating profile type UpdateProfileForm struct { - Name string `binding:"Username;MaxSize(40)"` - FullName string `binding:"MaxSize(100)"` - KeepEmailPrivate bool - Website string `binding:"ValidSiteUrl;MaxSize(255)"` - Location string `binding:"MaxSize(50)"` - Description string `binding:"MaxSize(255)"` - Visibility structs.VisibleType - KeepActivityPrivate bool + Name string `binding:"Username;MaxSize(40)"` + FullName string `binding:"MaxSize(100)"` + KeepEmailPrivate bool + Website string `binding:"ValidSiteUrl;MaxSize(255)"` + Location string `binding:"MaxSize(50)"` + Description string `binding:"MaxSize(255)"` + Visibility structs.VisibleType + KeepActivityPrivate bool + IncludePrivateContributions bool } // Validate validates the fields diff --git a/package/gitea/source/services/user/update.go b/package/gitea/source/services/user/update.go index d7354542..4793a781 100644 --- a/package/gitea/source/services/user/update.go +++ b/package/gitea/source/services/user/update.go @@ -8,6 +8,7 @@ import ( "fmt" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" password_module "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/optional" @@ -47,6 +48,7 @@ type UpdateOptions struct { IsRestricted optional.Option[bool] Visibility optional.Option[structs.VisibleType] KeepActivityPrivate optional.Option[bool] + IncludePrivateContributions optional.Option[bool] Language optional.Option[string] Theme optional.Option[string] DiffViewStyle optional.Option[string] @@ -182,7 +184,23 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er cols = append(cols, "last_login_unix") } - return user_model.UpdateUserCols(ctx, u, cols...) + if len(cols) > 0 || opts.IncludePrivateContributions.Has() { + return db.WithTx(ctx, func(ctx context.Context) error { + if len(cols) > 0 { + if err := user_model.UpdateUserCols(ctx, u, cols...); err != nil { + return err + } + } + + if opts.IncludePrivateContributions.Has() { + return user_model.SetIncludePrivateContributions(ctx, u.ID, opts.IncludePrivateContributions.Value()) + } + + return nil + }) + } + + return nil } type UpdateAuthOptions struct { diff --git a/package/gitea/source/services/user/update_test.go b/package/gitea/source/services/user/update_test.go index b81ac957..9567a04b 100644 --- a/package/gitea/source/services/user/update_test.go +++ b/package/gitea/source/services/user/update_test.go @@ -44,6 +44,7 @@ func TestUpdateUser(t *testing.T) { IsAdmin: UpdateOptionFieldFromValue(true), Visibility: optional.Some(structs.VisibleTypePrivate), KeepActivityPrivate: optional.Some(true), + IncludePrivateContributions: optional.Some(true), Language: optional.Some("lang"), Theme: optional.Some("theme"), DiffViewStyle: optional.Some("split"), @@ -66,6 +67,9 @@ func TestUpdateUser(t *testing.T) { assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) assert.Equal(t, opts.Visibility.Value(), user.Visibility) assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) + includePrivateContributions, err := user_model.GetIncludePrivateContributions(t.Context(), user.ID) + assert.NoError(t, err) + assert.Equal(t, opts.IncludePrivateContributions.Value(), includePrivateContributions) assert.Equal(t, opts.Language.Value(), user.Language) assert.Equal(t, opts.Theme.Value(), user.Theme) assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle) @@ -86,6 +90,9 @@ func TestUpdateUser(t *testing.T) { assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) assert.Equal(t, opts.Visibility.Value(), user.Visibility) assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) + includePrivateContributions, err = user_model.GetIncludePrivateContributions(t.Context(), user.ID) + assert.NoError(t, err) + assert.Equal(t, opts.IncludePrivateContributions.Value(), includePrivateContributions) assert.Equal(t, opts.Language.Value(), user.Language) assert.Equal(t, opts.Theme.Value(), user.Theme) assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle) diff --git a/package/gitea/source/templates/swagger/v1_json.tmpl b/package/gitea/source/templates/swagger/v1_json.tmpl index 355e4519..1979e20d 100644 --- a/package/gitea/source/templates/swagger/v1_json.tmpl +++ b/package/gitea/source/templates/swagger/v1_json.tmpl @@ -29655,6 +29655,10 @@ "type": "boolean", "x-go-name": "HideEmail" }, + "include_private_contributions": { + "type": "boolean", + "x-go-name": "IncludePrivateContributions" + }, "language": { "type": "string", "x-go-name": "Language" @@ -29699,6 +29703,10 @@ "type": "boolean", "x-go-name": "HideEmail" }, + "include_private_contributions": { + "type": "boolean", + "x-go-name": "IncludePrivateContributions" + }, "language": { "type": "string", "x-go-name": "Language" @@ -31073,4 +31081,4 @@ "TOTPHeader": [] } ] -} \ No newline at end of file +} diff --git a/package/gitea/source/templates/user/settings/profile.tmpl b/package/gitea/source/templates/user/settings/profile.tmpl index 0f88f712..68aa6c93 100644 --- a/package/gitea/source/templates/user/settings/profile.tmpl +++ b/package/gitea/source/templates/user/settings/profile.tmpl @@ -88,6 +88,13 @@ +