diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
commit | dd136858f1ea40ad3c94191d647487fa4f31926c (patch) | |
tree | 58fec94a7b2a12510c9664b21793f1ed560c6518 /routers/api/v1/notify | |
parent | Initial commit. (diff) | |
download | forgejo-dd136858f1ea40ad3c94191d647487fa4f31926c.tar.xz forgejo-dd136858f1ea40ad3c94191d647487fa4f31926c.zip |
Adding upstream version 9.0.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'routers/api/v1/notify')
-rw-r--r-- | routers/api/v1/notify/notifications.go | 77 | ||||
-rw-r--r-- | routers/api/v1/notify/repo.go | 227 | ||||
-rw-r--r-- | routers/api/v1/notify/threads.go | 118 | ||||
-rw-r--r-- | routers/api/v1/notify/user.go | 175 |
4 files changed, 597 insertions, 0 deletions
diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go new file mode 100644 index 0000000..46b3c7f --- /dev/null +++ b/routers/api/v1/notify/notifications.go @@ -0,0 +1,77 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package notify + +import ( + "net/http" + "strings" + + activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/db" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/context" +) + +// NewAvailable check if unread notifications exist +func NewAvailable(ctx *context.APIContext) { + // swagger:operation GET /notifications/new notification notifyNewAvailable + // --- + // summary: Check if unread notifications exist + // responses: + // "200": + // "$ref": "#/responses/NotificationCount" + + total, err := db.Count[activities_model.Notification](ctx, activities_model.FindNotificationOptions{ + UserID: ctx.Doer.ID, + Status: []activities_model.NotificationStatus{activities_model.NotificationStatusUnread}, + }) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "db.Count[activities_model.Notification]", err) + return + } + + ctx.JSON(http.StatusOK, api.NotificationCount{New: total}) +} + +func getFindNotificationOptions(ctx *context.APIContext) *activities_model.FindNotificationOptions { + before, since, err := context.GetQueryBeforeSince(ctx.Base) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) + return nil + } + opts := &activities_model.FindNotificationOptions{ + ListOptions: utils.GetListOptions(ctx), + UserID: ctx.Doer.ID, + UpdatedBeforeUnix: before, + UpdatedAfterUnix: since, + } + if !ctx.FormBool("all") { + statuses := ctx.FormStrings("status-types") + opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread", "pinned"}) + } + + subjectTypes := ctx.FormStrings("subject-type") + if len(subjectTypes) != 0 { + opts.Source = subjectToSource(subjectTypes) + } + + return opts +} + +func subjectToSource(value []string) (result []activities_model.NotificationSource) { + for _, v := range value { + switch strings.ToLower(v) { + case "issue": + result = append(result, activities_model.NotificationSourceIssue) + case "pull": + result = append(result, activities_model.NotificationSourcePullRequest) + case "commit": + result = append(result, activities_model.NotificationSourceCommit) + case "repository": + result = append(result, activities_model.NotificationSourceRepository) + } + } + return result +} diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go new file mode 100644 index 0000000..1744426 --- /dev/null +++ b/routers/api/v1/notify/repo.go @@ -0,0 +1,227 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package notify + +import ( + "net/http" + "strings" + "time" + + activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" +) + +func statusStringToNotificationStatus(status string) activities_model.NotificationStatus { + switch strings.ToLower(strings.TrimSpace(status)) { + case "unread": + return activities_model.NotificationStatusUnread + case "read": + return activities_model.NotificationStatusRead + case "pinned": + return activities_model.NotificationStatusPinned + default: + return 0 + } +} + +func statusStringsToNotificationStatuses(statuses, defaultStatuses []string) []activities_model.NotificationStatus { + if len(statuses) == 0 { + statuses = defaultStatuses + } + results := make([]activities_model.NotificationStatus, 0, len(statuses)) + for _, status := range statuses { + notificationStatus := statusStringToNotificationStatus(status) + if notificationStatus > 0 { + results = append(results, notificationStatus) + } + } + return results +} + +// ListRepoNotifications list users's notification threads on a specific repo +func ListRepoNotifications(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/notifications notification notifyGetRepoList + // --- + // summary: List users's notification threads on a specific repo + // consumes: + // - application/json + // 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: all + // in: query + // description: If true, show notifications marked as read. Default value is false + // type: boolean + // - name: status-types + // in: query + // description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned" + // type: array + // collectionFormat: multi + // items: + // type: string + // - name: subject-type + // in: query + // description: "filter notifications by subject type" + // type: array + // collectionFormat: multi + // items: + // type: string + // enum: [issue,pull,commit,repository] + // - name: since + // in: query + // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format + // type: string + // format: date-time + // - name: before + // in: query + // description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format + // type: string + // format: date-time + // - 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/NotificationThreadList" + opts := getFindNotificationOptions(ctx) + if ctx.Written() { + return + } + opts.RepoID = ctx.Repo.Repository.ID + + totalCount, err := db.Count[activities_model.Notification](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + nl, err := db.Find[activities_model.Notification](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + err = activities_model.NotificationList(nl).LoadAttributes(ctx) + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.SetTotalCountHeader(totalCount) + + ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl)) +} + +// ReadRepoNotifications mark notification threads as read on a specific repo +func ReadRepoNotifications(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/notifications notification notifyReadRepoList + // --- + // summary: Mark notification threads as read, pinned or unread on a specific repo + // consumes: + // - application/json + // 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: all + // in: query + // description: If true, mark all notifications on this repo. Default value is false + // type: string + // required: false + // - name: status-types + // in: query + // description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread." + // type: array + // collectionFormat: multi + // items: + // type: string + // required: false + // - name: to-status + // in: query + // description: Status to mark notifications as. Defaults to read. + // type: string + // required: false + // - name: last_read_at + // in: query + // description: Describes the last point that notifications were checked. Anything updated since this time will not be updated. + // type: string + // format: date-time + // required: false + // responses: + // "205": + // "$ref": "#/responses/NotificationThreadList" + + lastRead := int64(0) + qLastRead := ctx.FormTrim("last_read_at") + if len(qLastRead) > 0 { + tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) + if err != nil { + ctx.Error(http.StatusBadRequest, "Parse", err) + return + } + if !tmpLastRead.IsZero() { + lastRead = tmpLastRead.Unix() + } + } + + opts := &activities_model.FindNotificationOptions{ + UserID: ctx.Doer.ID, + RepoID: ctx.Repo.Repository.ID, + UpdatedBeforeUnix: lastRead, + } + + if !ctx.FormBool("all") { + statuses := ctx.FormStrings("status-types") + opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"}) + } + nl, err := db.Find[activities_model.Notification](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + targetStatus := statusStringToNotificationStatus(ctx.FormString("to-status")) + if targetStatus == 0 { + targetStatus = activities_model.NotificationStatusRead + } + + changed := make([]*structs.NotificationThread, 0, len(nl)) + + for _, n := range nl { + notif, err := activities_model.SetNotificationStatus(ctx, n.ID, ctx.Doer, targetStatus) + if err != nil { + ctx.InternalServerError(err) + return + } + _ = notif.LoadAttributes(ctx) + changed = append(changed, convert.ToNotificationThread(ctx, notif)) + } + ctx.JSON(http.StatusResetContent, changed) +} diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go new file mode 100644 index 0000000..8e12d35 --- /dev/null +++ b/routers/api/v1/notify/threads.go @@ -0,0 +1,118 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package notify + +import ( + "fmt" + "net/http" + + activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/db" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" +) + +// GetThread get notification by ID +func GetThread(ctx *context.APIContext) { + // swagger:operation GET /notifications/threads/{id} notification notifyGetThread + // --- + // summary: Get notification thread by ID + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of notification thread + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/NotificationThread" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + + n := getThread(ctx) + if n == nil { + return + } + if err := n.LoadAttributes(ctx); err != nil && !issues_model.IsErrCommentNotExist(err) { + ctx.InternalServerError(err) + return + } + + ctx.JSON(http.StatusOK, convert.ToNotificationThread(ctx, n)) +} + +// ReadThread mark notification as read by ID +func ReadThread(ctx *context.APIContext) { + // swagger:operation PATCH /notifications/threads/{id} notification notifyReadThread + // --- + // summary: Mark notification thread as read by ID + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of notification thread + // type: string + // required: true + // - name: to-status + // in: query + // description: Status to mark notifications as + // type: string + // default: read + // required: false + // responses: + // "205": + // "$ref": "#/responses/NotificationThread" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + + n := getThread(ctx) + if n == nil { + return + } + + targetStatus := statusStringToNotificationStatus(ctx.FormString("to-status")) + if targetStatus == 0 { + targetStatus = activities_model.NotificationStatusRead + } + + notif, err := activities_model.SetNotificationStatus(ctx, n.ID, ctx.Doer, targetStatus) + if err != nil { + ctx.InternalServerError(err) + return + } + if err = notif.LoadAttributes(ctx); err != nil && !issues_model.IsErrCommentNotExist(err) { + ctx.InternalServerError(err) + return + } + ctx.JSON(http.StatusResetContent, convert.ToNotificationThread(ctx, notif)) +} + +func getThread(ctx *context.APIContext) *activities_model.Notification { + n, err := activities_model.GetNotificationByID(ctx, ctx.ParamsInt64(":id")) + if err != nil { + if db.IsErrNotExist(err) { + ctx.Error(http.StatusNotFound, "GetNotificationByID", err) + } else { + ctx.InternalServerError(err) + } + return nil + } + if n.UserID != ctx.Doer.ID && !ctx.Doer.IsAdmin { + ctx.Error(http.StatusForbidden, "GetNotificationByID", fmt.Errorf("only user itself and admin are allowed to read/change this thread %d", n.ID)) + return nil + } + return n +} diff --git a/routers/api/v1/notify/user.go b/routers/api/v1/notify/user.go new file mode 100644 index 0000000..879f484 --- /dev/null +++ b/routers/api/v1/notify/user.go @@ -0,0 +1,175 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package notify + +import ( + "net/http" + "time" + + activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" +) + +// ListNotifications list users's notification threads +func ListNotifications(ctx *context.APIContext) { + // swagger:operation GET /notifications notification notifyGetList + // --- + // summary: List users's notification threads + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: all + // in: query + // description: If true, show notifications marked as read. Default value is false + // type: boolean + // - name: status-types + // in: query + // description: "Show notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread & pinned." + // type: array + // collectionFormat: multi + // items: + // type: string + // - name: subject-type + // in: query + // description: "filter notifications by subject type" + // type: array + // collectionFormat: multi + // items: + // type: string + // enum: [issue,pull,commit,repository] + // - name: since + // in: query + // description: Only show notifications updated after the given time. This is a timestamp in RFC 3339 format + // type: string + // format: date-time + // - name: before + // in: query + // description: Only show notifications updated before the given time. This is a timestamp in RFC 3339 format + // type: string + // format: date-time + // - 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/NotificationThreadList" + opts := getFindNotificationOptions(ctx) + if ctx.Written() { + return + } + + totalCount, err := db.Count[activities_model.Notification](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + nl, err := db.Find[activities_model.Notification](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + err = activities_model.NotificationList(nl).LoadAttributes(ctx) + if err != nil { + ctx.InternalServerError(err) + return + } + + ctx.SetTotalCountHeader(totalCount) + ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl)) +} + +// ReadNotifications mark notification threads as read, unread, or pinned +func ReadNotifications(ctx *context.APIContext) { + // swagger:operation PUT /notifications notification notifyReadList + // --- + // summary: Mark notification threads as read, pinned or unread + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: last_read_at + // in: query + // description: Describes the last point that notifications were checked. Anything updated since this time will not be updated. + // type: string + // format: date-time + // required: false + // - name: all + // in: query + // description: If true, mark all notifications on this repo. Default value is false + // type: string + // required: false + // - name: status-types + // in: query + // description: "Mark notifications with the provided status types. Options are: unread, read and/or pinned. Defaults to unread." + // type: array + // collectionFormat: multi + // items: + // type: string + // required: false + // - name: to-status + // in: query + // description: Status to mark notifications as, Defaults to read. + // type: string + // required: false + // responses: + // "205": + // "$ref": "#/responses/NotificationThreadList" + + lastRead := int64(0) + qLastRead := ctx.FormTrim("last_read_at") + if len(qLastRead) > 0 { + tmpLastRead, err := time.Parse(time.RFC3339, qLastRead) + if err != nil { + ctx.Error(http.StatusBadRequest, "Parse", err) + return + } + if !tmpLastRead.IsZero() { + lastRead = tmpLastRead.Unix() + } + } + opts := &activities_model.FindNotificationOptions{ + UserID: ctx.Doer.ID, + UpdatedBeforeUnix: lastRead, + } + if !ctx.FormBool("all") { + statuses := ctx.FormStrings("status-types") + opts.Status = statusStringsToNotificationStatuses(statuses, []string{"unread"}) + } + nl, err := db.Find[activities_model.Notification](ctx, opts) + if err != nil { + ctx.InternalServerError(err) + return + } + + targetStatus := statusStringToNotificationStatus(ctx.FormString("to-status")) + if targetStatus == 0 { + targetStatus = activities_model.NotificationStatusRead + } + + changed := make([]*structs.NotificationThread, 0, len(nl)) + + for _, n := range nl { + notif, err := activities_model.SetNotificationStatus(ctx, n.ID, ctx.Doer, targetStatus) + if err != nil { + ctx.InternalServerError(err) + return + } + _ = notif.LoadAttributes(ctx) + changed = append(changed, convert.ToNotificationThread(ctx, notif)) + } + + ctx.JSON(http.StatusResetContent, changed) +} |