mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-03-14 01:48:54 +01:00
Merge branch 'main' into xvello/import-mutes
This commit is contained in:
commit
f30ab4bae4
328 changed files with 31571 additions and 115701 deletions
|
@ -36,37 +36,37 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/interaction"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/spam"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media/ffmpeg"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/metrics"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/subscriptions"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/tracing"
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/interaction"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/spam"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/httpclient"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media/ffmpeg"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/metrics"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/subscriptions"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/tracing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/web"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/webpush"
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
)
|
||||
|
||||
// Start creates and starts a gotosocial server
|
||||
|
@ -248,6 +248,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Get or create a VAPID key pair.
|
||||
if _, err := dbService.GetVAPIDKeyPair(ctx); err != nil {
|
||||
return gtserror.Newf("error getting or creating VAPID key pair: %w", err)
|
||||
}
|
||||
|
||||
// Create a Web Push notification sender.
|
||||
webPushSender := webpush.NewSender(client, state, typeConverter)
|
||||
|
||||
// Initialize both home / list timelines.
|
||||
state.Timelines.Home = timeline.NewManager(
|
||||
tlprocessor.HomeTimelineGrab(state),
|
||||
|
@ -307,6 +315,7 @@ func(context.Context, time.Time) {
|
|||
mediaManager,
|
||||
state,
|
||||
emailSender,
|
||||
webPushSender,
|
||||
visFilter,
|
||||
intFilter,
|
||||
)
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
federator := testrig.NewTestFederator(state, transportController, mediaManager)
|
||||
|
||||
emailSender := testrig.NewEmailSender("./web/template/", nil)
|
||||
webPushSender := testrig.NewWebPushMockSender()
|
||||
typeConverter := typeutils.NewConverter(state)
|
||||
filter := visibility.NewFilter(state)
|
||||
|
||||
|
@ -187,7 +188,7 @@
|
|||
return fmt.Errorf("error starting list timeline: %s", err)
|
||||
}
|
||||
|
||||
processor := testrig.NewTestProcessor(state, federator, emailSender, mediaManager)
|
||||
processor := testrig.NewTestProcessor(state, federator, emailSender, webPushSender, mediaManager)
|
||||
|
||||
// Initialize workers.
|
||||
testrig.StartWorkers(state, processor.Workers())
|
||||
|
|
|
@ -24,11 +24,11 @@ In case the rate limit is exceeded, an [HTTP 429 Too Many Requests](https://deve
|
|||
|
||||
### My rate limit keeps being exceeded! Why?
|
||||
|
||||
If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
|
||||
If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged client IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
|
||||
|
||||
This happens when your server is running inside NAT (port forwarding), or behind an HTTP proxy without the correct configuration, causing your instance to see all incoming IP addresses as the same address: namely, the IP address of your reverse proxy or gateway. This means that all incoming requests are *sharing the same rate limit*, rather than being split correctly per IP.
|
||||
|
||||
If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. If this is the case, try adding the IP address of your reverse proxy to the list of `trusted-proxies`, and restarting your instance.
|
||||
If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. See the [trusted-proxies](../configuration/trusted_proxies.md) documentation for more info on how to resolve this.
|
||||
|
||||
If you don't have an HTTP proxy, then it's likely caused by NAT. In this case you should disable rate limiting altogether.
|
||||
|
||||
|
|
|
@ -186,6 +186,10 @@ definitions:
|
|||
title: TimelineMarker contains information about a user's progress through a specific timeline.
|
||||
type: object
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
WebPushNotificationPolicy:
|
||||
title: WebPushNotificationPolicy names sets of accounts that can generate notifications.
|
||||
type: string
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
account:
|
||||
description: The modelled account can be either a remote account, or one on this instance.
|
||||
properties:
|
||||
|
@ -1946,6 +1950,8 @@ definitions:
|
|||
$ref: '#/definitions/instanceV2ConfigurationTranslation'
|
||||
urls:
|
||||
$ref: '#/definitions/instanceV2URLs'
|
||||
vapid:
|
||||
$ref: '#/definitions/instanceV2ConfigurationVAPID'
|
||||
title: Configured values and limits for this instance.
|
||||
type: object
|
||||
x-go-name: InstanceV2Configuration
|
||||
|
@ -1962,6 +1968,16 @@ definitions:
|
|||
type: object
|
||||
x-go-name: InstanceV2ConfigurationTranslation
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
instanceV2ConfigurationVAPID:
|
||||
properties:
|
||||
public_key:
|
||||
description: The instance's VAPID public key, Base64-encoded.
|
||||
type: string
|
||||
x-go-name: PublicKey
|
||||
title: InstanceV2ConfigurationVAPID holds the instance's VAPID configuration.
|
||||
type: object
|
||||
x-go-name: InstanceV2ConfigurationVAPID
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
instanceV2Contact:
|
||||
properties:
|
||||
account:
|
||||
|
@ -3381,6 +3397,139 @@ definitions:
|
|||
type: object
|
||||
x-go-name: User
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
webPushNotification:
|
||||
description: |-
|
||||
It does not contain an entire Notification, just the NotificationID and some preview information.
|
||||
It is not used in the client API directly, but is included in the API doc for decoding Web Push notifications.
|
||||
properties:
|
||||
access_token:
|
||||
description: |-
|
||||
AccessToken is the access token associated with the Web Push subscription.
|
||||
I don't know why this is sent, given that the client should know that already,
|
||||
but Feditext does use it.
|
||||
type: string
|
||||
x-go-name: AccessToken
|
||||
body:
|
||||
description: |-
|
||||
Body is a preview of the notification body,
|
||||
such as the first line of a status's CW or text,
|
||||
or the first line of an account bio.
|
||||
type: string
|
||||
x-go-name: Body
|
||||
icon:
|
||||
description: |-
|
||||
Icon is an image URL that can be displayed with the notification,
|
||||
normally the account's avatar.
|
||||
type: string
|
||||
x-go-name: Icon
|
||||
notification_id:
|
||||
description: NotificationID is the Notification.ID of the referenced Notification.
|
||||
type: string
|
||||
x-go-name: NotificationID
|
||||
notification_type:
|
||||
description: NotificationType is the Notification.Type of the referenced Notification.
|
||||
type: string
|
||||
x-go-name: NotificationType
|
||||
preferred_locale:
|
||||
description: PreferredLocale is a BCP 47 language tag for the receiving user's locale.
|
||||
type: string
|
||||
x-go-name: PreferredLocale
|
||||
title:
|
||||
description: |-
|
||||
Title is a title for the notification,
|
||||
generally describing an action taken by a user.
|
||||
type: string
|
||||
x-go-name: Title
|
||||
title: WebPushNotification represents a notification summary delivered to the client by the Web Push server.
|
||||
type: object
|
||||
x-go-name: WebPushNotification
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
webPushSubscription:
|
||||
properties:
|
||||
alerts:
|
||||
$ref: '#/definitions/webPushSubscriptionAlerts'
|
||||
endpoint:
|
||||
description: Where push alerts will be sent to.
|
||||
type: string
|
||||
x-go-name: Endpoint
|
||||
id:
|
||||
description: The id of the push subscription in the database.
|
||||
type: string
|
||||
x-go-name: ID
|
||||
policy:
|
||||
$ref: '#/definitions/WebPushNotificationPolicy'
|
||||
server_key:
|
||||
description: The streaming server's VAPID public key.
|
||||
type: string
|
||||
x-go-name: ServerKey
|
||||
standard:
|
||||
description: |-
|
||||
Whether the subscription uses RFC or pre-RFC Web Push standards.
|
||||
For GotoSocial, this is always true.
|
||||
type: boolean
|
||||
x-go-name: Standard
|
||||
title: WebPushSubscription represents a subscription to a Web Push server.
|
||||
type: object
|
||||
x-go-name: WebPushSubscription
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
webPushSubscriptionAlerts:
|
||||
properties:
|
||||
admin.report:
|
||||
description: Receive a push notification when a new report has been filed?
|
||||
type: boolean
|
||||
x-go-name: AdminReport
|
||||
admin.sign_up:
|
||||
description: Receive a push notification when a new user has signed up?
|
||||
type: boolean
|
||||
x-go-name: AdminSignup
|
||||
favourite:
|
||||
description: Receive a push notification when a status you created has been favourited by someone else?
|
||||
type: boolean
|
||||
x-go-name: Favourite
|
||||
follow:
|
||||
description: Receive a push notification when someone has followed you?
|
||||
type: boolean
|
||||
x-go-name: Follow
|
||||
follow_request:
|
||||
description: Receive a push notification when someone has requested to follow you?
|
||||
type: boolean
|
||||
x-go-name: FollowRequest
|
||||
mention:
|
||||
description: Receive a push notification when someone else has mentioned you in a status?
|
||||
type: boolean
|
||||
x-go-name: Mention
|
||||
pending.favourite:
|
||||
description: Receive a push notification when a fave is pending?
|
||||
type: boolean
|
||||
x-go-name: PendingFavourite
|
||||
pending.reblog:
|
||||
description: Receive a push notification when a boost is pending?
|
||||
type: boolean
|
||||
x-go-name: PendingReblog
|
||||
pending.reply:
|
||||
description: Receive a push notification when a reply is pending?
|
||||
type: boolean
|
||||
x-go-name: PendingReply
|
||||
poll:
|
||||
description: Receive a push notification when a poll you voted in or created has ended?
|
||||
type: boolean
|
||||
x-go-name: Poll
|
||||
reblog:
|
||||
description: Receive a push notification when a status you created has been boosted by someone else?
|
||||
type: boolean
|
||||
x-go-name: Reblog
|
||||
status:
|
||||
description: Receive a push notification when a subscribed account posts a status?
|
||||
type: boolean
|
||||
x-go-name: Status
|
||||
update:
|
||||
description: Receive a push notification when a status you interacted with has been edited?
|
||||
type: boolean
|
||||
x-go-name: Update
|
||||
title: WebPushSubscriptionAlerts represents the specific events that this Web Push subscription will receive.
|
||||
type: object
|
||||
x-go-name: WebPushSubscriptionAlerts
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
wellKnownResponse:
|
||||
description: See https://webfinger.net/
|
||||
properties:
|
||||
|
@ -7805,14 +7954,14 @@ paths:
|
|||
The contexts in which the filter should be applied.
|
||||
|
||||
Sample: home, public
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
in: formData
|
||||
items:
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
type: string
|
||||
minItems: 1
|
||||
name: context[]
|
||||
|
@ -7959,14 +8108,14 @@ paths:
|
|||
The contexts in which the filter should be applied.
|
||||
|
||||
Sample: home, public
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
in: formData
|
||||
items:
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
type: string
|
||||
minItems: 1
|
||||
name: context[]
|
||||
|
@ -9642,6 +9791,259 @@ paths:
|
|||
summary: Delete the authenticated account's header.
|
||||
tags:
|
||||
- accounts
|
||||
/api/v1/push/subscription:
|
||||
delete:
|
||||
description: If there is no subscription, returns successfully anyway.
|
||||
operationId: pushSubscriptionDelete
|
||||
responses:
|
||||
"200":
|
||||
description: Push subscription deleted, or did not exist.
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- push
|
||||
summary: Delete the Web Push subscription associated with the current auth token.
|
||||
tags:
|
||||
- push
|
||||
get:
|
||||
operationId: pushSubscriptionGet
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Web Push subscription for current access token.
|
||||
schema:
|
||||
$ref: '#/definitions/webPushSubscription'
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"404":
|
||||
description: This access token doesn't have an associated subscription.
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- push
|
||||
summary: Get the push subscription for the current access token.
|
||||
tags:
|
||||
- push
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
- application/x-www-form-urlencoded
|
||||
operationId: pushSubscriptionPost
|
||||
parameters:
|
||||
- description: The URL to which Web Push notifications will be sent.
|
||||
in: formData
|
||||
minLength: 1
|
||||
name: subscription[endpoint]
|
||||
required: true
|
||||
type: string
|
||||
- description: The auth secret, a Base64 encoded string of 16 bytes of random data.
|
||||
in: formData
|
||||
minLength: 1
|
||||
name: subscription[keys][auth]
|
||||
required: true
|
||||
type: string
|
||||
- description: The user agent public key, a Base64 encoded string of a public key from an ECDH keypair using the prime256v1 curve.
|
||||
in: formData
|
||||
minLength: 1
|
||||
name: subscription[keys][p256dh]
|
||||
required: true
|
||||
type: string
|
||||
- default: false
|
||||
description: Receive a push notification when someone has followed you?
|
||||
in: formData
|
||||
name: data[alerts][follow]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when someone has requested to follow you?
|
||||
in: formData
|
||||
name: data[alerts][follow_request]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a status you created has been favourited by someone else?
|
||||
in: formData
|
||||
name: data[alerts][favourite]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when someone else has mentioned you in a status?
|
||||
in: formData
|
||||
name: data[alerts][mention]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a status you created has been boosted by someone else?
|
||||
in: formData
|
||||
name: data[alerts][reblog]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a poll you voted in or created has ended?
|
||||
in: formData
|
||||
name: data[alerts][poll]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a subscribed account posts a status?
|
||||
in: formData
|
||||
name: data[alerts][status]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a status you interacted with has been edited?
|
||||
in: formData
|
||||
name: data[alerts][update]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a new user has signed up?
|
||||
in: formData
|
||||
name: data[alerts][admin.sign_up]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a new report has been filed?
|
||||
in: formData
|
||||
name: data[alerts][admin.report]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a fave is pending?
|
||||
in: formData
|
||||
name: data[alerts][pending.favourite]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a reply is pending?
|
||||
in: formData
|
||||
name: data[alerts][pending.reply]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a boost is pending?
|
||||
in: formData
|
||||
name: data[alerts][pending.reblog]
|
||||
type: boolean
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Web Push subscription for current access token.
|
||||
schema:
|
||||
$ref: '#/definitions/webPushSubscription'
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"404":
|
||||
description: not found
|
||||
"406":
|
||||
description: not acceptable
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- push
|
||||
summary: Create a new Web Push subscription for the current access token, or replace the existing one.
|
||||
tags:
|
||||
- push
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
- application/x-www-form-urlencoded
|
||||
description: Only which notifications you receive can be updated.
|
||||
operationId: pushSubscriptionPut
|
||||
parameters:
|
||||
- default: false
|
||||
description: Receive a push notification when someone has followed you?
|
||||
in: formData
|
||||
name: data[alerts][follow]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when someone has requested to follow you?
|
||||
in: formData
|
||||
name: data[alerts][follow_request]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a status you created has been favourited by someone else?
|
||||
in: formData
|
||||
name: data[alerts][favourite]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when someone else has mentioned you in a status?
|
||||
in: formData
|
||||
name: data[alerts][mention]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a status you created has been boosted by someone else?
|
||||
in: formData
|
||||
name: data[alerts][reblog]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a poll you voted in or created has ended?
|
||||
in: formData
|
||||
name: data[alerts][poll]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a subscribed account posts a status?
|
||||
in: formData
|
||||
name: data[alerts][status]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a status you interacted with has been edited?
|
||||
in: formData
|
||||
name: data[alerts][update]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a new user has signed up?
|
||||
in: formData
|
||||
name: data[alerts][admin.sign_up]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a new report has been filed?
|
||||
in: formData
|
||||
name: data[alerts][admin.report]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a fave is pending?
|
||||
in: formData
|
||||
name: data[alerts][pending.favourite]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a reply is pending?
|
||||
in: formData
|
||||
name: data[alerts][pending.reply]
|
||||
type: boolean
|
||||
- default: false
|
||||
description: Receive a push notification when a boost is pending?
|
||||
in: formData
|
||||
name: data[alerts][pending.reblog]
|
||||
type: boolean
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Web Push subscription for current access token.
|
||||
schema:
|
||||
$ref: '#/definitions/webPushSubscription'
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"404":
|
||||
description: This access token doesn't have an associated subscription.
|
||||
"406":
|
||||
description: not acceptable
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- push
|
||||
summary: Update the Web Push subscription for the current access token.
|
||||
tags:
|
||||
- push
|
||||
/api/v1/reports:
|
||||
get:
|
||||
description: |-
|
||||
|
@ -11433,14 +11835,14 @@ paths:
|
|||
The contexts in which the filter should be applied.
|
||||
|
||||
Sample: home, public
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
in: formData
|
||||
items:
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
type: string
|
||||
minItems: 1
|
||||
name: context[]
|
||||
|
@ -11627,14 +12029,14 @@ paths:
|
|||
The contexts in which the filter should be applied.
|
||||
|
||||
Sample: home, public
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
in: formData
|
||||
items:
|
||||
enum:
|
||||
- home
|
||||
- notifications
|
||||
- public
|
||||
- thread
|
||||
- account
|
||||
type: string
|
||||
minItems: 1
|
||||
name: context[]
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# General
|
||||
|
||||
The top-level configuration for GoToSocial, including basic things like host, port, bind address and transport protocol.
|
||||
|
||||
The only things you *really* need to set here are `host`, which should be the hostname where your instance is reachable, and probably `port`.
|
||||
The top-level configuration for GoToSocial, including basic things like host, port, bind address, and trusted-proxies.
|
||||
|
||||
## Settings
|
||||
|
||||
|
|
79
docs/configuration/trusted_proxies.md
Normal file
79
docs/configuration/trusted_proxies.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Trusted Proxies
|
||||
|
||||
To correctly enforce [rate limiting](../api/ratelimiting.md), GoToSocial relies on the concept of "trusted proxies" in order to accurately determine the IP address of clients accessing your server.
|
||||
|
||||
A "trusted proxy" is an intermediate network hop that GoToSocial can be instructed to trust to provide a correct client IP address.
|
||||
|
||||
For example, if you are running in a reverse proxy configuration with Docker + Nginx, then the Docker network address of Nginx should be configured as a trusted proxy, since all traffic from the wider internet will come into GoToSocial via Nginx.
|
||||
|
||||
Without setting `trusted-proxies` correctly, GoToSocial will see all incoming client IP addresses as the same address, which leads to rate limiting issues, since GoToSocial uses client IP addresses to bucket rate limits.
|
||||
|
||||
## tl;dr: How to set `trusted-proxies` correctly
|
||||
|
||||
If your `trusted-proxies` setting is not correctly configured, you may see the following warning on the web view of your instance (v0.18.0 and above):
|
||||
|
||||
> Warning! It looks like trusted-proxies is not set correctly in this instance's configuration. This may cause rate-limiting issues and, by extension, federation issues.
|
||||
>
|
||||
> If you are the instance admin, you should fix this by adding `SUGGESTED_IP_RANGE` to your trusted-proxies.
|
||||
|
||||
To resolve this, copy the IP range in the message, and edit your `config.yaml` file to add the IP range to your `trusted-proxies`.
|
||||
|
||||
!!! tip "You may be getting rate limited even if you don't see the above warning!"
|
||||
If you're on a version of GoToSocial below v0.18.0, or you're running behind a CDN such as Cloudflare (not recommended), you won't see a warning message. Instead, you'll see in your GoToSocial logs that all client IPs are the same address. In this case, take the recurring client IP value as `SUGGESTED_IP_RANGE`.
|
||||
|
||||
In this example, we assume `SUGGESTED_IP_RANGE` to be `172.17.0.1/16` (the default Docker bridge network subnet).
|
||||
|
||||
Before (default config):
|
||||
|
||||
```yaml
|
||||
trusted-proxies:
|
||||
- "127.0.0.1/32"
|
||||
- "::1"
|
||||
```
|
||||
|
||||
After (new config):
|
||||
|
||||
```yaml
|
||||
trusted-proxies:
|
||||
- "172.17.0.1/16"
|
||||
- "127.0.0.1/32"
|
||||
- "::1"
|
||||
```
|
||||
|
||||
If you are using [environment variables](../configuration/index.md#environment-variables) to configure your instance, you can configure `trusted-proxies` by setting the environment variable `GTS_TRUSTED_PROXIES` to a comma-separated list of IP ranges, like so:
|
||||
|
||||
```env
|
||||
GTS_TRUSTED_PROXIES="172.17.0.1/16,127.0.0.1/32,::1"
|
||||
```
|
||||
|
||||
If you are using docker compose, your docker-compose.yaml file should look something like this after the change (note that yaml uses `: ` and not `=`):
|
||||
|
||||
```yaml
|
||||
################################
|
||||
# BLAH BLAH OTHER CONFIG STUFF #
|
||||
################################
|
||||
environment:
|
||||
############################
|
||||
# BLAH BLAH OTHER ENV VARS #
|
||||
############################
|
||||
## For reverse proxy setups:
|
||||
GTS_TRUSTED_PROXIES: "172.17.0.1/16,127.0.0.1/32,::1"
|
||||
################################
|
||||
# BLAH BLAH OTHER CONFIG STUFF #
|
||||
################################
|
||||
```
|
||||
|
||||
Once you have made the necessary configuration changes, **restart your instance** and refresh the home page.
|
||||
|
||||
If the message is gone, then the problem is resolved!
|
||||
|
||||
If you still see the warning message but with a different suggested IP range to add to `trusted-proxies`, then follow the same steps as above again, including the new suggested IP range in your config in addition to the one you just added.
|
||||
|
||||
!!! tip "Cloudflare IP Addresses"
|
||||
If you are running with a CDN/proxy such as Cloudflare in front of your GoToSocial instance (not recommended), then you may need to add one or more of the Cloudflare IP addresses to your `trusted-proxies` in order to have rate limiting work properly. You can find a list of Cloudflare IP addresses here: https://www.cloudflare.com/ips/
|
||||
|
||||
## I can't seem to get `trusted-proxies` configured properly, can I just disable the warning?
|
||||
|
||||
There are some situations where it's not practically possible to get `trusted-proxies` configured correctly to detect the real client IP of incoming requests For example, if you're running GoToSocial behind a home internet router that cannot inject an `X-Forwarded-For` header, then your suggested entry to add to `trusted-proxies` will look something like `192.168.x.x`, but adding this to `trusted-proxies` won't resolve the issue.
|
||||
|
||||
If you've tried everything, then you can disable the warning message by just turning off rate limiting entirely, ie., by setting `advanced-rate-limit-requests` to 0 in your config.yaml, or setting the environment variable `GTS_ADVANCED_RATE_LIMIT_REQUESTS` to 0. Don't forget to **restart your instance** after changing this setting.
|
|
@ -1,5 +1,13 @@
|
|||
# Actors and Actor Properties
|
||||
|
||||
## `Service` vs `Person` actors
|
||||
|
||||
GoToSocial serves most accounts as the ActivityStreams `Person` type described [here](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person).
|
||||
|
||||
Accounts that users have selected to mark as bot accounts, however, will use the `Service` type described [here](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service).
|
||||
|
||||
This type distinction can be used by remote servers to distinguish between bot accounts and "regular" user accounts.
|
||||
|
||||
## Inbox
|
||||
|
||||
GoToSocial implements Inboxes for Actors following the ActivityPub specification [here](https://www.w3.org/TR/activitypub/#inbox).
|
||||
|
|
|
@ -41,3 +41,7 @@ We have guides available for the following servers:
|
|||
When using a reverse-proxy, special care must be taken to allow WebSockets to work too. This is necessary as many client applications use WebSockets to stream your timeline. WebSockets is not used as part of federation.
|
||||
|
||||
Make sure you read the [WebSocket](websocket.md) documentation and configure your reverse proxy accordingly.
|
||||
|
||||
## Trusted Proxies
|
||||
|
||||
When using a reverse-proxy, you may run into issues with rate limiting and `trusted-proxies`. Check the [trusted proxies](../../configuration/trusted_proxies.md) documentation if you have any problems.
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
version: "3.3"
|
||||
|
||||
services:
|
||||
gotosocial:
|
||||
image: superseriousbusiness/gotosocial:latest
|
||||
|
@ -24,7 +22,7 @@ services:
|
|||
# Wazero compilation cache will be stored.
|
||||
GTS_WAZERO_COMPILATION_CACHE: /gotosocial/.cache
|
||||
## For reverse proxy setups:
|
||||
# GTS_TRUSTED_PROXIES: "172.x.x.x"
|
||||
GTS_TRUSTED_PROXIES: "172.18.0.1/16"
|
||||
## Set the timezone of your server:
|
||||
#TZ: UTC
|
||||
ports:
|
||||
|
@ -47,3 +45,6 @@ networks:
|
|||
gotosocial:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: "172.18.0.0/16"
|
||||
gateway: "172.18.0.1"
|
||||
|
|
38
go.mod
38
go.mod
|
@ -44,10 +44,11 @@ require (
|
|||
codeberg.org/superseriousbusiness/exif-terminator v0.9.1
|
||||
github.com/DmitriyVTitov/size v1.5.0
|
||||
github.com/KimMachineGun/automemlimit v0.6.1
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/gin-contrib/cors v1.7.3
|
||||
github.com/gin-contrib/gzip v1.1.0
|
||||
github.com/gin-contrib/gzip v1.2.2
|
||||
github.com/gin-contrib/sessions v1.0.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-playground/form/v4 v4.2.1
|
||||
|
@ -59,12 +60,13 @@ require (
|
|||
github.com/jackc/pgx/v5 v5.7.2
|
||||
github.com/k3a/html2text v1.2.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/miekg/dns v1.1.63
|
||||
github.com/minio/minio-go/v7 v7.0.81
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/ncruces/go-sqlite3 v0.22.0
|
||||
github.com/oklog/ulid v1.3.1
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
|
@ -76,20 +78,20 @@ require (
|
|||
github.com/tetratelabs/wazero v1.8.2
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
github.com/uptrace/bun v1.2.8
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.8
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.8
|
||||
github.com/uptrace/bun v1.2.9
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.9
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.9
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.9
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
go.opentelemetry.io/otel v1.33.0
|
||||
go.opentelemetry.io/otel v1.34.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.51.0
|
||||
go.opentelemetry.io/otel/metric v1.33.0
|
||||
go.opentelemetry.io/otel/metric v1.34.0
|
||||
go.opentelemetry.io/otel/sdk v1.32.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0
|
||||
go.opentelemetry.io/otel/trace v1.33.0
|
||||
go.opentelemetry.io/otel/trace v1.34.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/image v0.23.0
|
||||
|
@ -112,13 +114,12 @@ require (
|
|||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.7 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cilium/ebpf v0.9.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
|
@ -131,8 +132,8 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
|
@ -152,11 +153,12 @@ require (
|
|||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||
github.com/goccy/go-json v0.10.4 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
|
@ -200,7 +202,7 @@ require (
|
|||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.59.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0 // indirect
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect
|
||||
|
@ -228,7 +230,7 @@ require (
|
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.12.0 // indirect
|
||||
golang.org/x/arch v0.13.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
|
@ -237,7 +239,7 @@ require (
|
|||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/grpc v1.66.1 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
google.golang.org/protobuf v1.36.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
|
|
103
go.sum
generated
103
go.sum
generated
|
@ -88,6 +88,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
|
|||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0 h1:ocnzNKWN23T9nvHi6IfyrQjkIc0oJWv1B1pULsf9i3s=
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0/go.mod h1:XSq8pKX11vNV8MJEMwjrlTkxhAj1zKfxmyhdV7Pd6UA=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
|
@ -101,11 +103,11 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do=
|
||||
github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8=
|
||||
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
|
||||
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
|
||||
github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o=
|
||||
github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
@ -119,7 +121,6 @@ github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnx
|
|||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
|
||||
|
@ -173,18 +174,18 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
|||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
|
||||
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
|
||||
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
|
||||
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
|
||||
github.com/gin-contrib/gzip v1.1.0 h1:kVw7Nr9M+Z6Ch4qo7aGMbiqxDeyQFru+07MgAcUF62M=
|
||||
github.com/gin-contrib/gzip v1.1.0/go.mod h1:iHJXCup4CWiKyPUEl+GwkHjchl+YyYuMKbOCiXujPIA=
|
||||
github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
|
||||
github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
|
||||
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
|
||||
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
|
@ -236,8 +237,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
|
@ -250,6 +251,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
|||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
|
@ -406,8 +409,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA=
|
||||
|
@ -468,12 +471,14 @@ github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJ
|
|||
github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM=
|
||||
|
@ -513,6 +518,7 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
@ -522,6 +528,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
|
@ -579,14 +586,14 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
|||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA=
|
||||
github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI=
|
||||
github.com/uptrace/bun v1.2.8 h1:HEiLvy9wc7ehU5S02+O6NdV5BLz48lL4REPhTkMX3Dg=
|
||||
github.com/uptrace/bun v1.2.8/go.mod h1:JBq0uBKsKqNT0Ccce1IAFZY337Wkf08c6F6qlmfOHE8=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.8 h1:9n3qVh6yc+u7F3lpXzsWrAFJG1yLHUC2thjCCVEDpM8=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.8/go.mod h1:plksD43MjAlPGYLD9/SzsLUpGH5poXE9IB1+ka/sEzE=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8 h1:Huqw7YhLFTbocbSv8NETYYXqKtwLa6XsciCWtjzWSWU=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8/go.mod h1:ni7h2uwIc5zPhxgmCMTEbefONc4XsVr/ATfz1Q7d3CE=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.8 h1:mu98xQ2EcmkeNGT+YjVtMludtZNHfhfHqhrS77mk4YM=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.8/go.mod h1:NSjzSfYdDg0WSiY54pFp4ykGoGUmbc/xYQ7AsdyslHQ=
|
||||
github.com/uptrace/bun v1.2.9 h1:OOt2DlIcRUMSZPr6iXDFg/LaQd59kOxbAjpIVHddKRs=
|
||||
github.com/uptrace/bun v1.2.9/go.mod h1:r2ZaaGs9Ru5bpGTr8GQfp8jp+TlCav9grYCPOu2CJSg=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.9 h1:caf5uFbOGiXvadV6pA5gn87k0awFFxL1kuuY3SpxnWk=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.9/go.mod h1:m7L9JtOp/Lt8HccET70ULxplMweE/u0S9lNUSxz2duo=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.9 h1:HLzGWXBh07sT8zhVPy6veYbbGrAtYq0KzyRHXBj+GjA=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.9/go.mod h1:dUR+ecoCWA0FIa9vhQVRnGtYYPpuCLJoEEtX9E1aiBU=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.9 h1:BGGrBga+iVL78SGiMpLt2N9MAKvrG3f8wLk8zCLwFJg=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.9/go.mod h1:6dVl5Ko6xOhuoqUPWHpfFrntBDwmOnq0OMiR/SGwAC8=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
|
@ -657,8 +664,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
|
||||
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -666,6 +673,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -703,6 +714,10 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -737,6 +752,11 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -756,6 +776,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -796,12 +820,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -811,6 +848,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -858,6 +900,9 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -943,8 +988,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
|
||||
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -17,6 +17,22 @@
|
|||
|
||||
package ap
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/superseriousbusiness/activity/pub"
|
||||
)
|
||||
|
||||
// PublicURI returns a fresh copy of the *url.URL version of the
|
||||
// magic ActivityPub URI https://www.w3.org/ns/activitystreams#Public
|
||||
func PublicURI() *url.URL {
|
||||
publicURI, err := url.Parse(pub.PublicActivityPubIRI)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return publicURI
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/activitystreams-vocabulary
|
||||
const (
|
||||
ActivityAccept = "Accept" // ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"io"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/activity/pub"
|
||||
"github.com/superseriousbusiness/activity/streams"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
|
@ -111,7 +110,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
|
|||
|
||||
// Anyone can like.
|
||||
canLikeAlwaysProp := streams.NewGoToSocialAlwaysProperty()
|
||||
canLikeAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
canLikeAlwaysProp.AppendIRI(ap.PublicURI())
|
||||
canLike.SetGoToSocialAlways(canLikeAlwaysProp)
|
||||
|
||||
// Empty approvalRequired.
|
||||
|
@ -128,7 +127,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
|
|||
|
||||
// Anyone can reply.
|
||||
canReplyAlwaysProp := streams.NewGoToSocialAlwaysProperty()
|
||||
canReplyAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
canReplyAlwaysProp.AppendIRI(ap.PublicURI())
|
||||
canReply.SetGoToSocialAlways(canReplyAlwaysProp)
|
||||
|
||||
// Set empty approvalRequired.
|
||||
|
@ -151,7 +150,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
|
|||
|
||||
// Public requires approval to announce.
|
||||
canAnnounceApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
|
||||
canAnnounceApprovalRequiredProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
canAnnounceApprovalRequiredProp.AppendIRI(ap.PublicURI())
|
||||
canAnnounce.SetGoToSocialApprovalRequired(canAnnounceApprovalRequiredProp)
|
||||
|
||||
// Set canAnnounce on the policy.
|
||||
|
@ -266,7 +265,7 @@ func addressable1() ap.Addressable {
|
|||
note := streams.NewActivityStreamsNote()
|
||||
|
||||
toProp := streams.NewActivityStreamsToProperty()
|
||||
toProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
toProp.AppendIRI(ap.PublicURI())
|
||||
|
||||
note.SetActivityStreamsTo(toProp)
|
||||
|
||||
|
@ -288,7 +287,7 @@ func addressable2() ap.Addressable {
|
|||
note.SetActivityStreamsTo(toProp)
|
||||
|
||||
ccProp := streams.NewActivityStreamsCcProperty()
|
||||
ccProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
ccProp.AppendIRI(ap.PublicURI())
|
||||
|
||||
note.SetActivityStreamsCc(ccProp)
|
||||
|
||||
|
|
|
@ -188,6 +188,7 @@ type Accountable interface {
|
|||
WithTag
|
||||
WithPublished
|
||||
WithUpdated
|
||||
WithImage
|
||||
}
|
||||
|
||||
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
||||
|
@ -439,6 +440,7 @@ type WithValue interface {
|
|||
// WithImage represents an activity with ActivityStreamsImageProperty
|
||||
type WithImage interface {
|
||||
GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
|
||||
SetActivityStreamsImage(vocab.ActivityStreamsImageProperty)
|
||||
}
|
||||
|
||||
// WithSummary represents an activity with ActivityStreamsSummaryProperty
|
||||
|
|
|
@ -88,7 +88,13 @@ func (suite *EmojiGetTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.emojiModule = emoji.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -177,38 +177,6 @@ func (suite *InboxPostTestSuite) newUndo(
|
|||
return undo
|
||||
}
|
||||
|
||||
func (suite *InboxPostTestSuite) newUpdatePerson(person vocab.ActivityStreamsPerson, cc string, updateIRI string) vocab.ActivityStreamsUpdate {
|
||||
// create an update
|
||||
update := streams.NewActivityStreamsUpdate()
|
||||
|
||||
// set the appropriate actor on it
|
||||
updateActor := streams.NewActivityStreamsActorProperty()
|
||||
updateActor.AppendIRI(person.GetJSONLDId().Get())
|
||||
update.SetActivityStreamsActor(updateActor)
|
||||
|
||||
// Set the person as the 'object' property.
|
||||
updateObject := streams.NewActivityStreamsObjectProperty()
|
||||
updateObject.AppendActivityStreamsPerson(person)
|
||||
update.SetActivityStreamsObject(updateObject)
|
||||
|
||||
// Set the To of the update as public
|
||||
updateTo := streams.NewActivityStreamsToProperty()
|
||||
updateTo.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
update.SetActivityStreamsTo(updateTo)
|
||||
|
||||
// set the cc of the update to the receivingAccount
|
||||
updateCC := streams.NewActivityStreamsCcProperty()
|
||||
updateCC.AppendIRI(testrig.URLMustParse(cc))
|
||||
update.SetActivityStreamsCc(updateCC)
|
||||
|
||||
// set some random-ass ID for the activity
|
||||
updateID := streams.NewJSONLDIdProperty()
|
||||
updateID.SetIRI(testrig.URLMustParse(updateIRI))
|
||||
update.SetJSONLDId(updateID)
|
||||
|
||||
return update
|
||||
}
|
||||
|
||||
func (suite *InboxPostTestSuite) newDelete(actorIRI string, objectIRI string, deleteIRI string) vocab.ActivityStreamsDelete {
|
||||
// create a delete
|
||||
delete := streams.NewActivityStreamsDelete()
|
||||
|
@ -225,7 +193,7 @@ func (suite *InboxPostTestSuite) newDelete(actorIRI string, objectIRI string, de
|
|||
|
||||
// Set the To of the delete as public
|
||||
deleteTo := streams.NewActivityStreamsToProperty()
|
||||
deleteTo.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
deleteTo.AppendIRI(ap.PublicURI())
|
||||
delete.SetActivityStreamsTo(deleteTo)
|
||||
|
||||
// set some random-ass ID for the activity
|
||||
|
@ -329,7 +297,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
|||
var (
|
||||
requestingAccount = new(gtsmodel.Account)
|
||||
targetAccount = suite.testAccounts["local_account_1"]
|
||||
activityID = "http://fossbros-anonymous.io/72cc96a3-f742-4daf-b9f5-3407667260c5"
|
||||
updatedDisplayName = "updated display name!"
|
||||
)
|
||||
|
||||
|
@ -348,11 +315,19 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
|
|||
requestingAccount.Emojis = []*gtsmodel.Emoji{testEmoji}
|
||||
|
||||
// Create an update from the account.
|
||||
asAccount, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
|
||||
accountable, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
update := suite.newUpdatePerson(asAccount, targetAccount.URI, activityID)
|
||||
update, err := suite.tc.WrapAccountableInUpdate(accountable)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Set the ID to something from fossbros anonymous.
|
||||
idProp := streams.NewJSONLDIdProperty()
|
||||
idProp.SetIRI(testrig.URLMustParse("https://fossbros-anonymous.io/updates/waaaaaaaaaaaaaaaaa"))
|
||||
update.SetJSONLDId(idProp)
|
||||
|
||||
// Update.
|
||||
suite.inboxPost(
|
||||
|
@ -540,17 +515,20 @@ func (suite *InboxPostTestSuite) TestPostFromBlockedAccount() {
|
|||
var (
|
||||
requestingAccount = suite.testAccounts["remote_account_1"]
|
||||
targetAccount = suite.testAccounts["local_account_2"]
|
||||
activityID = requestingAccount.URI + "/some-new-activity/01FG9C441MCTW3R2W117V2PQK3"
|
||||
)
|
||||
|
||||
person, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
|
||||
// Create an update from the account.
|
||||
accountable, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
update, err := suite.tc.WrapAccountableInUpdate(accountable)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Post an update from foss satan to turtle, who blocks him.
|
||||
update := suite.newUpdatePerson(person, targetAccount.URI, activityID)
|
||||
|
||||
// Post an update from foss satan
|
||||
// to turtle, who blocks him.
|
||||
suite.inboxPost(
|
||||
update,
|
||||
requestingAccount,
|
||||
|
|
|
@ -100,7 +100,13 @@ func (suite *UserStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
testrig.StartWorkers(&suite.state, suite.processor.Workers())
|
||||
|
||||
suite.userModule = users.New(suite.processor)
|
||||
|
|
|
@ -91,7 +91,13 @@ func (suite *AuthStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.authModule = auth.New(suite.db, suite.processor, suite.idp)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/client/notifications"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/polls"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/preferences"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/push"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/reports"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/search"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"
|
||||
|
@ -91,6 +92,7 @@ type Client struct {
|
|||
notifications *notifications.Module // api/v1/notifications
|
||||
polls *polls.Module // api/v1/polls
|
||||
preferences *preferences.Module // api/v1/preferences
|
||||
push *push.Module // api/v1/push
|
||||
reports *reports.Module // api/v1/reports
|
||||
search *search.Module // api/v1/search, api/v2/search
|
||||
statuses *statuses.Module // api/v1/statuses
|
||||
|
@ -143,6 +145,7 @@ func (c *Client) Route(r *router.Router, m ...gin.HandlerFunc) {
|
|||
c.notifications.Route(h)
|
||||
c.polls.Route(h)
|
||||
c.preferences.Route(h)
|
||||
c.push.Route(h)
|
||||
c.reports.Route(h)
|
||||
c.search.Route(h)
|
||||
c.statuses.Route(h)
|
||||
|
@ -183,6 +186,7 @@ func NewClient(state *state.State, p *processing.Processor) *Client {
|
|||
notifications: notifications.New(p),
|
||||
polls: polls.New(p),
|
||||
preferences: preferences.New(p),
|
||||
push: push.New(p),
|
||||
reports: reports.New(p),
|
||||
search: search.New(p),
|
||||
statuses: statuses.New(p),
|
||||
|
|
|
@ -100,7 +100,13 @@ func (suite *AccountStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.accountsModule = accounts.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -106,7 +106,13 @@ func (suite *AdminStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.adminModule = admin.New(&suite.state, suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -114,7 +114,13 @@ func (suite *BookmarkTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.statusModule = statuses.New(suite.processor)
|
||||
suite.bookmarkModule = bookmarks.New(suite.processor)
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ func (suite *ExportsTestSuite) SetupTest() {
|
|||
&suite.state,
|
||||
federator,
|
||||
testrig.NewEmailSender("../../../../web/template/", nil),
|
||||
testrig.NewNoopWebPushSender(),
|
||||
mediaManager,
|
||||
)
|
||||
|
||||
|
|
|
@ -98,7 +98,13 @@ func (suite *FavouritesStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.favModule = favourites.New(suite.processor)
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,13 @@ func (suite *FiltersTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.filtersModule = filtersV1.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
|
|
|
@ -63,16 +63,16 @@
|
|||
// The contexts in which the filter should be applied.
|
||||
//
|
||||
// Sample: home, public
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// type: array
|
||||
// items:
|
||||
// type:
|
||||
// string
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// collectionFormat: multi
|
||||
// minItems: 1
|
||||
// uniqueItems: true
|
||||
|
|
|
@ -69,16 +69,16 @@
|
|||
// The contexts in which the filter should be applied.
|
||||
//
|
||||
// Sample: home, public
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// type: array
|
||||
// items:
|
||||
// type:
|
||||
// string
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// collectionFormat: multi
|
||||
// minItems: 1
|
||||
// uniqueItems: true
|
||||
|
|
|
@ -103,9 +103,14 @@ func (suite *FiltersTestSuite) SetupTest() {
|
|||
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.filtersModule = filtersV2.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
|
|
|
@ -65,16 +65,16 @@
|
|||
// The contexts in which the filter should be applied.
|
||||
//
|
||||
// Sample: home, public
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// type: array
|
||||
// items:
|
||||
// type:
|
||||
// string
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// collectionFormat: multi
|
||||
// minItems: 1
|
||||
// uniqueItems: true
|
||||
|
|
|
@ -98,16 +98,16 @@
|
|||
// The contexts in which the filter should be applied.
|
||||
//
|
||||
// Sample: home, public
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// type: array
|
||||
// items:
|
||||
// type:
|
||||
// string
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// collectionFormat: multi
|
||||
// minItems: 1
|
||||
// uniqueItems: true
|
||||
|
|
|
@ -88,7 +88,13 @@ func (suite *FollowedTagsTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.followedTagsModule = followedtags.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
|
|
|
@ -96,7 +96,13 @@ func (suite *FollowRequestStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.followRequestModule = followrequests.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -93,6 +93,7 @@ func (suite *ImportTestSuite) SetupTest() {
|
|||
&suite.state,
|
||||
federator,
|
||||
testrig.NewEmailSender("../../../../web/template/", nil),
|
||||
testrig.NewNoopWebPushSender(),
|
||||
mediaManager,
|
||||
)
|
||||
testrig.StartWorkers(&suite.state, processor.Workers())
|
||||
|
|
|
@ -99,7 +99,13 @@ func (suite *InstanceStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.instanceModule = instance.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -99,7 +99,13 @@ func (suite *ListsStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.listsModule = lists.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
|
|
|
@ -104,7 +104,13 @@ func (suite *MediaCreateTestSuite) SetupTest() {
|
|||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
|
||||
// setup module being tested
|
||||
suite.mediaModule = mediamodule.New(suite.processor)
|
||||
|
|
|
@ -102,7 +102,13 @@ func (suite *MediaUpdateTestSuite) SetupTest() {
|
|||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
|
||||
// setup module being tested
|
||||
suite.mediaModule = mediamodule.New(suite.processor)
|
||||
|
|
|
@ -96,7 +96,13 @@ func (suite *MutesTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.mutesModule = mutes.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -100,7 +100,13 @@ func (suite *NotificationsTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.notificationsModule = notifications.New(suite.processor)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,14 +36,15 @@
|
|||
|
||||
type PollsStandardTestSuite struct {
|
||||
suite.Suite
|
||||
db db.DB
|
||||
storage *storage.Driver
|
||||
mediaManager *media.Manager
|
||||
federator *federation.Federator
|
||||
processor *processing.Processor
|
||||
emailSender email.Sender
|
||||
sentEmails map[string]string
|
||||
state state.State
|
||||
db db.DB
|
||||
storage *storage.Driver
|
||||
mediaManager *media.Manager
|
||||
federator *federation.Federator
|
||||
processor *processing.Processor
|
||||
emailSender email.Sender
|
||||
sentEmails map[string]string
|
||||
webPushSender *testrig.WebPushMockSender
|
||||
state state.State
|
||||
|
||||
// standard suite models
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
|
@ -91,7 +92,13 @@ func (suite *PollsStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.pollsModule = polls.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
49
internal/api/client/push/push.go
Normal file
49
internal/api/client/push/push.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
)
|
||||
|
||||
const (
|
||||
// BasePath is the base path for serving the push API, minus the 'api' prefix.
|
||||
BasePath = "/v1/push"
|
||||
// SubscriptionPath is the path for serving requests for the current auth token's push subscription.
|
||||
SubscriptionPath = BasePath + "/subscription"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
}
|
||||
|
||||
func New(processor *processing.Processor) *Module {
|
||||
return &Module{
|
||||
processor: processor,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
|
||||
attachHandler(http.MethodGet, SubscriptionPath, m.PushSubscriptionGETHandler)
|
||||
attachHandler(http.MethodPost, SubscriptionPath, m.PushSubscriptionPOSTHandler)
|
||||
attachHandler(http.MethodPut, SubscriptionPath, m.PushSubscriptionPUTHandler)
|
||||
attachHandler(http.MethodDelete, SubscriptionPath, m.PushSubscriptionDELETEHandler)
|
||||
}
|
110
internal/api/client/push/push_test.go
Normal file
110
internal/api/client/push/push_test.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/push"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type PushTestSuite struct {
|
||||
suite.Suite
|
||||
db db.DB
|
||||
storage *storage.Driver
|
||||
mediaManager *media.Manager
|
||||
federator *federation.Federator
|
||||
processor *processing.Processor
|
||||
emailSender email.Sender
|
||||
sentEmails map[string]string
|
||||
state state.State
|
||||
|
||||
// standard suite models
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
testClients map[string]*gtsmodel.Client
|
||||
testApplications map[string]*gtsmodel.Application
|
||||
testUsers map[string]*gtsmodel.User
|
||||
testAccounts map[string]*gtsmodel.Account
|
||||
testWebPushSubscriptions map[string]*gtsmodel.WebPushSubscription
|
||||
|
||||
// module being tested
|
||||
pushModule *push.Module
|
||||
}
|
||||
|
||||
func (suite *PushTestSuite) SetupSuite() {
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
suite.testApplications = testrig.NewTestApplications()
|
||||
suite.testUsers = testrig.NewTestUsers()
|
||||
suite.testAccounts = testrig.NewTestAccounts()
|
||||
suite.testWebPushSubscriptions = testrig.NewTestWebPushSubscriptions()
|
||||
}
|
||||
|
||||
func (suite *PushTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
testrig.InitTestConfig()
|
||||
config.Config(func(cfg *config.Configuration) {
|
||||
cfg.WebAssetBaseDir = "../../../../web/assets/"
|
||||
cfg.WebTemplateBaseDir = "../../../../web/templates/"
|
||||
})
|
||||
testrig.InitTestLog()
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
suite.state.Storage = suite.storage
|
||||
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.pushModule = push.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
}
|
||||
|
||||
func (suite *PushTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StandardStorageTeardown(suite.storage)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
func TestPushTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(PushTestSuite))
|
||||
}
|
64
internal/api/client/push/pushsubscriptiondelete.go
Normal file
64
internal/api/client/push/pushsubscriptiondelete.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// PushSubscriptionDELETEHandler swagger:operation DELETE /api/v1/push/subscription pushSubscriptionDelete
|
||||
//
|
||||
// Delete the Web Push subscription associated with the current auth token.
|
||||
// If there is no subscription, returns successfully anyway.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - push
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - push
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: Push subscription deleted, or did not exist.
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) PushSubscriptionDELETEHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if errWithCode := m.processor.Push().Delete(c.Request.Context(), authed.Token.GetAccess()); errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject)
|
||||
}
|
83
internal/api/client/push/pushsubscriptiondelete_test.go
Normal file
83
internal/api/client/push/pushsubscriptiondelete_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/push"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
// deleteSubscription deletes the push subscription for the named account and token.
|
||||
func (suite *PushTestSuite) deleteSubscription(
|
||||
accountFixtureName string,
|
||||
tokenFixtureName string,
|
||||
expectedHTTPStatus int,
|
||||
) error {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts[accountFixtureName])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens[tokenFixtureName]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers[accountFixtureName])
|
||||
|
||||
// create the request
|
||||
requestUrl := config.GetProtocol() + "://" + config.GetHost() + "/api" + push.SubscriptionPath
|
||||
ctx.Request = httptest.NewRequest(http.MethodDelete, requestUrl, nil)
|
||||
|
||||
// trigger the handler
|
||||
suite.pushModule.PushSubscriptionDELETEHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer func() {
|
||||
_ = result.Body.Close()
|
||||
}()
|
||||
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
return fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete a subscription that should exist.
|
||||
func (suite *PushTestSuite) TestDeleteSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should have a subscription associated with it already.
|
||||
tokenFixtureName := "local_account_1"
|
||||
|
||||
err := suite.deleteSubscription(accountFixtureName, tokenFixtureName, 200)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Delete a subscription that should not exist, which should succeed anyway.
|
||||
func (suite *PushTestSuite) TestDeleteMissingSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
err := suite.deleteSubscription(accountFixtureName, tokenFixtureName, 200)
|
||||
suite.NoError(err)
|
||||
}
|
71
internal/api/client/push/pushsubscriptionget.go
Normal file
71
internal/api/client/push/pushsubscriptionget.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// PushSubscriptionGETHandler swagger:operation GET /api/v1/push/subscription pushSubscriptionGet
|
||||
//
|
||||
// Get the push subscription for the current access token.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - push
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - push
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: Web Push subscription for current access token.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/webPushSubscription"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: This access token doesn't have an associated subscription.
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) PushSubscriptionGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiSubscription, errWithCode := m.processor.Push().Get(c, authed.Token.GetAccess())
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiSubscription)
|
||||
}
|
102
internal/api/client/push/pushsubscriptionget_test.go
Normal file
102
internal/api/client/push/pushsubscriptionget_test.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/push"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
// getSubscription gets the push subscription for the named account and token.
|
||||
func (suite *PushTestSuite) getSubscription(
|
||||
accountFixtureName string,
|
||||
tokenFixtureName string,
|
||||
expectedHTTPStatus int,
|
||||
) (*apimodel.WebPushSubscription, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts[accountFixtureName])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens[tokenFixtureName]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers[accountFixtureName])
|
||||
|
||||
// create the request
|
||||
requestUrl := config.GetProtocol() + "://" + config.GetHost() + "/api" + push.SubscriptionPath
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, requestUrl, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
// trigger the handler
|
||||
suite.pushModule.PushSubscriptionGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer func() {
|
||||
_ = result.Body.Close()
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
return nil, fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
resp := &apimodel.WebPushSubscription{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Get a subscription that should exist.
|
||||
func (suite *PushTestSuite) TestGetSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should have a subscription associated with it already, with all event types turned on.
|
||||
tokenFixtureName := "local_account_1"
|
||||
|
||||
subscription, err := suite.getSubscription(accountFixtureName, tokenFixtureName, 200)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
suite.True(subscription.Alerts.Mention)
|
||||
}
|
||||
}
|
||||
|
||||
// Get a subscription that should not exist, which should fail.
|
||||
func (suite *PushTestSuite) TestGetMissingSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
_, err := suite.getSubscription(accountFixtureName, tokenFixtureName, 404)
|
||||
suite.NoError(err)
|
||||
}
|
284
internal/api/client/push/pushsubscriptionpost.go
Normal file
284
internal/api/client/push/pushsubscriptionpost.go
Normal file
|
@ -0,0 +1,284 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// PushSubscriptionPOSTHandler swagger:operation POST /api/v1/push/subscription pushSubscriptionPost
|
||||
//
|
||||
// Create a new Web Push subscription for the current access token, or replace the existing one.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - push
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: subscription[endpoint]
|
||||
// in: formData
|
||||
// type: string
|
||||
// required: true
|
||||
// minLength: 1
|
||||
// description: The URL to which Web Push notifications will be sent.
|
||||
// -
|
||||
// name: subscription[keys][auth]
|
||||
// in: formData
|
||||
// type: string
|
||||
// required: true
|
||||
// minLength: 1
|
||||
// description: The auth secret, a Base64 encoded string of 16 bytes of random data.
|
||||
// -
|
||||
// name: subscription[keys][p256dh]
|
||||
// in: formData
|
||||
// type: string
|
||||
// required: true
|
||||
// minLength: 1
|
||||
// description: The user agent public key, a Base64 encoded string of a public key from an ECDH keypair using the prime256v1 curve.
|
||||
// -
|
||||
// name: data[alerts][follow]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when someone has followed you?
|
||||
// -
|
||||
// name: data[alerts][follow_request]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when someone has requested to follow you?
|
||||
// -
|
||||
// name: data[alerts][favourite]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a status you created has been favourited by someone else?
|
||||
// -
|
||||
// name: data[alerts][mention]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when someone else has mentioned you in a status?
|
||||
// -
|
||||
// name: data[alerts][reblog]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a status you created has been boosted by someone else?
|
||||
// -
|
||||
// name: data[alerts][poll]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a poll you voted in or created has ended?
|
||||
// -
|
||||
// name: data[alerts][status]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a subscribed account posts a status?
|
||||
// -
|
||||
// name: data[alerts][update]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a status you interacted with has been edited?
|
||||
// -
|
||||
// name: data[alerts][admin.sign_up]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a new user has signed up?
|
||||
// -
|
||||
// name: data[alerts][admin.report]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a new report has been filed?
|
||||
// -
|
||||
// name: data[alerts][pending.favourite]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a fave is pending?
|
||||
// -
|
||||
// name: data[alerts][pending.reply]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a reply is pending?
|
||||
// -
|
||||
// name: data[alerts][pending.reblog]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a boost is pending?
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - push
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: Web Push subscription for current access token.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/webPushSubscription"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) PushSubscriptionPOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.WebPushSubscriptionCreateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeCreate(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiSubscription, errWithCode := m.processor.Push().CreateOrReplace(c, authed.Account.ID, authed.Token.GetAccess(), form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiSubscription)
|
||||
}
|
||||
|
||||
// validateNormalizeCreate checks subscription endpoint format and keys decodability,
|
||||
// and copies form fields to their canonical JSON equivalents.
|
||||
func validateNormalizeCreate(request *apimodel.WebPushSubscriptionCreateRequest) error {
|
||||
if request.Subscription == nil {
|
||||
request.Subscription = &apimodel.WebPushSubscriptionRequestSubscription{}
|
||||
}
|
||||
|
||||
// Normalize and validate endpoint URL.
|
||||
if request.SubscriptionEndpoint != nil {
|
||||
request.Subscription.Endpoint = *request.SubscriptionEndpoint
|
||||
}
|
||||
|
||||
if request.Subscription.Endpoint == "" {
|
||||
return errors.New("endpoint is required")
|
||||
}
|
||||
endpointURL, err := url.Parse(request.Subscription.Endpoint)
|
||||
if err != nil {
|
||||
return errors.New("endpoint must be a valid URL")
|
||||
}
|
||||
if endpointURL.Scheme != "https" {
|
||||
return errors.New("endpoint must be an https:// URL")
|
||||
}
|
||||
if endpointURL.Host == "" {
|
||||
return errors.New("endpoint URL must have a host")
|
||||
}
|
||||
if endpointURL.Fragment != "" {
|
||||
return errors.New("endpoint URL must not have a fragment")
|
||||
}
|
||||
|
||||
// Normalize and validate auth secret.
|
||||
if request.SubscriptionKeysAuth != nil {
|
||||
request.Subscription.Keys.Auth = *request.SubscriptionKeysAuth
|
||||
}
|
||||
|
||||
authBytes, err := base64DecodeAny("auth", request.Subscription.Keys.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(authBytes) != 16 {
|
||||
return fmt.Errorf("auth must be 16 bytes long, got %d", len(authBytes))
|
||||
}
|
||||
|
||||
// Normalize and validate public key.
|
||||
if request.SubscriptionKeysP256dh != nil {
|
||||
request.Subscription.Keys.P256dh = *request.SubscriptionKeysP256dh
|
||||
}
|
||||
|
||||
p256dhBytes, err := base64DecodeAny("p256dh", request.Subscription.Keys.P256dh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = ecdh.P256().NewPublicKey(p256dhBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("p256dh must be a valid public key on the NIST P-256 curve: %w", err)
|
||||
}
|
||||
|
||||
return validateNormalizeUpdate(&request.WebPushSubscriptionUpdateRequest)
|
||||
}
|
||||
|
||||
// base64DecodeAny tries decoding a string with standard and URL alphabets of Base64, with and without padding.
|
||||
func base64DecodeAny(name string, value string) ([]byte, error) {
|
||||
encodings := []*base64.Encoding{
|
||||
base64.StdEncoding,
|
||||
base64.URLEncoding,
|
||||
base64.RawStdEncoding,
|
||||
base64.RawURLEncoding,
|
||||
}
|
||||
|
||||
for _, encoding := range encodings {
|
||||
if bytes, err := encoding.DecodeString(value); err == nil {
|
||||
return bytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s is not valid Base64 data", name)
|
||||
}
|
346
internal/api/client/push/pushsubscriptionpost_test.go
Normal file
346
internal/api/client/push/pushsubscriptionpost_test.go
Normal file
|
@ -0,0 +1,346 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/push"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
// postSubscription creates or replaces the push subscription for the named account and token.
|
||||
// It only allows updating two event types if using the form API. Add more if you need them.
|
||||
func (suite *PushTestSuite) postSubscription(
|
||||
accountFixtureName string,
|
||||
tokenFixtureName string,
|
||||
endpoint *string,
|
||||
auth *string,
|
||||
p256dh *string,
|
||||
alertsMention *bool,
|
||||
alertsStatus *bool,
|
||||
requestJson *string,
|
||||
expectedHTTPStatus int,
|
||||
) (*apimodel.WebPushSubscription, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts[accountFixtureName])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens[tokenFixtureName]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers[accountFixtureName])
|
||||
|
||||
// create the request
|
||||
requestUrl := config.GetProtocol() + "://" + config.GetHost() + "/api" + push.SubscriptionPath
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, requestUrl, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if endpoint != nil {
|
||||
ctx.Request.Form["subscription[endpoint]"] = []string{*endpoint}
|
||||
}
|
||||
if auth != nil {
|
||||
ctx.Request.Form["subscription[keys][auth]"] = []string{*auth}
|
||||
}
|
||||
if p256dh != nil {
|
||||
ctx.Request.Form["subscription[keys][p256dh]"] = []string{*p256dh}
|
||||
}
|
||||
if alertsMention != nil {
|
||||
ctx.Request.Form["data[alerts][mention]"] = []string{strconv.FormatBool(*alertsMention)}
|
||||
}
|
||||
if alertsStatus != nil {
|
||||
ctx.Request.Form["data[alerts][status]"] = []string{strconv.FormatBool(*alertsStatus)}
|
||||
}
|
||||
}
|
||||
|
||||
// trigger the handler
|
||||
suite.pushModule.PushSubscriptionPOSTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer func() {
|
||||
_ = result.Body.Close()
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
return nil, fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
resp := &apimodel.WebPushSubscription{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Create a new subscription.
|
||||
func (suite *PushTestSuite) TestPostSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
endpoint := "https://example.test/push"
|
||||
auth := "cgna/fzrYLDQyPf5hD7IsA=="
|
||||
p256dh := "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
alertsMention := true
|
||||
alertsStatus := false
|
||||
subscription, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
&endpoint,
|
||||
&auth,
|
||||
&p256dh,
|
||||
&alertsMention,
|
||||
&alertsStatus,
|
||||
nil,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
suite.True(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
// Omitted event types should default to off.
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new subscription with only required fields.
|
||||
func (suite *PushTestSuite) TestPostSubscriptionMinimal() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
endpoint := "https://example.test/push"
|
||||
auth := "cgna/fzrYLDQyPf5hD7IsA=="
|
||||
p256dh := "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
subscription, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
&endpoint,
|
||||
&auth,
|
||||
&p256dh,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
// All event types should default to off.
|
||||
suite.False(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new subscription with a missing endpoint, which should fail.
|
||||
func (suite *PushTestSuite) TestPostInvalidSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
// No endpoint.
|
||||
auth := "cgna/fzrYLDQyPf5hD7IsA=="
|
||||
p256dh := "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
alertsMention := true
|
||||
alertsStatus := false
|
||||
_, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
nil,
|
||||
&auth,
|
||||
&p256dh,
|
||||
&alertsMention,
|
||||
&alertsStatus,
|
||||
nil,
|
||||
422,
|
||||
)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Create a new subscription, using the JSON format.
|
||||
func (suite *PushTestSuite) TestPostSubscriptionJSON() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
requestJson := `{
|
||||
"subscription": {
|
||||
"endpoint": "https://example.test/push",
|
||||
"keys": {
|
||||
"auth": "cgna/fzrYLDQyPf5hD7IsA==",
|
||||
"p256dh": "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"alerts": {
|
||||
"mention": true,
|
||||
"status": false
|
||||
}
|
||||
}
|
||||
}`
|
||||
subscription, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
&requestJson,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
suite.True(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
// Omitted event types should default to off.
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new subscription, using the JSON format and only required fields.
|
||||
func (suite *PushTestSuite) TestPostSubscriptionJSONMinimal() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
requestJson := `{
|
||||
"subscription": {
|
||||
"endpoint": "https://example.test/push",
|
||||
"keys": {
|
||||
"auth": "cgna/fzrYLDQyPf5hD7IsA==",
|
||||
"p256dh": "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
}
|
||||
}
|
||||
}`
|
||||
subscription, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
&requestJson,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
// All event types should default to off.
|
||||
suite.False(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new subscription with a missing endpoint, using the JSON format, which should fail.
|
||||
func (suite *PushTestSuite) TestPostInvalidSubscriptionJSON() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
// No endpoint.
|
||||
requestJson := `{
|
||||
"subscription": {
|
||||
"keys": {
|
||||
"auth": "cgna/fzrYLDQyPf5hD7IsA==",
|
||||
"p256dh": "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"alerts": {
|
||||
"mention": true,
|
||||
"status": false
|
||||
}
|
||||
}
|
||||
}`
|
||||
_, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
&requestJson,
|
||||
422,
|
||||
)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Replace a subscription that already exists.
|
||||
func (suite *PushTestSuite) TestPostExistingSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should have a subscription associated with it already, with all event types turned on.
|
||||
tokenFixtureName := "local_account_1"
|
||||
|
||||
endpoint := "https://example.test/push"
|
||||
auth := "JMFtMRgZaeHpwsDjBnhcmQ=="
|
||||
p256dh := "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
|
||||
alertsMention := true
|
||||
alertsStatus := false
|
||||
subscription, err := suite.postSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
&endpoint,
|
||||
&auth,
|
||||
&p256dh,
|
||||
&alertsMention,
|
||||
&alertsStatus,
|
||||
nil,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEqual(suite.testWebPushSubscriptions["local_account_1_token_1"].ID, subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
suite.True(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
// Omitted event types should default to off.
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
232
internal/api/client/push/pushsubscriptionput.go
Normal file
232
internal/api/client/push/pushsubscriptionput.go
Normal file
|
@ -0,0 +1,232 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// PushSubscriptionPUTHandler swagger:operation PUT /api/v1/push/subscription pushSubscriptionPut
|
||||
//
|
||||
// Update the Web Push subscription for the current access token.
|
||||
// Only which notifications you receive can be updated.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - push
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: data[alerts][follow]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when someone has followed you?
|
||||
// -
|
||||
// name: data[alerts][follow_request]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when someone has requested to follow you?
|
||||
// -
|
||||
// name: data[alerts][favourite]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a status you created has been favourited by someone else?
|
||||
// -
|
||||
// name: data[alerts][mention]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when someone else has mentioned you in a status?
|
||||
// -
|
||||
// name: data[alerts][reblog]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a status you created has been boosted by someone else?
|
||||
// -
|
||||
// name: data[alerts][poll]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a poll you voted in or created has ended?
|
||||
// -
|
||||
// name: data[alerts][status]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a subscribed account posts a status?
|
||||
// -
|
||||
// name: data[alerts][update]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a status you interacted with has been edited?
|
||||
// -
|
||||
// name: data[alerts][admin.sign_up]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a new user has signed up?
|
||||
// -
|
||||
// name: data[alerts][admin.report]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a new report has been filed?
|
||||
// -
|
||||
// name: data[alerts][pending.favourite]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a fave is pending?
|
||||
// -
|
||||
// name: data[alerts][pending.reply]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a reply is pending?
|
||||
// -
|
||||
// name: data[alerts][pending.reblog]
|
||||
// in: formData
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: Receive a push notification when a boost is pending?
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - push
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: Web Push subscription for current access token.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/webPushSubscription"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: This access token doesn't have an associated subscription.
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) PushSubscriptionPUTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.WebPushSubscriptionUpdateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeUpdate(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiSubscription, errWithCode := m.processor.Push().Update(c, authed.Token.GetAccess(), form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiSubscription)
|
||||
}
|
||||
|
||||
// validateNormalizeUpdate copies form fields to their canonical JSON equivalents.
|
||||
func validateNormalizeUpdate(request *apimodel.WebPushSubscriptionUpdateRequest) error {
|
||||
if request.Data == nil {
|
||||
request.Data = &apimodel.WebPushSubscriptionRequestData{}
|
||||
}
|
||||
|
||||
if request.Data.Alerts == nil {
|
||||
request.Data.Alerts = &apimodel.WebPushSubscriptionAlerts{}
|
||||
}
|
||||
|
||||
if request.DataAlertsFollow != nil {
|
||||
request.Data.Alerts.Follow = *request.DataAlertsFollow
|
||||
}
|
||||
if request.DataAlertsFollowRequest != nil {
|
||||
request.Data.Alerts.FollowRequest = *request.DataAlertsFollowRequest
|
||||
}
|
||||
if request.DataAlertsMention != nil {
|
||||
request.Data.Alerts.Mention = *request.DataAlertsMention
|
||||
}
|
||||
if request.DataAlertsReblog != nil {
|
||||
request.Data.Alerts.Reblog = *request.DataAlertsReblog
|
||||
}
|
||||
if request.DataAlertsPoll != nil {
|
||||
request.Data.Alerts.Poll = *request.DataAlertsPoll
|
||||
}
|
||||
if request.DataAlertsStatus != nil {
|
||||
request.Data.Alerts.Status = *request.DataAlertsStatus
|
||||
}
|
||||
if request.DataAlertsUpdate != nil {
|
||||
request.Data.Alerts.Update = *request.DataAlertsUpdate
|
||||
}
|
||||
if request.DataAlertsAdminSignup != nil {
|
||||
request.Data.Alerts.AdminSignup = *request.DataAlertsAdminSignup
|
||||
}
|
||||
if request.DataAlertsAdminReport != nil {
|
||||
request.Data.Alerts.AdminReport = *request.DataAlertsAdminReport
|
||||
}
|
||||
if request.DataAlertsPendingFavourite != nil {
|
||||
request.Data.Alerts.PendingFavourite = *request.DataAlertsPendingFavourite
|
||||
}
|
||||
if request.DataAlertsPendingReply != nil {
|
||||
request.Data.Alerts.PendingReply = *request.DataAlertsPendingReply
|
||||
}
|
||||
if request.DataAlertsPendingReblog != nil {
|
||||
request.Data.Alerts.Reblog = *request.DataAlertsPendingReblog
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
176
internal/api/client/push/pushsubscriptionput_test.go
Normal file
176
internal/api/client/push/pushsubscriptionput_test.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/push"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
// putSubscription updates the push subscription for the named account and token.
|
||||
// It only allows updating two event types if using the form API. Add more if you need them.
|
||||
func (suite *PushTestSuite) putSubscription(
|
||||
accountFixtureName string,
|
||||
tokenFixtureName string,
|
||||
alertsMention *bool,
|
||||
alertsStatus *bool,
|
||||
requestJson *string,
|
||||
expectedHTTPStatus int,
|
||||
) (*apimodel.WebPushSubscription, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts[accountFixtureName])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens[tokenFixtureName]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers[accountFixtureName])
|
||||
|
||||
// create the request
|
||||
requestUrl := config.GetProtocol() + "://" + config.GetHost() + "/api" + push.SubscriptionPath
|
||||
ctx.Request = httptest.NewRequest(http.MethodPut, requestUrl, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if alertsMention != nil {
|
||||
ctx.Request.Form["data[alerts][mention]"] = []string{strconv.FormatBool(*alertsMention)}
|
||||
}
|
||||
if alertsStatus != nil {
|
||||
ctx.Request.Form["data[alerts][status]"] = []string{strconv.FormatBool(*alertsStatus)}
|
||||
}
|
||||
}
|
||||
|
||||
// trigger the handler
|
||||
suite.pushModule.PushSubscriptionPUTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer func() {
|
||||
_ = result.Body.Close()
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
return nil, fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
resp := &apimodel.WebPushSubscription{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Update a subscription that already exists.
|
||||
func (suite *PushTestSuite) TestPutSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should have a subscription associated with it already, with all event types turned on.
|
||||
tokenFixtureName := "local_account_1"
|
||||
|
||||
alertsMention := true
|
||||
alertsStatus := false
|
||||
subscription, err := suite.putSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
&alertsMention,
|
||||
&alertsStatus,
|
||||
nil,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
suite.True(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
// Omitted event types should default to off.
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
||||
|
||||
// Update a subscription that already exists, using the JSON format.
|
||||
func (suite *PushTestSuite) TestPutSubscriptionJSON() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should have a subscription associated with it already, with all event types turned on.
|
||||
tokenFixtureName := "local_account_1"
|
||||
|
||||
requestJson := `{
|
||||
"data": {
|
||||
"alerts": {
|
||||
"mention": true,
|
||||
"status": false
|
||||
}
|
||||
}
|
||||
}`
|
||||
subscription, err := suite.putSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
nil,
|
||||
nil,
|
||||
&requestJson,
|
||||
200,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.NotEmpty(subscription.ID)
|
||||
suite.NotEmpty(subscription.Endpoint)
|
||||
suite.NotEmpty(subscription.ServerKey)
|
||||
suite.True(subscription.Alerts.Mention)
|
||||
suite.False(subscription.Alerts.Status)
|
||||
// Omitted event types should default to off.
|
||||
suite.False(subscription.Alerts.Favourite)
|
||||
}
|
||||
}
|
||||
|
||||
// Update a subscription that does not exist, which should fail.
|
||||
func (suite *PushTestSuite) TestPutMissingSubscription() {
|
||||
accountFixtureName := "local_account_1"
|
||||
// This token should not have a subscription.
|
||||
tokenFixtureName := "local_account_1_user_authorization_token"
|
||||
|
||||
alertsMention := true
|
||||
alertsStatus := false
|
||||
_, err := suite.putSubscription(
|
||||
accountFixtureName,
|
||||
tokenFixtureName,
|
||||
&alertsMention,
|
||||
&alertsStatus,
|
||||
nil,
|
||||
404,
|
||||
)
|
||||
suite.NoError(err)
|
||||
}
|
|
@ -91,7 +91,13 @@ func (suite *ReportsStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.reportsModule = reports.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -95,7 +95,13 @@ func (suite *SearchStandardTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.searchModule = search.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -211,7 +211,13 @@ func (suite *StatusStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.statusModule = statuses.New(suite.processor)
|
||||
|
||||
testrig.StartWorkers(&suite.state, suite.processor.Workers())
|
||||
|
|
|
@ -111,7 +111,13 @@ func (suite *StreamingTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.streamingModule = streaming.New(suite.processor, 1, 4096)
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,13 @@ func (suite *TagsTestSuite) SetupTest() {
|
|||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.tagsModule = tags.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
|
|
|
@ -44,7 +44,8 @@ func (suite *EmailChangeTestSuite) TestEmailChangePOST() {
|
|||
storage := testrig.NewInMemoryStorage()
|
||||
sentEmails := make(map[string]string)
|
||||
emailSender := testrig.NewEmailSender("../../../../web/template/", sentEmails)
|
||||
processor := testrig.NewTestProcessor(state, suite.federator, emailSender, suite.mediaManager)
|
||||
webPushSender := testrig.NewNoopWebPushSender()
|
||||
processor := testrig.NewTestProcessor(state, suite.federator, emailSender, webPushSender, suite.mediaManager)
|
||||
testrig.StartWorkers(state, processor.Workers())
|
||||
userModule := user.New(processor)
|
||||
testrig.StandardDBSetup(state.DB, suite.testAccounts)
|
||||
|
|
|
@ -86,8 +86,21 @@ func (suite *UserStandardTestSuite) SetupTest() {
|
|||
)
|
||||
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, testrig.NewEmailSender("../../../../web/template/", nil), suite.mediaManager)
|
||||
suite.federator = testrig.NewTestFederator(
|
||||
&suite.state,
|
||||
testrig.NewTestTransportController(
|
||||
&suite.state,
|
||||
testrig.NewMockHTTPClient(nil, "../../../../testrig/media"),
|
||||
),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
testrig.NewEmailSender("../../../../web/template/", nil),
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.userModule = user.New(suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
|
|
@ -75,8 +75,21 @@ func (suite *FileserverTestSuite) SetupSuite() {
|
|||
suite.state.Storage = suite.storage
|
||||
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../testrig/media")), suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.federator = testrig.NewTestFederator(
|
||||
&suite.state,
|
||||
testrig.NewTestTransportController(
|
||||
&suite.state,
|
||||
testrig.NewMockHTTPClient(nil, "../../../testrig/media"),
|
||||
),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
|
||||
suite.tc = typeutils.NewConverter(&suite.state)
|
||||
|
||||
|
|
|
@ -174,6 +174,8 @@ type InstanceV2Configuration struct {
|
|||
Emojis InstanceConfigurationEmojis `json:"emojis"`
|
||||
// True if instance is running with OIDC as auth/identity backend, else omitted.
|
||||
OIDCEnabled bool `json:"oidc_enabled,omitempty"`
|
||||
// Instance VAPID configuration.
|
||||
VAPID InstanceV2ConfigurationVAPID `json:"vapid"`
|
||||
}
|
||||
|
||||
// Information about registering for this instance.
|
||||
|
@ -204,3 +206,11 @@ type InstanceV2Contact struct {
|
|||
// Key/value not present if no contact account set.
|
||||
Account *Account `json:"account,omitempty"`
|
||||
}
|
||||
|
||||
// InstanceV2ConfigurationVAPID holds the instance's VAPID configuration.
|
||||
//
|
||||
// swagger:model instanceV2ConfigurationVAPID
|
||||
type InstanceV2ConfigurationVAPID struct {
|
||||
// The instance's VAPID public key, Base64-encoded.
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package model
|
||||
|
||||
// PushSubscription represents a subscription to the push streaming server.
|
||||
type PushSubscription struct {
|
||||
// The id of the push subscription in the database.
|
||||
ID string `json:"id"`
|
||||
// Where push alerts will be sent to.
|
||||
Endpoint string `json:"endpoint"`
|
||||
// The streaming server's VAPID key.
|
||||
ServerKey string `json:"server_key"`
|
||||
// Which alerts should be delivered to the endpoint.
|
||||
Alerts *PushSubscriptionAlerts `json:"alerts"`
|
||||
}
|
||||
|
||||
// PushSubscriptionAlerts represents the specific alerts that this push subscription will give.
|
||||
type PushSubscriptionAlerts struct {
|
||||
// Receive a push notification when someone has followed you?
|
||||
Follow bool `json:"follow"`
|
||||
// Receive a push notification when a status you created has been favourited by someone else?
|
||||
Favourite bool `json:"favourite"`
|
||||
// Receive a push notification when someone else has mentioned you in a status?
|
||||
Mention bool `json:"mention"`
|
||||
// Receive a push notification when a status you created has been boosted by someone else?
|
||||
Reblog bool `json:"reblog"`
|
||||
// Receive a push notification when a poll you voted in or created has ended?
|
||||
Poll bool `json:"poll"`
|
||||
}
|
52
internal/api/model/webpushnotification.go
Normal file
52
internal/api/model/webpushnotification.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package model
|
||||
|
||||
// WebPushNotification represents a notification summary delivered to the client by the Web Push server.
|
||||
// It does not contain an entire Notification, just the NotificationID and some preview information.
|
||||
// It is not used in the client API directly, but is included in the API doc for decoding Web Push notifications.
|
||||
//
|
||||
// swagger:model webPushNotification
|
||||
type WebPushNotification struct {
|
||||
// NotificationID is the Notification.ID of the referenced Notification.
|
||||
NotificationID string `json:"notification_id"`
|
||||
|
||||
// NotificationType is the Notification.Type of the referenced Notification.
|
||||
NotificationType string `json:"notification_type"`
|
||||
|
||||
// Title is a title for the notification,
|
||||
// generally describing an action taken by a user.
|
||||
Title string `json:"title"`
|
||||
|
||||
// Body is a preview of the notification body,
|
||||
// such as the first line of a status's CW or text,
|
||||
// or the first line of an account bio.
|
||||
Body string `json:"body"`
|
||||
|
||||
// Icon is an image URL that can be displayed with the notification,
|
||||
// normally the account's avatar.
|
||||
Icon string `json:"icon"`
|
||||
|
||||
// PreferredLocale is a BCP 47 language tag for the receiving user's locale.
|
||||
PreferredLocale string `json:"preferred_locale"`
|
||||
|
||||
// AccessToken is the access token associated with the Web Push subscription.
|
||||
// I don't know why this is sent, given that the client should know that already,
|
||||
// but Feditext does use it.
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
157
internal/api/model/webpushsubscription.go
Normal file
157
internal/api/model/webpushsubscription.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package model
|
||||
|
||||
// WebPushSubscription represents a subscription to a Web Push server.
|
||||
//
|
||||
// swagger:model webPushSubscription
|
||||
type WebPushSubscription struct {
|
||||
// The id of the push subscription in the database.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Where push alerts will be sent to.
|
||||
Endpoint string `json:"endpoint"`
|
||||
|
||||
// The streaming server's VAPID public key.
|
||||
ServerKey string `json:"server_key"`
|
||||
|
||||
// Which alerts should be delivered to the endpoint.
|
||||
Alerts WebPushSubscriptionAlerts `json:"alerts"`
|
||||
|
||||
// Which accounts should generate notifications.
|
||||
Policy WebPushNotificationPolicy `json:"policy"`
|
||||
|
||||
// Whether the subscription uses RFC or pre-RFC Web Push standards.
|
||||
// For GotoSocial, this is always true.
|
||||
Standard bool `json:"standard"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionAlerts represents the specific events that this Web Push subscription will receive.
|
||||
//
|
||||
// swagger:model webPushSubscriptionAlerts
|
||||
type WebPushSubscriptionAlerts struct {
|
||||
// Receive a push notification when someone has followed you?
|
||||
Follow bool `json:"follow"`
|
||||
|
||||
// Receive a push notification when someone has requested to follow you?
|
||||
FollowRequest bool `json:"follow_request"`
|
||||
|
||||
// Receive a push notification when a status you created has been favourited by someone else?
|
||||
Favourite bool `json:"favourite"`
|
||||
|
||||
// Receive a push notification when someone else has mentioned you in a status?
|
||||
Mention bool `json:"mention"`
|
||||
|
||||
// Receive a push notification when a status you created has been boosted by someone else?
|
||||
Reblog bool `json:"reblog"`
|
||||
|
||||
// Receive a push notification when a poll you voted in or created has ended?
|
||||
Poll bool `json:"poll"`
|
||||
|
||||
// Receive a push notification when a subscribed account posts a status?
|
||||
Status bool `json:"status"`
|
||||
|
||||
// Receive a push notification when a status you interacted with has been edited?
|
||||
Update bool `json:"update"`
|
||||
|
||||
// Receive a push notification when a new user has signed up?
|
||||
AdminSignup bool `json:"admin.sign_up"`
|
||||
|
||||
// Receive a push notification when a new report has been filed?
|
||||
AdminReport bool `json:"admin.report"`
|
||||
|
||||
// Receive a push notification when a fave is pending?
|
||||
PendingFavourite bool `json:"pending.favourite"`
|
||||
|
||||
// Receive a push notification when a reply is pending?
|
||||
PendingReply bool `json:"pending.reply"`
|
||||
|
||||
// Receive a push notification when a boost is pending?
|
||||
PendingReblog bool `json:"pending.reblog"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionCreateRequest captures params for creating or replacing a Web Push subscription.
|
||||
//
|
||||
// swagger:ignore
|
||||
type WebPushSubscriptionCreateRequest struct {
|
||||
Subscription *WebPushSubscriptionRequestSubscription `form:"-" json:"subscription"`
|
||||
|
||||
SubscriptionEndpoint *string `form:"subscription[endpoint]" json:"-"`
|
||||
SubscriptionKeysAuth *string `form:"subscription[keys][auth]" json:"-"`
|
||||
SubscriptionKeysP256dh *string `form:"subscription[keys][p256dh]" json:"-"`
|
||||
|
||||
WebPushSubscriptionUpdateRequest
|
||||
}
|
||||
|
||||
// WebPushSubscriptionRequestSubscription is the part of a Web Push subscription that is fixed at creation.
|
||||
//
|
||||
// swagger:ignore
|
||||
type WebPushSubscriptionRequestSubscription struct {
|
||||
// Endpoint is the URL to which Web Push notifications will be sent.
|
||||
Endpoint string `json:"endpoint"`
|
||||
|
||||
Keys WebPushSubscriptionRequestSubscriptionKeys `json:"keys"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionRequestSubscriptionKeys is the part of a Web Push subscription that contains auth secrets.
|
||||
//
|
||||
// swagger:ignore
|
||||
type WebPushSubscriptionRequestSubscriptionKeys struct {
|
||||
// Auth is the auth secret, a Base64 encoded string of 16 bytes of random data.
|
||||
Auth string `json:"auth"`
|
||||
|
||||
// P256dh is the user agent public key, a Base64 encoded string of a public key from an ECDH keypair using the prime256v1 curve.
|
||||
P256dh string `json:"p256dh"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionUpdateRequest captures params for updating a Web Push subscription.
|
||||
//
|
||||
// swagger:ignore
|
||||
type WebPushSubscriptionUpdateRequest struct {
|
||||
Data *WebPushSubscriptionRequestData `form:"-" json:"data"`
|
||||
|
||||
DataAlertsFollow *bool `form:"data[alerts][follow]" json:"-"`
|
||||
DataAlertsFollowRequest *bool `form:"data[alerts][follow_request]" json:"-"`
|
||||
DataAlertsFavourite *bool `form:"data[alerts][favourite]" json:"-"`
|
||||
DataAlertsMention *bool `form:"data[alerts][mention]" json:"-"`
|
||||
DataAlertsReblog *bool `form:"data[alerts][reblog]" json:"-"`
|
||||
DataAlertsPoll *bool `form:"data[alerts][poll]" json:"-"`
|
||||
DataAlertsStatus *bool `form:"data[alerts][status]" json:"-"`
|
||||
DataAlertsUpdate *bool `form:"data[alerts][update]" json:"-"`
|
||||
DataAlertsAdminSignup *bool `form:"data[alerts][admin.sign_up]" json:"-"`
|
||||
DataAlertsAdminReport *bool `form:"data[alerts][admin.report]" json:"-"`
|
||||
DataAlertsPendingFavourite *bool `form:"data[alerts][pending.favourite]" json:"-"`
|
||||
DataAlertsPendingReply *bool `form:"data[alerts][pending.reply]" json:"-"`
|
||||
DataAlertsPendingReblog *bool `form:"data[alerts][pending.reblog]" json:"-"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionRequestData is the part of a Web Push subscription that can be changed after creation.
|
||||
//
|
||||
// swagger:ignore
|
||||
type WebPushSubscriptionRequestData struct {
|
||||
// Alerts selects the specific events that this Web Push subscription will receive.
|
||||
Alerts *WebPushSubscriptionAlerts `form:"-" json:"alerts"`
|
||||
}
|
||||
|
||||
// WebPushNotificationPolicy names sets of accounts that can generate notifications.
|
||||
type WebPushNotificationPolicy string
|
||||
|
||||
const (
|
||||
// WebPushNotificationPolicyAll allows all accounts to send notifications to the subscribing user.
|
||||
WebPushNotificationPolicyAll WebPushNotificationPolicy = "all"
|
||||
)
|
|
@ -18,10 +18,12 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
)
|
||||
|
||||
// WebPage encapsulates variables for
|
||||
|
@ -63,6 +65,11 @@ type WebPage struct {
|
|||
// ogMeta, stylesheets, javascript, and any extra
|
||||
// properties will be provided to the template if
|
||||
// set, but can all be nil.
|
||||
//
|
||||
// TemplateWebPage also checks whether the requesting
|
||||
// clientIP is 127.0.0.1 or within a private IP range.
|
||||
// If so, it injects a suggestion into the page header
|
||||
// about setting trusted-proxies correctly.
|
||||
func TemplateWebPage(
|
||||
c *gin.Context,
|
||||
page WebPage,
|
||||
|
@ -74,13 +81,99 @@ func TemplateWebPage(
|
|||
"javascript": page.Javascript,
|
||||
}
|
||||
|
||||
// Add extras to template object.
|
||||
for k, v := range page.Extra {
|
||||
obj[k] = v
|
||||
}
|
||||
|
||||
// Inject trustedProxiesRec to template
|
||||
// object (or noop if not necessary).
|
||||
injectTrustedProxiesRec(c, obj)
|
||||
|
||||
templatePage(c, page.Template, http.StatusOK, obj)
|
||||
}
|
||||
|
||||
func injectTrustedProxiesRec(
|
||||
c *gin.Context,
|
||||
obj map[string]any,
|
||||
) {
|
||||
if config.GetAdvancedRateLimitRequests() <= 0 {
|
||||
// If rate limiting is disabled entirely
|
||||
// there's no point in giving a trusted
|
||||
// proxies rec, as proper clientIP is
|
||||
// basically only used for rate limiting.
|
||||
return
|
||||
}
|
||||
|
||||
// clientIP = the client IP that gin
|
||||
// derives based on x-forwarded-for
|
||||
// and current trusted proxies.
|
||||
clientIP := c.ClientIP()
|
||||
if clientIP == "127.0.0.1" {
|
||||
// Suggest precise 127.0.0.1/32.
|
||||
trustedProxiesRec := clientIP + "/32"
|
||||
obj["trustedProxiesRec"] = trustedProxiesRec
|
||||
return
|
||||
}
|
||||
|
||||
// True if "X-Forwarded-For"
|
||||
// or "X-Real-IP" were set.
|
||||
var hasRemoteIPHeader bool
|
||||
for _, k := range []string{
|
||||
"X-Forwarded-For",
|
||||
"X-Real-IP",
|
||||
} {
|
||||
if v := c.GetHeader(k); v != "" {
|
||||
hasRemoteIPHeader = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasRemoteIPHeader {
|
||||
// Upstream hasn't set a
|
||||
// remote IP header so we're
|
||||
// probably not in a reverse
|
||||
// proxy setup, bail.
|
||||
return
|
||||
}
|
||||
|
||||
ip := net.ParseIP(clientIP)
|
||||
if !ip.IsPrivate() {
|
||||
// Upstream set a remote IP
|
||||
// header but final clientIP
|
||||
// isn't private, so upstream
|
||||
// is probably already trusted.
|
||||
// Don't inject suggestion.
|
||||
return
|
||||
}
|
||||
|
||||
// Private IP, guess if Docker.
|
||||
if dockerSubnet.Contains(ip) {
|
||||
// Suggest a CIDR that likely
|
||||
// covers this Docker subnet,
|
||||
// eg., 172.17.0.0 -> 172.17.255.255.
|
||||
trustedProxiesRec := clientIP + "/16"
|
||||
obj["trustedProxiesRec"] = trustedProxiesRec
|
||||
return
|
||||
}
|
||||
|
||||
// Private IP but we don't know
|
||||
// what it is. Suggest precise CIDR.
|
||||
trustedProxiesRec := clientIP + "/32"
|
||||
obj["trustedProxiesRec"] = trustedProxiesRec
|
||||
}
|
||||
|
||||
// dockerSubnet is a CIDR that lets one make hazy guesses
|
||||
// as to whether an address is within the ranges Docker
|
||||
// uses for subnets, ie., 172.16.0.0 -> 172.31.255.255.
|
||||
var dockerSubnet = func() *net.IPNet {
|
||||
_, subnet, err := net.ParseCIDR("172.16.0.0/12")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return subnet
|
||||
}()
|
||||
|
||||
// templateErrorPage renders the given
|
||||
// HTTP code, error, and request ID
|
||||
// within the standard error template.
|
||||
|
|
|
@ -94,7 +94,13 @@ func (suite *WebfingerStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.processor = testrig.NewTestProcessor(
|
||||
&suite.state,
|
||||
suite.federator,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
suite.mediaManager,
|
||||
)
|
||||
suite.webfingerModule = webfinger.New(suite.processor)
|
||||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||
|
|
|
@ -98,6 +98,7 @@ func (suite *WebfingerGetTestSuite) funkifyAccountDomain(host string, accountDom
|
|||
testrig.NewTestMediaManager(&suite.state),
|
||||
&suite.state,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
visibility.NewFilter(&suite.state),
|
||||
interaction.NewFilter(&suite.state),
|
||||
)
|
||||
|
|
2
internal/cache/cache.go
vendored
2
internal/cache/cache.go
vendored
|
@ -117,6 +117,8 @@ func (c *Caches) Init() {
|
|||
c.initUserMute()
|
||||
c.initUserMuteIDs()
|
||||
c.initWebfinger()
|
||||
c.initWebPushSubscription()
|
||||
c.initWebPushSubscriptionIDs()
|
||||
c.initVisibility()
|
||||
c.initStatusesFilterableFields()
|
||||
}
|
||||
|
|
53
internal/cache/db.go
vendored
53
internal/cache/db.go
vendored
|
@ -258,6 +258,15 @@ type DBCaches struct {
|
|||
|
||||
// UserMuteIDs provides access to the user mute IDs database cache.
|
||||
UserMuteIDs SliceCache[string]
|
||||
|
||||
// VAPIDKeyPair caches the server's VAPID key pair.
|
||||
VAPIDKeyPair atomic.Pointer[gtsmodel.VAPIDKeyPair]
|
||||
|
||||
// WebPushSubscription provides access to the gtsmodel WebPushSubscription database cache.
|
||||
WebPushSubscription StructCache[*gtsmodel.WebPushSubscription]
|
||||
|
||||
// WebPushSubscriptionIDs provides access to the Web Push subscription IDs database cache.
|
||||
WebPushSubscriptionIDs SliceCache[string]
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
|
@ -1579,9 +1588,10 @@ func (c *Caches) initToken() {
|
|||
{Fields: "Refresh"},
|
||||
{Fields: "ClientID", Multiple: true},
|
||||
},
|
||||
MaxSize: cap,
|
||||
IgnoreErr: ignoreErrors,
|
||||
Copy: copyF,
|
||||
MaxSize: cap,
|
||||
IgnoreErr: ignoreErrors,
|
||||
Copy: copyF,
|
||||
Invalidate: c.OnInvalidateToken,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1691,3 +1701,40 @@ func (c *Caches) initUserMuteIDs() {
|
|||
|
||||
c.DB.UserMuteIDs.Init(0, cap)
|
||||
}
|
||||
|
||||
func (c *Caches) initWebPushSubscription() {
|
||||
cap := calculateResultCacheMax(
|
||||
sizeofWebPushSubscription(), // model in-mem size.
|
||||
config.GetCacheWebPushSubscriptionMemRatio(),
|
||||
)
|
||||
|
||||
log.Infof(nil, "cache size = %d", cap)
|
||||
|
||||
copyF := func(s1 *gtsmodel.WebPushSubscription) *gtsmodel.WebPushSubscription {
|
||||
s2 := new(gtsmodel.WebPushSubscription)
|
||||
*s2 = *s1
|
||||
return s2
|
||||
}
|
||||
|
||||
c.DB.WebPushSubscription.Init(structr.CacheConfig[*gtsmodel.WebPushSubscription]{
|
||||
Indices: []structr.IndexConfig{
|
||||
{Fields: "ID"},
|
||||
{Fields: "TokenID"},
|
||||
{Fields: "AccountID", Multiple: true},
|
||||
},
|
||||
MaxSize: cap,
|
||||
IgnoreErr: ignoreErrors,
|
||||
Invalidate: c.OnInvalidateWebPushSubscription,
|
||||
Copy: copyF,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Caches) initWebPushSubscriptionIDs() {
|
||||
cap := calculateSliceCacheMax(
|
||||
config.GetCacheWebPushSubscriptionIDsMemRatio(),
|
||||
)
|
||||
|
||||
log.Infof(nil, "cache size = %d", cap)
|
||||
|
||||
c.DB.WebPushSubscriptionIDs.Init(0, cap)
|
||||
}
|
||||
|
|
10
internal/cache/invalidate.go
vendored
10
internal/cache/invalidate.go
vendored
|
@ -283,6 +283,11 @@ func (c *Caches) OnInvalidateStatusFave(fave *gtsmodel.StatusFave) {
|
|||
c.DB.StatusFaveIDs.Invalidate(fave.StatusID)
|
||||
}
|
||||
|
||||
func (c *Caches) OnInvalidateToken(token *gtsmodel.Token) {
|
||||
// Invalidate token's push subscription.
|
||||
c.DB.WebPushSubscription.Invalidate("ID", token.ID)
|
||||
}
|
||||
|
||||
func (c *Caches) OnInvalidateUser(user *gtsmodel.User) {
|
||||
// Invalidate local account ID cached visibility.
|
||||
c.Visibility.Invalidate("ItemID", user.AccountID)
|
||||
|
@ -296,3 +301,8 @@ func (c *Caches) OnInvalidateUserMute(mute *gtsmodel.UserMute) {
|
|||
// Invalidate source account's user mute lists.
|
||||
c.DB.UserMuteIDs.Invalidate(mute.AccountID)
|
||||
}
|
||||
|
||||
func (c *Caches) OnInvalidateWebPushSubscription(subscription *gtsmodel.WebPushSubscription) {
|
||||
// Invalidate source account's Web Push subscription list.
|
||||
c.DB.WebPushSubscriptionIDs.Invalidate(subscription.AccountID)
|
||||
}
|
||||
|
|
18
internal/cache/size.go
vendored
18
internal/cache/size.go
vendored
|
@ -66,6 +66,14 @@
|
|||
// be a serialized string of almost any type, so we pick a
|
||||
// nice serialized key size on the upper end of normal.
|
||||
sizeofResultKey = 2 * sizeofIDStr
|
||||
|
||||
// exampleWebPushAuth is a Base64-encoded 16-byte random auth secret.
|
||||
// This secret is consumed as Base64 by webpush-go.
|
||||
exampleWebPushAuth = "ZVxqlt5fzVgmSz2aqiA2XQ=="
|
||||
|
||||
// exampleWebPushP256dh is a Base64-encoded DH P-256 public key.
|
||||
// This secret is consumed as Base64 by webpush-go.
|
||||
exampleWebPushP256dh = "OrpejO16gV97uBXew/T0I7YoUv/CX8fz0z4g8RrQ+edXJqQPjX3XVSo2P0HhcCpCOR1+Dzj5LFcK9jYNqX7SBg=="
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -576,7 +584,7 @@ func sizeofMove() uintptr {
|
|||
func sizeofNotification() uintptr {
|
||||
return uintptr(size.Of(>smodel.Notification{
|
||||
ID: exampleID,
|
||||
NotificationType: gtsmodel.NotificationFave,
|
||||
NotificationType: gtsmodel.NotificationFavourite,
|
||||
CreatedAt: exampleTime,
|
||||
TargetAccountID: exampleID,
|
||||
OriginAccountID: exampleID,
|
||||
|
@ -821,3 +829,11 @@ func sizeofUserMute() uintptr {
|
|||
Notifications: util.Ptr(false),
|
||||
}))
|
||||
}
|
||||
|
||||
func sizeofWebPushSubscription() uintptr {
|
||||
return uintptr(size.Of(>smodel.WebPushSubscription{
|
||||
TokenID: exampleID,
|
||||
Auth: exampleWebPushAuth,
|
||||
P256dh: exampleWebPushP256dh,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -252,6 +252,8 @@ type CacheConfiguration struct {
|
|||
UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
|
||||
UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"`
|
||||
WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
|
||||
WebPushSubscriptionMemRatio float64 `name:"web-push-subscription-mem-ratio"`
|
||||
WebPushSubscriptionIDsMemRatio float64 `name:"web-push-subscription-ids-mem-ratio"`
|
||||
VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
|
||||
}
|
||||
|
||||
|
|
|
@ -213,6 +213,8 @@
|
|||
UserMuteMemRatio: 2,
|
||||
UserMuteIDsMemRatio: 3,
|
||||
WebfingerMemRatio: 0.1,
|
||||
WebPushSubscriptionMemRatio: 1,
|
||||
WebPushSubscriptionIDsMemRatio: 1,
|
||||
VisibilityMemRatio: 2,
|
||||
},
|
||||
|
||||
|
|
|
@ -4274,6 +4274,64 @@ func GetCacheWebfingerMemRatio() float64 { return global.GetCacheWebfingerMemRat
|
|||
// SetCacheWebfingerMemRatio safely sets the value for global configuration 'Cache.WebfingerMemRatio' field
|
||||
func SetCacheWebfingerMemRatio(v float64) { global.SetCacheWebfingerMemRatio(v) }
|
||||
|
||||
// GetCacheWebPushSubscriptionMemRatio safely fetches the Configuration value for state's 'Cache.WebPushSubscriptionMemRatio' field
|
||||
func (st *ConfigState) GetCacheWebPushSubscriptionMemRatio() (v float64) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.Cache.WebPushSubscriptionMemRatio
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetCacheWebPushSubscriptionMemRatio safely sets the Configuration value for state's 'Cache.WebPushSubscriptionMemRatio' field
|
||||
func (st *ConfigState) SetCacheWebPushSubscriptionMemRatio(v float64) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Cache.WebPushSubscriptionMemRatio = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// CacheWebPushSubscriptionMemRatioFlag returns the flag name for the 'Cache.WebPushSubscriptionMemRatio' field
|
||||
func CacheWebPushSubscriptionMemRatioFlag() string { return "cache-web-push-subscription-mem-ratio" }
|
||||
|
||||
// GetCacheWebPushSubscriptionMemRatio safely fetches the value for global configuration 'Cache.WebPushSubscriptionMemRatio' field
|
||||
func GetCacheWebPushSubscriptionMemRatio() float64 {
|
||||
return global.GetCacheWebPushSubscriptionMemRatio()
|
||||
}
|
||||
|
||||
// SetCacheWebPushSubscriptionMemRatio safely sets the value for global configuration 'Cache.WebPushSubscriptionMemRatio' field
|
||||
func SetCacheWebPushSubscriptionMemRatio(v float64) { global.SetCacheWebPushSubscriptionMemRatio(v) }
|
||||
|
||||
// GetCacheWebPushSubscriptionIDsMemRatio safely fetches the Configuration value for state's 'Cache.WebPushSubscriptionIDsMemRatio' field
|
||||
func (st *ConfigState) GetCacheWebPushSubscriptionIDsMemRatio() (v float64) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.Cache.WebPushSubscriptionIDsMemRatio
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetCacheWebPushSubscriptionIDsMemRatio safely sets the Configuration value for state's 'Cache.WebPushSubscriptionIDsMemRatio' field
|
||||
func (st *ConfigState) SetCacheWebPushSubscriptionIDsMemRatio(v float64) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Cache.WebPushSubscriptionIDsMemRatio = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// CacheWebPushSubscriptionIDsMemRatioFlag returns the flag name for the 'Cache.WebPushSubscriptionIDsMemRatio' field
|
||||
func CacheWebPushSubscriptionIDsMemRatioFlag() string {
|
||||
return "cache-web-push-subscription-ids-mem-ratio"
|
||||
}
|
||||
|
||||
// GetCacheWebPushSubscriptionIDsMemRatio safely fetches the value for global configuration 'Cache.WebPushSubscriptionIDsMemRatio' field
|
||||
func GetCacheWebPushSubscriptionIDsMemRatio() float64 {
|
||||
return global.GetCacheWebPushSubscriptionIDsMemRatio()
|
||||
}
|
||||
|
||||
// SetCacheWebPushSubscriptionIDsMemRatio safely sets the value for global configuration 'Cache.WebPushSubscriptionIDsMemRatio' field
|
||||
func SetCacheWebPushSubscriptionIDsMemRatio(v float64) {
|
||||
global.SetCacheWebPushSubscriptionIDsMemRatio(v)
|
||||
}
|
||||
|
||||
// GetCacheVisibilityMemRatio safely fetches the Configuration value for state's 'Cache.VisibilityMemRatio' field
|
||||
func (st *ConfigState) GetCacheVisibilityMemRatio() (v float64) {
|
||||
st.mutex.RLock()
|
||||
|
|
|
@ -36,39 +36,42 @@ type Application interface {
|
|||
// DeleteApplicationByClientID deletes the application with corresponding client_id value from the database.
|
||||
DeleteApplicationByClientID(ctx context.Context, clientID string) error
|
||||
|
||||
// GetClientByID ...
|
||||
// GetClientByID fetches the application client from database with ID.
|
||||
GetClientByID(ctx context.Context, id string) (*gtsmodel.Client, error)
|
||||
|
||||
// PutClient ...
|
||||
// PutClient puts the given application client in the database.
|
||||
PutClient(ctx context.Context, client *gtsmodel.Client) error
|
||||
|
||||
// DeleteClientByID ...
|
||||
// DeleteClientByID deletes the application client from database with ID.
|
||||
DeleteClientByID(ctx context.Context, id string) error
|
||||
|
||||
// GetAllTokens ...
|
||||
// GetAllTokens fetches all client oauth tokens from database.
|
||||
GetAllTokens(ctx context.Context) ([]*gtsmodel.Token, error)
|
||||
|
||||
// GetTokenByCode ...
|
||||
// GetTokenByID fetches the client oauth token from database with ID.
|
||||
GetTokenByID(ctx context.Context, id string) (*gtsmodel.Token, error)
|
||||
|
||||
// GetTokenByCode fetches the client oauth token from database with code.
|
||||
GetTokenByCode(ctx context.Context, code string) (*gtsmodel.Token, error)
|
||||
|
||||
// GetTokenByAccess ...
|
||||
// GetTokenByAccess fetches the client oauth token from database with access code.
|
||||
GetTokenByAccess(ctx context.Context, access string) (*gtsmodel.Token, error)
|
||||
|
||||
// GetTokenByRefresh ...
|
||||
// GetTokenByRefresh fetches the client oauth token from database with refresh code.
|
||||
GetTokenByRefresh(ctx context.Context, refresh string) (*gtsmodel.Token, error)
|
||||
|
||||
// PutToken ...
|
||||
// PutToken puts given client oauth token in the database.
|
||||
PutToken(ctx context.Context, token *gtsmodel.Token) error
|
||||
|
||||
// DeleteTokenByID ...
|
||||
// DeleteTokenByID deletes client oauth token from database with ID.
|
||||
DeleteTokenByID(ctx context.Context, id string) error
|
||||
|
||||
// DeleteTokenByCode ...
|
||||
// DeleteTokenByCode deletes client oauth token from database with code.
|
||||
DeleteTokenByCode(ctx context.Context, code string) error
|
||||
|
||||
// DeleteTokenByAccess ...
|
||||
// DeleteTokenByAccess deletes client oauth token from database with access code.
|
||||
DeleteTokenByAccess(ctx context.Context, access string) error
|
||||
|
||||
// DeleteTokenByRefresh ...
|
||||
// DeleteTokenByRefresh deletes client oauth token from database with refresh code.
|
||||
DeleteTokenByRefresh(ctx context.Context, refresh string) error
|
||||
}
|
||||
|
|
|
@ -899,15 +899,19 @@ func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, li
|
|||
|
||||
if excludeReplies {
|
||||
q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
// We're excluding replies so
|
||||
// only include posts if they:
|
||||
return q.
|
||||
// Do include self replies (threads), but
|
||||
// don't include replies to other people.
|
||||
Where("? = ?", bun.Ident("status.in_reply_to_account_id"), accountID).
|
||||
WhereOr("? IS NULL", bun.Ident("status.in_reply_to_uri"))
|
||||
// Don't reply to anything OR
|
||||
Where("? IS NULL", bun.Ident("status.in_reply_to_uri")).
|
||||
// reply to self AND don't mention
|
||||
// anyone (ie., self-reply threads).
|
||||
WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
q = q.Where("? = ?", bun.Ident("status.in_reply_to_account_id"), accountID)
|
||||
q = whereArrayIsNullOrEmpty(q, bun.Ident("status.mentions"))
|
||||
return q
|
||||
})
|
||||
})
|
||||
// Don't include replies that mention other people:
|
||||
// for example, an account's reply to its own reply to someone else.
|
||||
q = whereArrayIsNullOrEmpty(q, bun.Ident("status.mentions"))
|
||||
}
|
||||
|
||||
if excludeReblogs {
|
||||
|
|
|
@ -174,6 +174,16 @@ func(uncached []string) ([]*gtsmodel.Token, error) {
|
|||
return tokens, nil
|
||||
}
|
||||
|
||||
func (a *applicationDB) GetTokenByID(ctx context.Context, code string) (*gtsmodel.Token, error) {
|
||||
return a.getTokenBy(
|
||||
"ID",
|
||||
func(t *gtsmodel.Token) error {
|
||||
return a.db.NewSelect().Model(t).Where("? = ?", bun.Ident("id"), code).Scan(ctx)
|
||||
},
|
||||
code,
|
||||
)
|
||||
}
|
||||
|
||||
func (a *applicationDB) GetTokenByCode(ctx context.Context, code string) (*gtsmodel.Token, error) {
|
||||
return a.getTokenBy(
|
||||
"Code",
|
||||
|
|
|
@ -88,6 +88,7 @@ type DBService struct {
|
|||
db.Timeline
|
||||
db.User
|
||||
db.Tombstone
|
||||
db.WebPush
|
||||
db.WorkerTask
|
||||
db *bun.DB
|
||||
}
|
||||
|
@ -301,6 +302,10 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
|
|||
db: db,
|
||||
state: state,
|
||||
},
|
||||
WebPush: &webPushDB{
|
||||
db: db,
|
||||
state: state,
|
||||
},
|
||||
WorkerTask: &workerTaskDB{
|
||||
db: db,
|
||||
},
|
||||
|
|
|
@ -149,10 +149,10 @@ func notificationEnumMapping[T ~string]() map[T]new_gtsmodel.NotificationType {
|
|||
T(old_gtsmodel.NotificationFollowRequest): new_gtsmodel.NotificationFollowRequest,
|
||||
T(old_gtsmodel.NotificationMention): new_gtsmodel.NotificationMention,
|
||||
T(old_gtsmodel.NotificationReblog): new_gtsmodel.NotificationReblog,
|
||||
T(old_gtsmodel.NotificationFave): new_gtsmodel.NotificationFave,
|
||||
T(old_gtsmodel.NotificationFave): new_gtsmodel.NotificationFavourite,
|
||||
T(old_gtsmodel.NotificationPoll): new_gtsmodel.NotificationPoll,
|
||||
T(old_gtsmodel.NotificationStatus): new_gtsmodel.NotificationStatus,
|
||||
T(old_gtsmodel.NotificationSignup): new_gtsmodel.NotificationSignup,
|
||||
T(old_gtsmodel.NotificationSignup): new_gtsmodel.NotificationAdminSignup,
|
||||
T(old_gtsmodel.NotificationPendingFave): new_gtsmodel.NotificationPendingFave,
|
||||
T(old_gtsmodel.NotificationPendingReply): new_gtsmodel.NotificationPendingReply,
|
||||
T(old_gtsmodel.NotificationPendingReblog): new_gtsmodel.NotificationPendingReblog,
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model(>smodel.VAPIDKeyPair{}).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model(>smodel.WebPushSubscription{}).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Model(>smodel.WebPushSubscription{}).
|
||||
Index("web_push_subscriptions_account_id_idx").
|
||||
Column("account_id").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// This file exists because tobi is a silly billy and named two migration files
|
||||
// with the same date part, so the latter migration didn't always run properly.
|
||||
// The file just repeats migrations in 20250119112745_domain_permission_subscriptions.go,
|
||||
// which will be a noop in most cases, but will fix some issues for those who
|
||||
// were running snapshots between GtS v0.17.0 and GtS v0.18.0.
|
||||
//
|
||||
// See https://github.com/superseriousbusiness/gotosocial/pull/3679.
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
// Create `domain_permission_subscriptions`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionSubscription)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes. Indices. Indie sexes.
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table("domain_permission_subscriptions").
|
||||
// Filter on permission type.
|
||||
Index("domain_permission_subscriptions_permission_type_idx").
|
||||
Column("permission_type").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table("domain_permission_subscriptions").
|
||||
// Sort by priority DESC.
|
||||
Index("domain_permission_subscriptions_priority_order_idx").
|
||||
ColumnExpr("? DESC", bun.Ident("priority")).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
var expression string
|
||||
switch db.Dialect().Name() {
|
||||
case dialect.PG:
|
||||
expression = "(mentions IS NULL OR CARDINALITY(mentions) = 0)"
|
||||
case dialect.SQLite:
|
||||
expression = "(mentions IS NULL OR json_array_length(mentions) = 0)"
|
||||
default:
|
||||
panic("db conn was neither pg not sqlite")
|
||||
}
|
||||
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
log.Info(ctx,
|
||||
"removing previous statuses_account_view_idx and reindexing statuses; "+
|
||||
"this may take a few minutes, please don't interrupt this migration",
|
||||
)
|
||||
|
||||
// Remove old index with columns
|
||||
// in really awkward order.
|
||||
if _, err := tx.
|
||||
NewDropIndex().
|
||||
Model((*gtsmodel.Status)(nil)).
|
||||
Index("statuses_account_view_idx").
|
||||
IfExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create new index with
|
||||
// columns in desired order.
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Model((*gtsmodel.Status)(nil)).
|
||||
Index("statuses_account_view_idx").
|
||||
Column(
|
||||
"account_id",
|
||||
"in_reply_to_uri",
|
||||
"in_reply_to_account_id",
|
||||
).
|
||||
ColumnExpr(expression).
|
||||
ColumnExpr("id DESC").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ func (suite *NotificationTestSuite) spamNotifs() {
|
|||
|
||||
notif := >smodel.Notification{
|
||||
ID: notifID,
|
||||
NotificationType: gtsmodel.NotificationFave,
|
||||
NotificationType: gtsmodel.NotificationFavourite,
|
||||
CreatedAt: time.Now(),
|
||||
TargetAccountID: targetAccountID,
|
||||
OriginAccountID: originAccountID,
|
||||
|
|
270
internal/db/bundb/webpush.go
Normal file
270
internal/db/bundb/webpush.go
Normal file
|
@ -0,0 +1,270 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bundb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
webpushgo "github.com/SherClockHolmes/webpush-go"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type webPushDB struct {
|
||||
db *bun.DB
|
||||
state *state.State
|
||||
}
|
||||
|
||||
func (w *webPushDB) GetVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error) {
|
||||
var err error
|
||||
|
||||
vapidKeyPair, err := w.getVAPIDKeyPair(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if vapidKeyPair != nil {
|
||||
return vapidKeyPair, nil
|
||||
}
|
||||
|
||||
// If there aren't any, generate new ones.
|
||||
vapidKeyPair = >smodel.VAPIDKeyPair{}
|
||||
if vapidKeyPair.Private, vapidKeyPair.Public, err = webpushgo.GenerateVAPIDKeys(); err != nil {
|
||||
return nil, gtserror.Newf("error generating VAPID key pair: %w", err)
|
||||
}
|
||||
|
||||
// Store the keys in the database.
|
||||
if _, err = w.db.NewInsert().
|
||||
Model(vapidKeyPair).
|
||||
Exec(ctx); // nocollapse
|
||||
err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
// Multiple concurrent attempts to generate new keys, and this one didn't win.
|
||||
// Get the results of the one that did.
|
||||
return w.getVAPIDKeyPair(ctx)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache the keys.
|
||||
w.state.Caches.DB.VAPIDKeyPair.Store(vapidKeyPair)
|
||||
|
||||
return vapidKeyPair, nil
|
||||
}
|
||||
|
||||
// getVAPIDKeyPair gets an existing VAPID key pair from cache or DB.
|
||||
// If there is no existing VAPID key pair, it returns nil, with no error.
|
||||
func (w *webPushDB) getVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error) {
|
||||
// Look for cached keys.
|
||||
vapidKeyPair := w.state.Caches.DB.VAPIDKeyPair.Load()
|
||||
if vapidKeyPair != nil {
|
||||
return vapidKeyPair, nil
|
||||
}
|
||||
|
||||
// Look for previously generated keys in the database.
|
||||
vapidKeyPair = >smodel.VAPIDKeyPair{}
|
||||
if err := w.db.NewSelect().
|
||||
Model(vapidKeyPair).
|
||||
Limit(1).
|
||||
Scan(ctx); // nocollapse
|
||||
err != nil {
|
||||
if errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vapidKeyPair, nil
|
||||
}
|
||||
|
||||
func (w *webPushDB) DeleteVAPIDKeyPair(ctx context.Context) error {
|
||||
// Delete any existing keys.
|
||||
if _, err := w.db.NewTruncateTable().
|
||||
Model((*gtsmodel.VAPIDKeyPair)(nil)).
|
||||
Exec(ctx); // nocollapse
|
||||
err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the key cache.
|
||||
w.state.Caches.DB.VAPIDKeyPair.Store(nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *webPushDB) GetWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) (*gtsmodel.WebPushSubscription, error) {
|
||||
subscription, err := w.state.Caches.DB.WebPushSubscription.LoadOne(
|
||||
"TokenID",
|
||||
func() (*gtsmodel.WebPushSubscription, error) {
|
||||
var subscription gtsmodel.WebPushSubscription
|
||||
err := w.db.
|
||||
NewSelect().
|
||||
Model(&subscription).
|
||||
Where("? = ?", bun.Ident("token_id"), tokenID).
|
||||
Scan(ctx)
|
||||
return &subscription, err
|
||||
},
|
||||
tokenID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (w *webPushDB) PutWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription) error {
|
||||
return w.state.Caches.DB.WebPushSubscription.Store(subscription, func() error {
|
||||
_, err := w.db.NewInsert().
|
||||
Model(subscription).
|
||||
Exec(ctx)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (w *webPushDB) UpdateWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription, columns ...string) error {
|
||||
// Update database.
|
||||
result, err := w.db.
|
||||
NewUpdate().
|
||||
Model(subscription).
|
||||
Column(columns...).
|
||||
Where("? = ?", bun.Ident("id"), subscription.ID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting updated row count: %w", err)
|
||||
}
|
||||
if rowsAffected == 0 {
|
||||
return db.ErrNoEntries
|
||||
}
|
||||
|
||||
// Update cache.
|
||||
w.state.Caches.DB.WebPushSubscription.Put(subscription)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *webPushDB) DeleteWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) error {
|
||||
// Deleted partial model for cache invalidation.
|
||||
var deleted gtsmodel.WebPushSubscription
|
||||
|
||||
// Delete subscription, returning subset of columns used by invalidation hook.
|
||||
if _, err := w.db.NewDelete().
|
||||
Model(&deleted).
|
||||
Where("? = ?", bun.Ident("token_id"), tokenID).
|
||||
Returning("?", bun.Ident("account_id")).
|
||||
Exec(ctx); // nocollapse
|
||||
err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Invalidate cached subscription by token ID.
|
||||
w.state.Caches.DB.WebPushSubscription.Invalidate("TokenID", tokenID)
|
||||
|
||||
// Call invalidate hook directly.
|
||||
w.state.Caches.OnInvalidateWebPushSubscription(&deleted)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *webPushDB) GetWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) ([]*gtsmodel.WebPushSubscription, error) {
|
||||
// Fetch IDs of all subscriptions created by this account.
|
||||
subscriptionIDs, err := loadPagedIDs(&w.state.Caches.DB.WebPushSubscriptionIDs, accountID, nil, func() ([]string, error) {
|
||||
// Subscription IDs not in cache. Perform DB query.
|
||||
var subscriptionIDs []string
|
||||
if _, err := w.db.
|
||||
NewSelect().
|
||||
Model((*gtsmodel.WebPushSubscription)(nil)).
|
||||
Column("id").
|
||||
Where("? = ?", bun.Ident("account_id"), accountID).
|
||||
Order("id DESC").
|
||||
Exec(ctx, &subscriptionIDs); // nocollapse
|
||||
err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, err
|
||||
}
|
||||
return subscriptionIDs, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(subscriptionIDs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get each subscription by ID from the cache or DB.
|
||||
subscriptions, err := w.state.Caches.DB.WebPushSubscription.LoadIDs("ID",
|
||||
subscriptionIDs,
|
||||
func(uncached []string) ([]*gtsmodel.WebPushSubscription, error) {
|
||||
subscriptions := make([]*gtsmodel.WebPushSubscription, 0, len(uncached))
|
||||
if err := w.db.
|
||||
NewSelect().
|
||||
Model(&subscriptions).
|
||||
Where("? IN (?)", bun.Ident("id"), bun.In(uncached)).
|
||||
Scan(ctx); // nocollapse
|
||||
err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subscriptions, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Put the subscription structs in the same order as the filter IDs.
|
||||
xslices.OrderBy(
|
||||
subscriptions,
|
||||
subscriptionIDs,
|
||||
func(subscription *gtsmodel.WebPushSubscription) string {
|
||||
return subscription.ID
|
||||
},
|
||||
)
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (w *webPushDB) DeleteWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) error {
|
||||
// Deleted partial models for cache invalidation.
|
||||
var deleted []*gtsmodel.WebPushSubscription
|
||||
|
||||
// Delete subscriptions, returning subset of columns.
|
||||
if _, err := w.db.NewDelete().
|
||||
Model(&deleted).
|
||||
Where("? = ?", bun.Ident("account_id"), accountID).
|
||||
Returning("?", bun.Ident("account_id")).
|
||||
Exec(ctx); // nocollapse
|
||||
err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Invalidate cached subscriptions by account ID.
|
||||
w.state.Caches.DB.WebPushSubscription.Invalidate("AccountID", accountID)
|
||||
|
||||
// Call invalidate hooks directly in case those entries weren't cached.
|
||||
for _, subscription := range deleted {
|
||||
w.state.Caches.OnInvalidateWebPushSubscription(subscription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
81
internal/db/bundb/webpush_test.go
Normal file
81
internal/db/bundb/webpush_test.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bundb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type WebPushTestSuite struct {
|
||||
BunDBStandardTestSuite
|
||||
}
|
||||
|
||||
// Get the text fixture VAPID key pair.
|
||||
func (suite *WebPushTestSuite) TestGetVAPIDKeyPair() {
|
||||
ctx := context.Background()
|
||||
|
||||
vapidKeyPair, err := suite.db.GetVAPIDKeyPair(ctx)
|
||||
suite.NoError(err)
|
||||
if !suite.NotNil(vapidKeyPair) {
|
||||
suite.FailNow("Got a nil VAPID key pair, can't continue")
|
||||
}
|
||||
suite.NotEmpty(vapidKeyPair.Private)
|
||||
suite.NotEmpty(vapidKeyPair.Public)
|
||||
|
||||
// Get it again. It should be the same one.
|
||||
vapidKeyPair2, err := suite.db.GetVAPIDKeyPair(ctx)
|
||||
suite.NoError(err)
|
||||
if suite.NotNil(vapidKeyPair2) {
|
||||
suite.Equal(vapidKeyPair.Private, vapidKeyPair2.Private)
|
||||
suite.Equal(vapidKeyPair.Public, vapidKeyPair2.Public)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a VAPID key pair when there isn't one.
|
||||
func (suite *WebPushTestSuite) TestGenerateVAPIDKeyPair() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Delete the text fixture VAPID key pair.
|
||||
if err := suite.db.DeleteVAPIDKeyPair(ctx); !suite.NoError(err) {
|
||||
suite.FailNow("Test setup failed: DB error deleting fixture VAPID key pair: %v", err)
|
||||
}
|
||||
|
||||
// Get a new one.
|
||||
vapidKeyPair, err := suite.db.GetVAPIDKeyPair(ctx)
|
||||
suite.NoError(err)
|
||||
if !suite.NotNil(vapidKeyPair) {
|
||||
suite.FailNow("Got a nil VAPID key pair, can't continue")
|
||||
}
|
||||
suite.NotEmpty(vapidKeyPair.Private)
|
||||
suite.NotEmpty(vapidKeyPair.Public)
|
||||
|
||||
// Get it again. It should be the same one.
|
||||
vapidKeyPair2, err := suite.db.GetVAPIDKeyPair(ctx)
|
||||
suite.NoError(err)
|
||||
if suite.NotNil(vapidKeyPair2) {
|
||||
suite.Equal(vapidKeyPair.Private, vapidKeyPair2.Private)
|
||||
suite.Equal(vapidKeyPair.Public, vapidKeyPair2.Public)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebPushTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(WebPushTestSuite))
|
||||
}
|
|
@ -58,5 +58,6 @@ type DB interface {
|
|||
Timeline
|
||||
User
|
||||
Tombstone
|
||||
WebPush
|
||||
WorkerTask
|
||||
}
|
||||
|
|
54
internal/db/webpush.go
Normal file
54
internal/db/webpush.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// WebPush contains functions related to Web Push notifications.
|
||||
type WebPush interface {
|
||||
// GetVAPIDKeyPair retrieves the server's existing VAPID key pair, if there is one.
|
||||
// If there isn't one, it generates a new one, stores it, and returns that.
|
||||
GetVAPIDKeyPair(ctx context.Context) (*gtsmodel.VAPIDKeyPair, error)
|
||||
|
||||
// DeleteVAPIDKeyPair deletes the server's VAPID key pair.
|
||||
DeleteVAPIDKeyPair(ctx context.Context) error
|
||||
|
||||
// GetWebPushSubscriptionByTokenID retrieves an access token's Web Push subscription.
|
||||
// There may not be one, in which case an error will be returned.
|
||||
GetWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) (*gtsmodel.WebPushSubscription, error)
|
||||
|
||||
// PutWebPushSubscription creates an access token's Web Push subscription.
|
||||
PutWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription) error
|
||||
|
||||
// UpdateWebPushSubscription updates an access token's Web Push subscription.
|
||||
// There may not be one, in which case an error will be returned.
|
||||
UpdateWebPushSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription, columns ...string) error
|
||||
|
||||
// DeleteWebPushSubscriptionByTokenID deletes an access token's Web Push subscription, if there is one.
|
||||
DeleteWebPushSubscriptionByTokenID(ctx context.Context, tokenID string) error
|
||||
|
||||
// GetWebPushSubscriptionsByAccountID retrieves an account's list of Web Push subscriptions.
|
||||
GetWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) ([]*gtsmodel.WebPushSubscription, error)
|
||||
|
||||
// DeleteWebPushSubscriptionsByAccountID deletes an account's list of Web Push subscriptions.
|
||||
DeleteWebPushSubscriptionsByAccountID(ctx context.Context, accountID string) error
|
||||
}
|
|
@ -29,6 +29,7 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
)
|
||||
|
||||
func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
|
||||
|
@ -89,6 +90,18 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo)
|
|||
return err
|
||||
}
|
||||
|
||||
// UNDO ANNOUNCE
|
||||
case ap.ActivityAnnounce:
|
||||
if err := f.undoAnnounce(
|
||||
ctx,
|
||||
receivingAcct,
|
||||
requestingAcct,
|
||||
undo,
|
||||
asType,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// UNHANDLED
|
||||
default:
|
||||
log.Debugf(ctx, "unhandled object type: %s", name)
|
||||
|
@ -323,3 +336,72 @@ func (f *federatingDB) undoBlock(
|
|||
log.Debug(ctx, "Block undone")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *federatingDB) undoAnnounce(
|
||||
ctx context.Context,
|
||||
receivingAcct *gtsmodel.Account,
|
||||
requestingAcct *gtsmodel.Account,
|
||||
undo vocab.ActivityStreamsUndo,
|
||||
t vocab.Type,
|
||||
) error {
|
||||
asAnnounce, ok := t.(vocab.ActivityStreamsAnnounce)
|
||||
if !ok {
|
||||
err := fmt.Errorf("%T not parseable as vocab.ActivityStreamsAnnounce", t)
|
||||
return gtserror.SetMalformed(err)
|
||||
}
|
||||
|
||||
// Make sure the Undo actor owns the
|
||||
// Announce they're trying to undo.
|
||||
if !sameActor(
|
||||
undo.GetActivityStreamsActor(),
|
||||
asAnnounce.GetActivityStreamsActor(),
|
||||
) {
|
||||
// Ignore this Activity.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert AS Announce to *gtsmodel.Status,
|
||||
// retrieving origin account + target status.
|
||||
boost, isNew, err := f.converter.ASAnnounceToStatus(
|
||||
// Use barebones as we don't
|
||||
// need to populate attachments
|
||||
// on boosted status, mentions, etc.
|
||||
gtscontext.SetBarebones(ctx),
|
||||
asAnnounce,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("error converting AS Announce to boost: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if boost == nil {
|
||||
// We were missing origin or
|
||||
// target(s) for this Announce,
|
||||
// so we cannot Undo anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
if isNew {
|
||||
// We hadn't seen this boost
|
||||
// before anyway, so there's
|
||||
// nothing to Undo.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure requester == announcer.
|
||||
if boost.AccountID != requestingAcct.ID {
|
||||
const text = "requestingAcct was not Block origin"
|
||||
return gtserror.NewErrorForbidden(errors.New(text), text)
|
||||
}
|
||||
|
||||
// Looks valid. Process side effects asynchronously.
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: boost,
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -75,12 +75,12 @@ func (suite *FederatorStandardTestSuite) SetupTest() {
|
|||
|
||||
// Ensure it's possible to deref
|
||||
// main key of foss satan.
|
||||
fossSatanPerson, err := suite.typeconverter.AccountToAS(context.Background(), suite.testAccounts["remote_account_1"])
|
||||
fossSatanAS, err := suite.typeconverter.AccountToAS(context.Background(), suite.testAccounts["remote_account_1"])
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.httpClient = testrig.NewMockHTTPClient(nil, "../../testrig/media", fossSatanPerson)
|
||||
suite.httpClient = testrig.NewMockHTTPClient(nil, "../../testrig/media", fossSatanAS)
|
||||
suite.httpClient.TestRemotePeople = testrig.NewTestFediPeople()
|
||||
suite.httpClient.TestRemoteStatuses = testrig.NewTestFediStatuses()
|
||||
|
||||
|
|
|
@ -48,13 +48,16 @@ type Notification struct {
|
|||
NotificationFollowRequest NotificationType = 2 // NotificationFollowRequest -- someone requested to follow you
|
||||
NotificationMention NotificationType = 3 // NotificationMention -- someone mentioned you in their status
|
||||
NotificationReblog NotificationType = 4 // NotificationReblog -- someone boosted one of your statuses
|
||||
NotificationFave NotificationType = 5 // NotificationFave -- someone faved/liked one of your statuses
|
||||
NotificationFavourite NotificationType = 5 // NotificationFavourite -- someone faved/liked one of your statuses
|
||||
NotificationPoll NotificationType = 6 // NotificationPoll -- a poll you voted in or created has ended
|
||||
NotificationStatus NotificationType = 7 // NotificationStatus -- someone you enabled notifications for has posted a status.
|
||||
NotificationSignup NotificationType = 8 // NotificationSignup -- someone has submitted a new account sign-up to the instance.
|
||||
NotificationPendingFave NotificationType = 9 // Someone has faved a status of yours, which requires approval by you.
|
||||
NotificationPendingReply NotificationType = 10 // Someone has replied to a status of yours, which requires approval by you.
|
||||
NotificationPendingReblog NotificationType = 11 // Someone has boosted a status of yours, which requires approval by you.
|
||||
NotificationAdminSignup NotificationType = 8 // NotificationAdminSignup -- someone has submitted a new account sign-up to the instance.
|
||||
NotificationPendingFave NotificationType = 9 // NotificationPendingFave -- Someone has faved a status of yours, which requires approval by you.
|
||||
NotificationPendingReply NotificationType = 10 // NotificationPendingReply -- Someone has replied to a status of yours, which requires approval by you.
|
||||
NotificationPendingReblog NotificationType = 11 // NotificationPendingReblog -- Someone has boosted a status of yours, which requires approval by you.
|
||||
NotificationAdminReport NotificationType = 12 // NotificationAdminReport -- someone has submitted a new report to the instance.
|
||||
NotificationUpdate NotificationType = 13 // NotificationUpdate -- someone has edited their status.
|
||||
NotificationTypeNumValues NotificationType = 14 // NotificationTypeNumValues -- 1 + number of max notification type
|
||||
)
|
||||
|
||||
// String returns a stringified, frontend API compatible form of NotificationType.
|
||||
|
@ -68,13 +71,13 @@ func (t NotificationType) String() string {
|
|||
return "mention"
|
||||
case NotificationReblog:
|
||||
return "reblog"
|
||||
case NotificationFave:
|
||||
case NotificationFavourite:
|
||||
return "favourite"
|
||||
case NotificationPoll:
|
||||
return "poll"
|
||||
case NotificationStatus:
|
||||
return "status"
|
||||
case NotificationSignup:
|
||||
case NotificationAdminSignup:
|
||||
return "admin.sign_up"
|
||||
case NotificationPendingFave:
|
||||
return "pending.favourite"
|
||||
|
@ -82,6 +85,10 @@ func (t NotificationType) String() string {
|
|||
return "pending.reply"
|
||||
case NotificationPendingReblog:
|
||||
return "pending.reblog"
|
||||
case NotificationAdminReport:
|
||||
return "admin.report"
|
||||
case NotificationUpdate:
|
||||
return "update"
|
||||
default:
|
||||
panic("invalid notification type")
|
||||
}
|
||||
|
@ -99,19 +106,23 @@ func ParseNotificationType(in string) NotificationType {
|
|||
case "reblog":
|
||||
return NotificationReblog
|
||||
case "favourite":
|
||||
return NotificationFave
|
||||
return NotificationFavourite
|
||||
case "poll":
|
||||
return NotificationPoll
|
||||
case "status":
|
||||
return NotificationStatus
|
||||
case "admin.sign_up":
|
||||
return NotificationSignup
|
||||
return NotificationAdminSignup
|
||||
case "pending.favourite":
|
||||
return NotificationPendingFave
|
||||
case "pending.reply":
|
||||
return NotificationPendingReply
|
||||
case "pending.reblog":
|
||||
return NotificationPendingReblog
|
||||
case "admin.report":
|
||||
return NotificationAdminReport
|
||||
case "update":
|
||||
return NotificationUpdate
|
||||
default:
|
||||
return NotificationUnknown
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
|
||||
import "time"
|
||||
|
||||
// Token is a translation of the gotosocial token with the ExpiresIn fields replaced with ExpiresAt.
|
||||
// Token is a translation of the gotosocial token
|
||||
// with the ExpiresIn fields replaced with ExpiresAt.
|
||||
type Token struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
|
|
28
internal/gtsmodel/vapidkeypair.go
Normal file
28
internal/gtsmodel/vapidkeypair.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
// VAPIDKeyPair represents the instance's VAPID keys (stored as Base64 strings).
|
||||
// This table should only ever have one entry, with a known ID of 0.
|
||||
//
|
||||
// See: https://datatracker.ietf.org/doc/html/rfc8292
|
||||
type VAPIDKeyPair struct {
|
||||
ID int `bun:",pk,notnull"`
|
||||
Public string `bun:",notnull,nullzero"`
|
||||
Private string `bun:",notnull,nullzero"`
|
||||
}
|
82
internal/gtsmodel/webpushsubscription.go
Normal file
82
internal/gtsmodel/webpushsubscription.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package gtsmodel
|
||||
|
||||
// WebPushSubscription represents an access token's Web Push subscription.
|
||||
// There can be at most one per access token.
|
||||
type WebPushSubscription struct {
|
||||
// ID of this subscription in the database.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero"`
|
||||
|
||||
// AccountID of the local account that created this subscription.
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
|
||||
// TokenID is the ID of the associated access token.
|
||||
// There can be at most one subscription for any given access token,
|
||||
TokenID string `bun:"type:CHAR(26),nullzero,notnull,unique"`
|
||||
|
||||
// Endpoint is the URL receiving Web Push notifications for this subscription.
|
||||
Endpoint string `bun:",nullzero,notnull"`
|
||||
|
||||
// Auth is a Base64-encoded authentication secret.
|
||||
Auth string `bun:",nullzero,notnull"`
|
||||
|
||||
// P256dh is a Base64-encoded Diffie-Hellman public key on the P-256 elliptic curve.
|
||||
P256dh string `bun:",nullzero,notnull"`
|
||||
|
||||
// NotificationFlags controls which notifications are delivered to a given subscription.
|
||||
// Corresponds to model.PushSubscriptionAlerts.
|
||||
NotificationFlags WebPushSubscriptionNotificationFlags `bun:",notnull"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionNotificationFlags is a bitfield representation of a set of NotificationType.
|
||||
type WebPushSubscriptionNotificationFlags int64
|
||||
|
||||
// WebPushSubscriptionNotificationFlagsFromSlice packs a slice of NotificationType into a WebPushSubscriptionNotificationFlags.
|
||||
func WebPushSubscriptionNotificationFlagsFromSlice(notificationTypes []NotificationType) WebPushSubscriptionNotificationFlags {
|
||||
var n WebPushSubscriptionNotificationFlags
|
||||
for _, notificationType := range notificationTypes {
|
||||
n.Set(notificationType, true)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ToSlice unpacks a WebPushSubscriptionNotificationFlags into a slice of NotificationType.
|
||||
func (n *WebPushSubscriptionNotificationFlags) ToSlice() []NotificationType {
|
||||
notificationTypes := make([]NotificationType, 0, NotificationTypeNumValues)
|
||||
for notificationType := NotificationUnknown; notificationType < NotificationTypeNumValues; notificationType++ {
|
||||
if n.Get(notificationType) {
|
||||
notificationTypes = append(notificationTypes, notificationType)
|
||||
}
|
||||
}
|
||||
return notificationTypes
|
||||
}
|
||||
|
||||
// Get tests to see if a given NotificationType is included in this set of flags.
|
||||
func (n *WebPushSubscriptionNotificationFlags) Get(notificationType NotificationType) bool {
|
||||
return *n&(1<<notificationType) != 0
|
||||
}
|
||||
|
||||
// Set adds or removes a given NotificationType to or from this set of flags.
|
||||
func (n *WebPushSubscriptionNotificationFlags) Set(notificationType NotificationType, value bool) {
|
||||
if value {
|
||||
*n |= 1 << notificationType
|
||||
} else {
|
||||
*n &= ^(1 << notificationType)
|
||||
}
|
||||
}
|
|
@ -178,6 +178,9 @@ func New(cfg Config) *Client {
|
|||
return &c
|
||||
}
|
||||
|
||||
// RoundTrip allows httpclient.Client{} to be used as an http.Transport{}, just calling Client{}.Do().
|
||||
func (c *Client) RoundTrip(r *http.Request) (rsp *http.Response, err error) { return c.Do(r) }
|
||||
|
||||
// Do will essentially perform http.Client{}.Do() with retry-backoff functionality.
|
||||
func (c *Client) Do(r *http.Request) (rsp *http.Response, err error) {
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ func (p *Processor) Delete(
|
|||
}
|
||||
|
||||
// deleteUserAndTokensForAccount deletes the gtsmodel.User and
|
||||
// any OAuth tokens and applications for the given account.
|
||||
// any OAuth tokens, applications, and Web Push subscriptions for the given account.
|
||||
//
|
||||
// Callers to this function should already have checked that
|
||||
// this is a local account, or else it won't have a user associated
|
||||
|
@ -129,6 +129,10 @@ func (p *Processor) deleteUserAndTokensForAccount(ctx context.Context, account *
|
|||
}
|
||||
}
|
||||
|
||||
if err := p.state.DB.DeleteWebPushSubscriptionsByAccountID(ctx, account.ID); err != nil {
|
||||
return gtserror.Newf("db error deleting Web Push subscriptions: %w", err)
|
||||
}
|
||||
|
||||
columns, err := stubbifyUser(user)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error stubbifying user: %w", err)
|
||||
|
|
|
@ -119,6 +119,7 @@ func (suite *AdminStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager,
|
||||
&suite.state,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
visibility.NewFilter(&suite.state),
|
||||
interaction.NewFilter(&suite.state),
|
||||
)
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
|
@ -72,7 +71,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
|
|||
}
|
||||
|
||||
// Auth passed, generate the proper AP representation.
|
||||
person, err := p.converter.AccountToAS(ctx, receiver)
|
||||
accountable, err := p.converter.AccountToAS(ctx, receiver)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error converting account: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
|
@ -91,7 +90,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
|
|||
// Instead, we end up in an 'I'll show you mine if you show me
|
||||
// yours' situation, where we sort of agree to reveal each
|
||||
// other's profiles at the same time.
|
||||
return data(person)
|
||||
return data(accountable)
|
||||
}
|
||||
|
||||
// Get requester from auth.
|
||||
|
@ -107,13 +106,13 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
|
|||
return nil, gtserror.NewErrorForbidden(errors.New(text))
|
||||
}
|
||||
|
||||
return data(person)
|
||||
return data(accountable)
|
||||
}
|
||||
|
||||
func data(requestedPerson vocab.ActivityStreamsPerson) (interface{}, gtserror.WithCode) {
|
||||
data, err := ap.Serialize(requestedPerson)
|
||||
func data(accountable ap.Accountable) (interface{}, gtserror.WithCode) {
|
||||
data, err := ap.Serialize(accountable)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error serializing person: %w", err)
|
||||
err := gtserror.Newf("error serializing accountable: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/processing/markers"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/polls"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/push"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/report"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/search"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/status"
|
||||
|
@ -51,6 +52,7 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/subscriptions"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/text"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/webpush"
|
||||
)
|
||||
|
||||
// Processor groups together processing functions and
|
||||
|
@ -88,6 +90,7 @@ type Processor struct {
|
|||
markers markers.Processor
|
||||
media media.Processor
|
||||
polls polls.Processor
|
||||
push push.Processor
|
||||
report report.Processor
|
||||
search search.Processor
|
||||
status status.Processor
|
||||
|
@ -146,6 +149,10 @@ func (p *Processor) Polls() *polls.Processor {
|
|||
return &p.polls
|
||||
}
|
||||
|
||||
func (p *Processor) Push() *push.Processor {
|
||||
return &p.push
|
||||
}
|
||||
|
||||
func (p *Processor) Report() *report.Processor {
|
||||
return &p.report
|
||||
}
|
||||
|
@ -188,6 +195,7 @@ func NewProcessor(
|
|||
mediaManager *mm.Manager,
|
||||
state *state.State,
|
||||
emailSender email.Sender,
|
||||
webPushSender webpush.Sender,
|
||||
visFilter *visibility.Filter,
|
||||
intFilter *interaction.Filter,
|
||||
) *Processor {
|
||||
|
@ -221,6 +229,7 @@ func NewProcessor(
|
|||
processor.list = list.New(state, converter)
|
||||
processor.markers = markers.New(state, converter)
|
||||
processor.polls = polls.New(&common, state, converter)
|
||||
processor.push = push.New(state, converter)
|
||||
processor.report = report.New(state, converter)
|
||||
processor.tags = tags.New(state, converter)
|
||||
processor.timeline = timeline.New(state, converter, visFilter)
|
||||
|
@ -241,6 +250,7 @@ func NewProcessor(
|
|||
converter,
|
||||
visFilter,
|
||||
emailSender,
|
||||
webPushSender,
|
||||
&processor.account,
|
||||
&processor.media,
|
||||
&processor.stream,
|
||||
|
|
|
@ -135,6 +135,7 @@ func (suite *ProcessingStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager,
|
||||
&suite.state,
|
||||
suite.emailSender,
|
||||
testrig.NewNoopWebPushSender(),
|
||||
visibility.NewFilter(&suite.state),
|
||||
interaction.NewFilter(&suite.state),
|
||||
)
|
||||
|
|
65
internal/processing/push/create.go
Normal file
65
internal/processing/push/create.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
)
|
||||
|
||||
// CreateOrReplace creates a Web Push subscription for the given access token,
|
||||
// or entirely replaces the previously existing subscription for that token.
|
||||
func (p *Processor) CreateOrReplace(
|
||||
ctx context.Context,
|
||||
accountID string,
|
||||
accessToken string,
|
||||
request *apimodel.WebPushSubscriptionCreateRequest,
|
||||
) (*apimodel.WebPushSubscription, gtserror.WithCode) {
|
||||
tokenID, errWithCode := p.getTokenID(ctx, accessToken)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Clear any previous subscription.
|
||||
if err := p.state.DB.DeleteWebPushSubscriptionByTokenID(ctx, tokenID); err != nil {
|
||||
err := gtserror.Newf("couldn't delete Web Push subscription for token ID %s: %w", tokenID, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Insert a new one.
|
||||
subscription := >smodel.WebPushSubscription{
|
||||
ID: id.NewULID(),
|
||||
AccountID: accountID,
|
||||
TokenID: tokenID,
|
||||
Endpoint: request.Subscription.Endpoint,
|
||||
Auth: request.Subscription.Keys.Auth,
|
||||
P256dh: request.Subscription.Keys.P256dh,
|
||||
NotificationFlags: alertsToNotificationFlags(request.Data.Alerts),
|
||||
}
|
||||
|
||||
if err := p.state.DB.PutWebPushSubscription(ctx, subscription); err != nil {
|
||||
err := gtserror.Newf("couldn't create Web Push subscription for token ID %s: %w", tokenID, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return p.apiSubscription(ctx, subscription)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue