mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-18 05:06:29 +00:00
[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:
parent
f01492ae48
commit
ecb97f4e0b
11 changed files with 172 additions and 12 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue