summaryrefslogtreecommitdiffstats
path: root/routers/api
diff options
context:
space:
mode:
author6543 <6543@obermui.de>2020-01-09 12:56:32 +0100
committerzeripath <art27@cantab.net>2020-01-09 11:56:32 +0000
commit6baa5d7588bcf0e1fee8f4e4d77381b39b973363 (patch)
treec7fe9835cc8ead37a1a8d90555b3caeab11f027b /routers/api
parentee9ce0cfa9c003b359d2d3fba9346fd0929e88f4 (diff)
downloadgitea-6baa5d7588bcf0e1fee8f4e4d77381b39b973363.tar.gz
gitea-6baa5d7588bcf0e1fee8f4e4d77381b39b973363.zip
[API] Add notification endpoint (#9488)
* [API] Add notification endpoints * add func GetNotifications(opts FindNotificationOptions) * add func (n *Notification) APIFormat() * add func (nl NotificationList) APIFormat() * add func (n *Notification) APIURL() * add func (nl NotificationList) APIFormat() * add LoadAttributes functions (loadRepo, loadIssue, loadComment, loadUser) * add func (c *Comment) APIURL() * add func (issue *Issue) GetLastComment() * add endpoint GET /notifications * add endpoint PUT /notifications * add endpoint GET /repos/{owner}/{repo}/notifications * add endpoint PUT /repos/{owner}/{repo}/notifications * add endpoint GET /notifications/threads/{id} * add endpoint PATCH /notifications/threads/{id} * Add TEST * code format * code format
Diffstat (limited to 'routers/api')
-rw-r--r--routers/api/v1/admin/user.go4
-rw-r--r--routers/api/v1/api.go14
-rw-r--r--routers/api/v1/notify/repo.go151
-rw-r--r--routers/api/v1/notify/threads.go101
-rw-r--r--routers/api/v1/notify/user.go129
-rw-r--r--routers/api/v1/repo/pull.go2
-rw-r--r--routers/api/v1/swagger/notify.go23
7 files changed, 422 insertions, 2 deletions
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 7387037d33..ebc651516a 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -56,10 +56,10 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
// responses:
// "201":
// "$ref": "#/responses/User"
- // "403":
- // "$ref": "#/responses/forbidden"
// "400":
// "$ref": "#/responses/error"
+ // "403":
+ // "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 9f18951893..ccce00e2b2 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -70,6 +70,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/admin"
"code.gitea.io/gitea/routers/api/v1/misc"
+ "code.gitea.io/gitea/routers/api/v1/notify"
"code.gitea.io/gitea/routers/api/v1/org"
"code.gitea.io/gitea/routers/api/v1/repo"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
@@ -512,6 +513,16 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", misc.MarkdownRaw)
+ // Notifications
+ m.Group("/notifications", func() {
+ m.Combo("").
+ Get(notify.ListNotifications).
+ Put(notify.ReadNotifications)
+ m.Combo("/threads/:id").
+ Get(notify.GetThread).
+ Patch(notify.ReadThread)
+ }, reqToken())
+
// Users
m.Group("/users", func() {
m.Get("/search", user.Search)
@@ -610,6 +621,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
Delete(reqToken(), reqOwner(), repo.Delete).
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), context.RepoRef(), repo.Edit)
+ m.Combo("/notifications").
+ Get(reqToken(), notify.ListRepoNotifications).
+ Put(reqToken(), notify.ReadRepoNotifications)
m.Group("/hooks", func() {
m.Combo("").Get(repo.ListHooks).
Post(bind(api.CreateHookOption{}), repo.CreateHook)
diff --git a/routers/api/v1/notify/repo.go b/routers/api/v1/notify/repo.go
new file mode 100644
index 0000000000..b939d90f06
--- /dev/null
+++ b/routers/api/v1/notify/repo.go
@@ -0,0 +1,151 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package notify
+
+import (
+ "net/http"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+)
+
+// 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: string
+ // required: false
+ // - 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
+ // required: false
+ // - 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
+ // required: false
+ // responses:
+ // "200":
+ // "$ref": "#/responses/NotificationThreadList"
+
+ before, since, err := utils.GetQueryBeforeSince(ctx)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ opts := models.FindNotificationOptions{
+ UserID: ctx.User.ID,
+ RepoID: ctx.Repo.Repository.ID,
+ UpdatedBeforeUnix: before,
+ UpdatedAfterUnix: since,
+ }
+ qAll := strings.Trim(ctx.Query("all"), " ")
+ if qAll != "true" {
+ opts.Status = models.NotificationStatusUnread
+ }
+ nl, err := models.GetNotifications(opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ err = nl.LoadAttributes()
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, nl.APIFormat())
+}
+
+// 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 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: 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/empty"
+
+ lastRead := int64(0)
+ qLastRead := strings.Trim(ctx.Query("last_read_at"), " ")
+ if len(qLastRead) > 0 {
+ tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ if !tmpLastRead.IsZero() {
+ lastRead = tmpLastRead.Unix()
+ }
+ }
+ opts := models.FindNotificationOptions{
+ UserID: ctx.User.ID,
+ RepoID: ctx.Repo.Repository.ID,
+ UpdatedBeforeUnix: lastRead,
+ Status: models.NotificationStatusUnread,
+ }
+ nl, err := models.GetNotifications(opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ for _, n := range nl {
+ err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ ctx.Status(http.StatusResetContent)
+ }
+
+ ctx.Status(http.StatusResetContent)
+}
diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go
new file mode 100644
index 0000000000..d0119e9938
--- /dev/null
+++ b/routers/api/v1/notify/threads.go
@@ -0,0 +1,101 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package notify
+
+import (
+ "fmt"
+ "net/http"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+)
+
+// 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(); err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, n.APIFormat())
+}
+
+// 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
+ // responses:
+ // "205":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ n := getThread(ctx)
+ if n == nil {
+ return
+ }
+
+ err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ ctx.Status(http.StatusResetContent)
+}
+
+func getThread(ctx *context.APIContext) *models.Notification {
+ n, err := models.GetNotificationByID(ctx.ParamsInt64(":id"))
+ if err != nil {
+ if models.IsErrNotExist(err) {
+ ctx.Error(http.StatusNotFound, "GetNotificationByID", err)
+ } else {
+ ctx.InternalServerError(err)
+ }
+ return nil
+ }
+ if n.UserID != ctx.User.ID && !ctx.User.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 0000000000..d16e4da0e0
--- /dev/null
+++ b/routers/api/v1/notify/user.go
@@ -0,0 +1,129 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package notify
+
+import (
+ "net/http"
+ "strings"
+ "time"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+)
+
+// 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: string
+ // required: false
+ // - 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
+ // required: false
+ // - 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
+ // required: false
+ // responses:
+ // "200":
+ // "$ref": "#/responses/NotificationThreadList"
+
+ before, since, err := utils.GetQueryBeforeSince(ctx)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ opts := models.FindNotificationOptions{
+ UserID: ctx.User.ID,
+ UpdatedBeforeUnix: before,
+ UpdatedAfterUnix: since,
+ }
+ qAll := strings.Trim(ctx.Query("all"), " ")
+ if qAll != "true" {
+ opts.Status = models.NotificationStatusUnread
+ }
+ nl, err := models.GetNotifications(opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ err = nl.LoadAttributes()
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, nl.APIFormat())
+}
+
+// ReadNotifications mark notification threads as read
+func ReadNotifications(ctx *context.APIContext) {
+ // swagger:operation PUT /notifications notification notifyReadList
+ // ---
+ // summary: Mark notification threads as read
+ // 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
+ // responses:
+ // "205":
+ // "$ref": "#/responses/empty"
+
+ lastRead := int64(0)
+ qLastRead := strings.Trim(ctx.Query("last_read_at"), " ")
+ if len(qLastRead) > 0 {
+ tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ if !tmpLastRead.IsZero() {
+ lastRead = tmpLastRead.Unix()
+ }
+ }
+ opts := models.FindNotificationOptions{
+ UserID: ctx.User.ID,
+ UpdatedBeforeUnix: lastRead,
+ Status: models.NotificationStatusUnread,
+ }
+ nl, err := models.GetNotifications(opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ for _, n := range nl {
+ err := models.SetNotificationStatus(n.ID, ctx.User, models.NotificationStatusRead)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ ctx.Status(http.StatusResetContent)
+ }
+
+ ctx.Status(http.StatusResetContent)
+}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index d0551320fd..85ef419780 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -136,6 +136,8 @@ func GetPullRequest(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/PullRequest"
+ // "404":
+ // "$ref": "#/responses/notFound"
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
diff --git a/routers/api/v1/swagger/notify.go b/routers/api/v1/swagger/notify.go
new file mode 100644
index 0000000000..7d45da0e12
--- /dev/null
+++ b/routers/api/v1/swagger/notify.go
@@ -0,0 +1,23 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package swagger
+
+import (
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+// NotificationThread
+// swagger:response NotificationThread
+type swaggerNotificationThread struct {
+ // in:body
+ Body api.NotificationThread `json:"body"`
+}
+
+// NotificationThreadList
+// swagger:response NotificationThreadList
+type swaggerNotificationThreadList struct {
+ // in:body
+ Body []api.NotificationThread `json:"body"`
+}