[feature] Add support for the exclude_types[] parameter on the notifications endpoint (#784)

* Add support for the exclude_types[] parameter on the notifications endpoint

* Add swagger docs to notifications
This commit is contained in:
Blackle Morisanchetto 2022-08-31 13:20:52 -04:00 committed by GitHub
parent f01492ae48
commit ecb97f4e0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 12 deletions

View file

@ -1401,6 +1401,36 @@ definitions:
type: object type: object
x-go-name: Nodeinfo x-go-name: Nodeinfo
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
notification:
properties:
account:
$ref: '#/definitions/account'
created_at:
description: The timestamp of the notification (ISO 8601 Datetime)
type: string
x-go-name: CreatedAt
id:
description: The id of the notification in the database.
type: string
x-go-name: ID
status:
$ref: '#/definitions/status'
type:
description: |-
The type of event that resulted in the notification.
follow = Someone followed you
follow_request = Someone requested to follow you
mention = Someone mentioned you in their status
reblog = Someone boosted one of your statuses
favourite = Someone favourited one of your statuses
poll = A poll you have voted in or created has ended
status = Someone you enabled notifications for has posted a status
type: string
x-go-name: Type
title: Notification represents a notification of an event relevant to the user.
type: object
x-go-name: Notification
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
oauthToken: oauthToken:
properties: properties:
access_token: access_token:
@ -3422,6 +3452,61 @@ paths:
summary: Update a media attachment. summary: Update a media attachment.
tags: tags:
- media - media
/api/v1/notifications:
get:
description: The notifications will be returned in descending chronological
order (newest first), with sequential IDs (bigger = newer).
operationId: notifications
parameters:
- default: 20
description: Number of notifications to return.
in: query
name: limit
type: integer
- description: Array of types of notifications to exclude (follow, favourite,
reblog, mention, poll, follow_request)
in: query
items:
type: string
name: exclude_types
type: array
- description: |-
Return only notifications *OLDER* than the given max status ID.
The status with the specified ID will not be included in the response.
in: query
name: max_id
type: string
- description: |-
Return only notifications *NEWER* than the given since status ID.
The status with the specified ID will not be included in the response.
in: query
name: since_id
type: string
produces:
- application/json
responses:
"200":
description: Array of notifications.
schema:
items:
$ref: '#/definitions/notification'
type: array
"400":
description: bad request
"401":
description: unauthorized
"404":
description: not found
"406":
description: not acceptable
"500":
description: internal server error
security:
- OAuth2 Bearer:
- read:notifications
summary: Get notifications for currently authorized user.
tags:
- notifications
/api/v1/search: /api/v1/search:
get: get:
description: If statuses are in the result, they will be returned in descending description: If statuses are in the result, they will be returned in descending
@ -4341,6 +4426,7 @@ securityDefinitions:
read:accounts: grants read access to accounts read:accounts: grants read access to accounts
read:blocks: grant read access to blocks read:blocks: grant read access to blocks
read:media: grant read access to media read:media: grant read access to media
read:notifications: grants read access to notifications
read:search: grant read access to searches read:search: grant read access to searches
read:statuses: grants read access to statuses read:statuses: grants read access to statuses
read:streaming: grants read access to streaming api read:streaming: grants read access to streaming api

View file

@ -41,6 +41,7 @@
// read:statuses: grants read access to statuses // read:statuses: grants read access to statuses
// read:streaming: grants read access to streaming api // read:streaming: grants read access to streaming api
// read:user: grants read access to user-level info // read:user: grants read access to user-level info
// read:notifications: grants read access to notifications
// write: grants write access to everything // write: grants write access to everything
// write:accounts: grants write access to accounts // write:accounts: grants write access to accounts
// write:blocks: grants write access to blocks // write:blocks: grants write access to blocks

View file

@ -36,6 +36,8 @@ const (
BasePathWithID = BasePath + "/:" + IDKey BasePathWithID = BasePath + "/:" + IDKey
BasePathWithClear = BasePath + "/clear" BasePathWithClear = BasePath + "/clear"
// ExcludeTypes is an array specifying notification types to exclude
ExcludeTypesKey = "exclude_types[]"
// MaxIDKey is the url query for setting a max notification ID to return // MaxIDKey is the url query for setting a max notification ID to return
MaxIDKey = "max_id" MaxIDKey = "max_id"
// LimitKey is for specifying maximum number of notifications to return. // LimitKey is for specifying maximum number of notifications to return.

View file

@ -29,7 +29,70 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
) )
// NotificationsGETHandler serves a list of notifications to the caller, with the desired query parameters // NotificationsGETHandler swagger:operation GET /api/v1/notifications notifications
//
// Get notifications for currently authorized user.
//
// The notifications will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer).
//
// ---
// tags:
// - notifications
//
// produces:
// - application/json
//
// parameters:
// - name: limit
// type: integer
// description: Number of notifications to return.
// default: 20
// in: query
// required: false
// - name: exclude_types
// type: array
// items:
// type: string
// description: Array of types of notifications to exclude (follow, favourite, reblog, mention, poll, follow_request)
// in: query
// required: false
// - name: max_id
// type: string
// description: |-
// Return only notifications *OLDER* than the given max status ID.
// The status with the specified ID will not be included in the response.
// in: query
// required: false
// - name: since_id
// type: string
// description: |-
// Return only notifications *NEWER* than the given since status ID.
// The status with the specified ID will not be included in the response.
// in: query
// required: false
//
// security:
// - OAuth2 Bearer:
// - read:notifications
//
// responses:
// '200':
// name: notifications
// description: Array of notifications.
// schema:
// type: array
// items:
// "$ref": "#/definitions/notification"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) NotificationsGETHandler(c *gin.Context) { func (m *Module) NotificationsGETHandler(c *gin.Context) {
authed, err := oauth.Authed(c, true, true, true, true) authed, err := oauth.Authed(c, true, true, true, true)
if err != nil { if err != nil {
@ -66,7 +129,9 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {
sinceID = sinceIDString sinceID = sinceIDString
} }
resp, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, limit, maxID, sinceID) excludeTypes := c.QueryArray(ExcludeTypesKey)
resp, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, excludeTypes, limit, maxID, sinceID)
if errWithCode != nil { if errWithCode != nil {
api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
return return

View file

@ -19,6 +19,8 @@
package model package model
// Notification represents a notification of an event relevant to the user. // Notification represents a notification of an event relevant to the user.
//
// swagger:model notification
type Notification struct { type Notification struct {
// REQUIRED // REQUIRED

View file

@ -56,7 +56,7 @@ func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmo
return &dst, nil return &dst, nil
} }
func (n *notificationDB) GetNotifications(ctx context.Context, accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, db.Error) { func (n *notificationDB) GetNotifications(ctx context.Context, accountID string, excludeTypes []string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, db.Error) {
// Ensure reasonable // Ensure reasonable
if limit < 0 { if limit < 0 {
limit = 0 limit = 0
@ -78,6 +78,10 @@ func (n *notificationDB) GetNotifications(ctx context.Context, accountID string,
q = q.Where("id > ?", sinceID) q = q.Where("id > ?", sinceID)
} }
for _, excludeType := range excludeTypes {
q = q.Where("notification_type != ?", excludeType)
}
q = q. q = q.
Where("target_account_id = ?", accountID). Where("target_account_id = ?", accountID).
Order("id DESC") Order("id DESC")

View file

@ -91,7 +91,7 @@ func (suite *NotificationTestSuite) TestGetNotificationsWithSpam() {
suite.spamNotifs() suite.spamNotifs()
testAccount := suite.testAccounts["local_account_1"] testAccount := suite.testAccounts["local_account_1"]
before := time.Now() before := time.Now()
notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000") notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, []string{}, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000")
suite.NoError(err) suite.NoError(err)
timeTaken := time.Since(before) timeTaken := time.Since(before)
fmt.Printf("\n\n\n withSpam: got %d notifications in %s\n\n\n", len(notifications), timeTaken) fmt.Printf("\n\n\n withSpam: got %d notifications in %s\n\n\n", len(notifications), timeTaken)
@ -105,7 +105,7 @@ func (suite *NotificationTestSuite) TestGetNotificationsWithSpam() {
func (suite *NotificationTestSuite) TestGetNotificationsWithoutSpam() { func (suite *NotificationTestSuite) TestGetNotificationsWithoutSpam() {
testAccount := suite.testAccounts["local_account_1"] testAccount := suite.testAccounts["local_account_1"]
before := time.Now() before := time.Now()
notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000") notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, []string{}, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000")
suite.NoError(err) suite.NoError(err)
timeTaken := time.Since(before) timeTaken := time.Since(before)
fmt.Printf("\n\n\n withoutSpam: got %d notifications in %s\n\n\n", len(notifications), timeTaken) fmt.Printf("\n\n\n withoutSpam: got %d notifications in %s\n\n\n", len(notifications), timeTaken)
@ -125,7 +125,7 @@ func (suite *NotificationTestSuite) TestClearNotificationsWithSpam() {
err := suite.db.ClearNotifications(context.Background(), testAccount.ID) err := suite.db.ClearNotifications(context.Background(), testAccount.ID)
suite.NoError(err) suite.NoError(err)
notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000") notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, []string{}, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000")
suite.NoError(err) suite.NoError(err)
suite.NotNil(notifications) suite.NotNil(notifications)
suite.Empty(notifications) suite.Empty(notifications)
@ -137,7 +137,7 @@ func (suite *NotificationTestSuite) TestClearNotificationsWithTwoAccounts() {
err := suite.db.ClearNotifications(context.Background(), testAccount.ID) err := suite.db.ClearNotifications(context.Background(), testAccount.ID)
suite.NoError(err) suite.NoError(err)
notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000") notifications, err := suite.db.GetNotifications(context.Background(), testAccount.ID, []string{}, 20, "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", "00000000000000000000000000")
suite.NoError(err) suite.NoError(err)
suite.NotNil(notifications) suite.NotNil(notifications)
suite.Empty(notifications) suite.Empty(notifications)

View file

@ -29,7 +29,7 @@ type Notification interface {
// GetNotifications returns a slice of notifications that pertain to the given accountID. // GetNotifications returns a slice of notifications that pertain to the given accountID.
// //
// Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest). // Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest).
GetNotifications(ctx context.Context, accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, Error) GetNotifications(ctx context.Context, accountID string, excludeTypes []string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, Error)
// GetNotification returns one notification according to its id. // GetNotification returns one notification according to its id.
GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, Error) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, Error)
// ClearNotifications deletes every notification that pertain to the given accountID. // ClearNotifications deletes every notification that pertain to the given accountID.

View file

@ -29,8 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
) )
func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode) { func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode) {
notifs, err := p.db.GetNotifications(ctx, authed.Account.ID, limit, maxID, sinceID) notifs, err := p.db.GetNotifications(ctx, authed.Account.ID, excludeTypes, limit, maxID, sinceID)
if err != nil { if err != nil {
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }

View file

@ -33,7 +33,7 @@ type NotificationTestSuite struct {
// get a notification where someone has liked our status // get a notification where someone has liked our status
func (suite *NotificationTestSuite) TestGetNotifications() { func (suite *NotificationTestSuite) TestGetNotifications() {
receivingAccount := suite.testAccounts["local_account_1"] receivingAccount := suite.testAccounts["local_account_1"]
notifsResponse, err := suite.processor.NotificationsGet(context.Background(), suite.testAutheds["local_account_1"], 10, "", "") notifsResponse, err := suite.processor.NotificationsGet(context.Background(), suite.testAutheds["local_account_1"], []string{}, 10, "", "")
suite.NoError(err) suite.NoError(err)
suite.Len(notifsResponse.Items, 1) suite.Len(notifsResponse.Items, 1)
notif, ok := notifsResponse.Items[0].(*apimodel.Notification) notif, ok := notifsResponse.Items[0].(*apimodel.Notification)

View file

@ -154,7 +154,7 @@ type Processor interface {
MediaUpdate(ctx context.Context, authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) MediaUpdate(ctx context.Context, authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode)
// NotificationsGet // NotificationsGet
NotificationsGet(ctx context.Context, authed *oauth.Auth, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode) NotificationsGet(ctx context.Context, authed *oauth.Auth, excludeTypes []string, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode)
// NotificationsClear // NotificationsClear
NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode