From ea958da30ff3d17fdfa1fc0aff431a2ac63dce55 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Fri, 1 Sep 2023 14:53:40 +0200 Subject: [PATCH 1/8] Allow watching custom events --- models/activities/notification.go | 9 ++- models/repo/watch.go | 96 +++++++++++++++++++++++-- modules/notification/ui/ui.go | 2 +- modules/structs/repo.go | 7 ++ routers/api/v1/api.go | 2 + routers/api/v1/repo/subscriber.go | 65 +++++++++++++++++ routers/api/v1/swagger/options.go | 3 + routers/api/v1/user/watch.go | 41 +++++++++++ services/mailer/mail_issue.go | 6 +- services/mailer/mail_release.go | 2 +- templates/swagger/v1_json.tmpl | 115 +++++++++++++++++++++++++++++- 11 files changed, 336 insertions(+), 12 deletions(-) diff --git a/models/activities/notification.go b/models/activities/notification.go index ef263ef735c3d..a91b4a60dc4ec 100644 --- a/models/activities/notification.go +++ b/models/activities/notification.go @@ -213,7 +213,14 @@ func createOrUpdateIssueNotifications(ctx context.Context, issueID, commentID, n } toNotify.AddMultiple(issueWatches...) if !(issue.IsPull && issues_model.HasWorkInProgressPrefix(issue.Title)) { - repoWatches, err := repo_model.GetRepoWatchersIDs(ctx, issue.RepoID) + var repoWatches []int64 + + if issue.IsPull { + repoWatches, err = repo_model.GetRepoWatchersEventIDs(ctx, issue.RepoID, repo_model.WatchEventTypePullRequest) + } else { + repoWatches, err = repo_model.GetRepoWatchersEventIDs(ctx, issue.RepoID, repo_model.WatchEventTypeIssue) + } + if err != nil { return err } diff --git a/models/repo/watch.go b/models/repo/watch.go index 00f313ca7cc5b..b40de7ac0b707 100644 --- a/models/repo/watch.go +++ b/models/repo/watch.go @@ -9,7 +9,10 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/builder" ) // WatchMode specifies what kind of watch the user has on a repository @@ -24,16 +27,33 @@ const ( WatchModeDont // 2 // WatchModeAuto watch repository (from AutoWatchOnChanges) WatchModeAuto // 3 + // WatchModeCustom watch repository for custom events + WatchModeCustom // 4 +) + +// WatchEventType specifies what kind of event is wanted +type WatchEventType int8 + +const ( + // WatchEventTypeIssue watch issues + WatchEventTypeIssue WatchEventType = iota + // WatchEventTypeIssue watch pull requests + WatchEventTypePullRequest + // WatchEventTypeIssue watch releases + WatchEventTypeRelease ) // Watch is connection request for receiving repository notification. type Watch struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"UNIQUE(watch)"` - RepoID int64 `xorm:"UNIQUE(watch)"` - Mode WatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(watch)"` + RepoID int64 `xorm:"UNIQUE(watch)"` + Mode WatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + CustomIssues bool `xorm:"NOT NULL DEFAULT FALSE"` + CustomPullRequests bool `xorm:"NOT NULL DEFAULT FALSE"` + CustomReleases bool `xorm:"NOT NULL DEFAULT FALSE"` } func init() { @@ -65,7 +85,7 @@ func IsWatching(userID, repoID int64) bool { } func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) { - if watch.Mode == mode { + if watch.Mode == mode && mode != WatchModeCustom { return nil } if mode == WatchModeAuto && (watch.Mode == WatchModeDont || IsWatchMode(watch.Mode)) { @@ -85,6 +105,12 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) watch.Mode = mode + if mode != WatchModeCustom { + watch.CustomIssues = false + watch.CustomPullRequests = false + watch.CustomReleases = false + } + e := db.GetEngine(ctx) if !hadrec && needsrec { @@ -131,6 +157,19 @@ func WatchRepo(ctx context.Context, userID, repoID int64, doWatch bool) (err err return err } +func WatchRepoCustom(ctx context.Context, userID, repoID int64, watchTypes api.RepoCustomWatchOptions) error { + watch, err := GetWatch(ctx, userID, repoID) + if err != nil { + return err + } + + watch.CustomIssues = watchTypes.Issues + watch.CustomPullRequests = watchTypes.PullRequests + watch.CustomReleases = watchTypes.Releases + + return watchRepoMode(ctx, watch, WatchModeCustom) +} + // GetWatchers returns all watchers of given repository. func GetWatchers(ctx context.Context, repoID int64) ([]*Watch, error) { watches := make([]*Watch, 0, 10) @@ -170,6 +209,49 @@ func GetRepoWatchers(repoID int64, opts db.ListOptions) ([]*user_model.User, err return users, sess.Find(&users) } +func getRepoWatchEventCond(event WatchEventType) builder.Cond { + cond := builder.NewCond() + + switch event { + case WatchEventTypeIssue: + cond = cond.And(builder.Eq{"`watch`.mode": WatchModeCustom}, builder.Eq{"`watch`.custom_issues": true}) + case WatchEventTypePullRequest: + cond = cond.And(builder.Eq{"`watch`.mode": WatchModeCustom}, builder.Eq{"`watch`.custom_pull_requests": true}) + case WatchEventTypeRelease: + cond = cond.And(builder.Eq{"`watch`.mode": WatchModeCustom}, builder.Eq{"`watch`.custom_releases": true}) + } + + return builder.Or(builder.Eq{"`watch`.mode": WatchModeNormal}, cond) +} + +// GetRepoWatchers returns range of users watching the given event in the given repository. +func GetRepoWatchersEvent(repoID int64, event WatchEventType, opts db.ListOptions) ([]*user_model.User, error) { + sess := db.GetEngine(db.DefaultContext).Where("watch.repo_id=?", repoID). + Join("LEFT", "watch", "`user`.id=`watch`.user_id"). + And(getRepoWatchEventCond(event)) + if opts.Page > 0 { + sess = db.SetSessionPagination(sess, &opts) + users := make([]*user_model.User, 0, opts.PageSize) + + return users, sess.Find(&users) + } + + users := make([]*user_model.User, 0, 8) + return users, sess.Find(&users) +} + +// GetRepoWatchersIDs returns IDs of watchers for a given repo ID and the givene Event +// but avoids joining with `user` for performance reasons +// User permissions must be verified elsewhere if required +func GetRepoWatchersEventIDs(ctx context.Context, repoID int64, event WatchEventType) ([]int64, error) { + ids := make([]int64, 0, 64) + return ids, db.GetEngine(ctx).Table("watch"). + Where("watch.repo_id=?", repoID). + And(getRepoWatchEventCond(event)). + Select("user_id"). + Find(&ids) +} + // WatchIfAuto subscribes to repo if AutoWatchOnChanges is set func WatchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error { if !isWrite || !setting.Service.AutoWatchOnChanges { diff --git a/modules/notification/ui/ui.go b/modules/notification/ui/ui.go index 2ca1a7700f2cf..b3e05cb632805 100644 --- a/modules/notification/ui/ui.go +++ b/modules/notification/ui/ui.go @@ -133,7 +133,7 @@ func (ns *notificationService) NotifyNewPullRequest(ctx context.Context, pr *iss return } toNotify := make(container.Set[int64], 32) - repoWatchers, err := repo_model.GetRepoWatchersIDs(ctx, pr.Issue.RepoID) + repoWatchers, err := repo_model.GetRepoWatchersEventIDs(ctx, pr.Issue.RepoID, repo_model.WatchEventTypePullRequest) if err != nil { log.Error("GetRepoWatchersIDs: %v", err) return diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6a2ba4836bb93..84167a9b8c7da 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -388,3 +388,10 @@ type UpdateRepoAvatarOption struct { // image must be base64 encoded Image string `json:"image" binding:"Required"` } + +// RepoCustomWatchOptions options when wtaching custom events of a repo +type RepoCustomWatchOptions struct { + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` + Releases bool `json:"releases"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 32e5a10bbe78d..5a8a6633e186b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1022,10 +1022,12 @@ func Routes() *web.Route { m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw) m.Get("/stargazers", repo.ListStargazers) m.Get("/subscribers", repo.ListSubscribers) + m.Get("/subscribers/{event}", repo.ListSubscribersEvent) m.Group("/subscription", func() { m.Get("", user.IsWatching) m.Put("", reqToken(), user.Watch) m.Delete("", reqToken(), user.Unwatch) + m.Put("/custom", reqToken(), bind(api.RepoCustomWatchOptions{}), user.WatchCustom) }) m.Group("/releases", func() { m.Combo("").Get(repo.ListReleases). diff --git a/routers/api/v1/repo/subscriber.go b/routers/api/v1/repo/subscriber.go index 613fbee409298..5a8f9d7af5bd0 100644 --- a/routers/api/v1/repo/subscriber.go +++ b/routers/api/v1/repo/subscriber.go @@ -4,7 +4,9 @@ package repo import ( + "fmt" "net/http" + "strings" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" @@ -56,3 +58,66 @@ func ListSubscribers(ctx *context.APIContext) { ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumWatches)) ctx.JSON(http.StatusOK, users) } + +// ListSubscribers list a repo's subscribers (i.e. watchers) for the given event +func ListSubscribersEvent(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/subscribers/{event} repository repoListSubscribersEvent + // --- + // summary: List a repo's watchers for the given event + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: event + // in: path + // description: the event + // type: string + // enum: [issues,pullrequests,releases] + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/UserList" + var eventConst repo_model.WatchEventType + + switch strings.ToLower(ctx.Params("event")) { + case "issues": + eventConst = repo_model.WatchEventTypeIssue + case "pullrequests": + eventConst = repo_model.WatchEventTypePullRequest + case "releases": + eventConst = repo_model.WatchEventTypeRelease + default: + ctx.Error(http.StatusBadRequest, "InvalidEvent", fmt.Errorf("%s is not a valid event", ctx.Params("event"))) + return + } + + subscribers, err := repo_model.GetRepoWatchersEvent(ctx.Repo.Repository.ID, eventConst, utils.GetListOptions(ctx)) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetRepoWatchersEvent", err) + return + } + users := make([]*api.User, len(subscribers)) + for i, subscriber := range subscribers { + users[i] = convert.ToUser(ctx, subscriber, ctx.Doer) + } + + ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumWatches)) + ctx.JSON(http.StatusOK, users) +} diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index 6f7859df62ed4..ee3d93267b4fa 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -190,4 +190,7 @@ type swaggerParameterBodies struct { // in:body CreateOrUpdateSecretOption api.CreateOrUpdateSecretOption + + // in:body + RepoCustomWatchOptions api.RepoCustomWatchOptions } diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go index 172d9d5cc5977..5d74ec1666334 100644 --- a/routers/api/v1/user/watch.go +++ b/routers/api/v1/user/watch.go @@ -13,6 +13,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/convert" ) @@ -171,6 +172,46 @@ func Watch(ctx *context.APIContext) { }) } +// Watch the repo specified in ctx, as the authenticated user +func WatchCustom(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/subscription/custom repository userCurrentPutSubscriptionCustom + // --- + // summary: Watch a repo + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/RepoCustomWatchOptions" + // responses: + // "200": + // "$ref": "#/responses/WatchInfo" + opts := *web.GetForm(ctx).(*api.RepoCustomWatchOptions) + + err := repo_model.WatchRepoCustom(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, opts) + if err != nil { + ctx.Error(http.StatusInternalServerError, "WatchRepo", err) + return + } + ctx.JSON(http.StatusOK, api.WatchInfo{ + Subscribed: true, + Ignored: false, + Reason: nil, + CreatedAt: ctx.Repo.Repository.CreatedUnix.AsTime(), + URL: subscriptionURL(ctx.Repo.Repository), + RepositoryURL: ctx.Repo.Repository.APIURL(), + }) +} + // Unwatch the repo specified in ctx, as the authenticated user func Unwatch(ctx *context.APIContext) { // swagger:operation DELETE /repos/{owner}/{repo}/subscription repository userCurrentDeleteSubscription diff --git a/services/mailer/mail_issue.go b/services/mailer/mail_issue.go index be5279aac5c51..c7678dcc561ba 100644 --- a/services/mailer/mail_issue.go +++ b/services/mailer/mail_issue.go @@ -83,7 +83,11 @@ func mailIssueCommentToParticipants(ctx *mailCommentContext, mentions []*user_mo // =========== Repo watchers =========== // Make repo watchers last, since it's likely the list with the most users if !(ctx.Issue.IsPull && ctx.Issue.PullRequest.IsWorkInProgress() && ctx.ActionType != activities_model.ActionCreatePullRequest) { - ids, err = repo_model.GetRepoWatchersIDs(ctx, ctx.Issue.RepoID) + if ctx.Issue.IsPull { + ids, err = repo_model.GetRepoWatchersEventIDs(ctx, ctx.Issue.RepoID, repo_model.WatchEventTypePullRequest) + } else { + ids, err = repo_model.GetRepoWatchersEventIDs(ctx, ctx.Issue.RepoID, repo_model.WatchEventTypeIssue) + } if err != nil { return fmt.Errorf("GetRepoWatchersIDs(%d): %w", ctx.Issue.RepoID, err) } diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go index fb638ebd42cf1..b5095b23428d7 100644 --- a/services/mailer/mail_release.go +++ b/services/mailer/mail_release.go @@ -29,7 +29,7 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) { return } - watcherIDList, err := repo_model.GetRepoWatchersIDs(ctx, rel.RepoID) + watcherIDList, err := repo_model.GetRepoWatchersEventIDs(ctx, rel.RepoID, repo_model.WatchEventTypeRelease) if err != nil { log.Error("GetRepoWatchersIDs(%d): %v", rel.RepoID, err) return diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 78491de2e1028..c50ce69fb15cd 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -12147,6 +12147,63 @@ } } }, + "/repos/{owner}/{repo}/subscribers/{event}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "List a repo's watchers for the given event", + "operationId": "repoListSubscribersEvent", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "enum": [ + "issues", + "pullrequests", + "releases" + ], + "type": "string", + "description": "the event", + "name": "event", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/UserList" + } + } + } + }, "/repos/{owner}/{repo}/subscription": { "get": { "tags": [ @@ -12236,6 +12293,43 @@ } } }, + "/repos/{owner}/{repo}/subscription/custom": { + "put": { + "tags": [ + "repository" + ], + "summary": "Watch a repo", + "operationId": "userCurrentPutSubscriptionCustom", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/RepoCustomWatchOptions" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/WatchInfo" + } + } + } + }, "/repos/{owner}/{repo}/tags": { "get": { "produces": [ @@ -21182,6 +21276,25 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "RepoCustomWatchOptions": { + "description": "RepoCustomWatchOptions options when wtaching custom events of a repo", + "type": "object", + "properties": { + "issues": { + "type": "boolean", + "x-go-name": "Issues" + }, + "pull_requests": { + "type": "boolean", + "x-go-name": "PullRequests" + }, + "releases": { + "type": "boolean", + "x-go-name": "Releases" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "RepoTopicOptions": { "description": "RepoTopicOptions a collection of repo topic names", "type": "object", @@ -23310,7 +23423,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/CreateOrUpdateSecretOption" + "$ref": "#/definitions/RepoCustomWatchOptions" } }, "redirect": { From 155cc6c890062bddfe13baa56d0ecb084c10ab17 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 4 Sep 2023 12:51:25 +0200 Subject: [PATCH 2/8] Add Frontend --- models/repo/watch.go | 29 ++++++++++++++++++++++++-- modules/context/repo.go | 7 ++++++- routers/web/repo/repo.go | 36 ++++++++++++++++++++++++++++++++ routers/web/web.go | 3 +++ services/forms/repo_form.go | 7 +++++++ templates/repo/header.tmpl | 41 +++++++++++++++++++++++++++++++++++-- 6 files changed, 118 insertions(+), 5 deletions(-) diff --git a/models/repo/watch.go b/models/repo/watch.go index b40de7ac0b707..4a2d67920b0c7 100644 --- a/models/repo/watch.go +++ b/models/repo/watch.go @@ -31,6 +31,26 @@ const ( WatchModeCustom // 4 ) +func (mode WatchMode) IsWatchModeNone() bool { + return mode == WatchModeNone +} + +func (mode WatchMode) IsWatchModeNormal() bool { + return mode == WatchModeNormal +} + +func (mode WatchMode) IsWatchModeDont() bool { + return mode == WatchModeDont +} + +func (mode WatchMode) IsWatchModeAuto() bool { + return mode == WatchModeAuto +} + +func (mode WatchMode) IsWatchModeCustom() bool { + return mode == WatchModeCustom +} + // WatchEventType specifies what kind of event is wanted type WatchEventType int8 @@ -60,6 +80,11 @@ func init() { db.RegisterModel(new(Watch)) } +// IsWatching checks if user has watched the Repo +func (watch Watch) IsWatching() bool { + return IsWatchMode(watch.Mode) +} + // GetWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found func GetWatch(ctx context.Context, userID, repoID int64) (Watch, error) { watch := Watch{UserID: userID, RepoID: repoID} @@ -133,12 +158,12 @@ func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) } // WatchRepoMode watch repository in specific mode. -func WatchRepoMode(userID, repoID int64, mode WatchMode) (err error) { +func WatchRepoMode(ctx context.Context, userID, repoID int64, mode WatchMode) (err error) { var watch Watch if watch, err = GetWatch(db.DefaultContext, userID, repoID); err != nil { return err } - return watchRepoMode(db.DefaultContext, watch, mode) + return watchRepoMode(ctx, watch, mode) } // WatchRepo watch or unwatch repository. diff --git a/modules/context/repo.go b/modules/context/repo.go index f5c56cf833234..7afcfe94d6ae1 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -597,8 +597,13 @@ func RepoAssignment(ctx *Context) context.CancelFunc { } if ctx.IsSigned { - ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx.Doer.ID, repo.ID) ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID) + + ctx.Data["RepoWatchData"], err = repo_model.GetWatch(ctx, ctx.Doer.ID, repo.ID) + if err != nil { + ctx.ServerError("getWatch", err) + return nil + } } if repo.IsFork { diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 4409381bc5081..a7dd4d0ec5ffc 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -686,3 +686,39 @@ func PrepareBranchList(ctx *context.Context) { } ctx.Data["Branches"] = brs } + +func SetWatchMode(ctx *context.Context) { + var mode repo_model.WatchMode + + switch strings.ToLower(ctx.Params("mode")) { + case "none": + mode = repo_model.WatchModeNone + case "normal": + mode = repo_model.WatchModeNormal + case "dont": + mode = repo_model.WatchModeDont + default: + ctx.ServerError("InvalidMode", fmt.Errorf("%s is not a valid mode", ctx.Params("mode"))) + return + } + + err := repo_model.WatchRepoMode(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, mode) + if err != nil { + ctx.ServerError("WatchRepoMode", err) + return + } + + ctx.Redirect(ctx.Repo.RepoLink) +} + +func WatchCustomPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RepoWatchCustomForm) + + err := repo_model.WatchRepoCustom(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID, api.RepoCustomWatchOptions{Issues: form.Issues, PullRequests: form.PullRequests, Releases: form.Releases}) + if err != nil { + ctx.ServerError("WatchRepoCustom", err) + return + } + + ctx.Redirect(ctx.Repo.RepoLink) +} diff --git a/routers/web/web.go b/routers/web/web.go index ec6742f6ce765..e2f333f27292c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1107,6 +1107,9 @@ func registerRoutes(m *web.Route) { m.Post("/delete", repo.DeleteBranchPost) m.Post("/restore", repo.RestoreBranchPost) }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) + + m.Get("/watch_mode/{mode}", reqSignIn, repo.SetWatchMode) + m.Post("/watch_custom", reqSignIn, web.Bind(forms.RepoWatchCustomForm{}), repo.WatchCustomPost) }, reqSignIn, context.RepoAssignment, context.UnitTypes()) // Tags diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index b36c8cc9b6613..6ff9058a29d95 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -901,3 +901,10 @@ func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding. ctx := context.GetValidateContext(req) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } + +// RepoWatchCustomForm wtach options for a repo +type RepoWatchCustomForm struct { + Issues bool `binding:"Required"` + PullRequests bool `binding:"Required"` + Releases bool `binding:"Required"` +} diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 984e9f044eb06..8e2b523e2d29d 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -63,17 +63,54 @@ {{end}} -
+ {{$.CsrfTokenHtml}}
{{CountFmt .NumWatches}}
+ {{if $.IsSigned}} + + {{end}} + {{if not $.DisableStars}}
{{$.CsrfTokenHtml}} From 7629b39d5c0b27e2833848dad7bdf4b22716b3b3 Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 4 Sep 2023 13:06:56 +0200 Subject: [PATCH 3/8] Fix tests --- models/repo/watch_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index 8b8c6d6250c4a..e0b3590b61200 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -122,18 +122,18 @@ func TestWatchRepoMode(t *testing.T) { unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeAuto)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeAuto)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeAuto}, 1) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNormal)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeNormal)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeNormal}, 1) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeDont)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeDont)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 1) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1, Mode: repo_model.WatchModeDont}, 1) - assert.NoError(t, repo_model.WatchRepoMode(12, 1, repo_model.WatchModeNone)) + assert.NoError(t, repo_model.WatchRepoMode(db.DefaultContext, 12, 1, repo_model.WatchModeNone)) unittest.AssertCount(t, &repo_model.Watch{UserID: 12, RepoID: 1}, 0) } From 9b8db5b5d0d3e6773dff616c2988a56be54e530c Mon Sep 17 00:00:00 2001 From: JakobDev Date: Mon, 4 Sep 2023 13:24:13 +0200 Subject: [PATCH 4/8] Remove Whitespace --- templates/repo/header.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 8e2b523e2d29d..20242dfa55ba6 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -74,7 +74,7 @@ - {{if $.IsSigned}} + {{if $.IsSigned}}