mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-06-02 21:39:25 +00:00
Merge branch 'main' into filters-v2
This commit is contained in:
commit
02f2147d51
16
.drone.yml
16
.drone.yml
|
@ -12,7 +12,7 @@ steps:
|
|||
# We use golangci-lint for linting.
|
||||
# See: https://golangci-lint.run/
|
||||
- name: lint
|
||||
image: golangci/golangci-lint:v1.55.0
|
||||
image: golangci/golangci-lint:v1.57.2
|
||||
volumes:
|
||||
- name: go-build-cache
|
||||
path: /root/.cache/go-build
|
||||
|
@ -28,7 +28,7 @@ steps:
|
|||
- pull_request
|
||||
|
||||
- name: test
|
||||
image: golang:1.21-alpine
|
||||
image: golang:1.22-alpine
|
||||
volumes:
|
||||
- name: go-build-cache
|
||||
path: /root/.cache/go-build
|
||||
|
@ -80,7 +80,7 @@ steps:
|
|||
- yarn --cwd ./web/source build
|
||||
|
||||
- name: snapshot
|
||||
image: superseriousbusiness/gotosocial-drone-build:0.4.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
|
||||
image: superseriousbusiness/gotosocial-drone-build:0.6.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
|
||||
volumes:
|
||||
- name: go-build-cache
|
||||
path: /root/.cache/go-build
|
||||
|
@ -99,7 +99,7 @@ steps:
|
|||
commands:
|
||||
# Create a snapshot build with GoReleaser.
|
||||
- git fetch --tags
|
||||
- goreleaser release --rm-dist --snapshot
|
||||
- goreleaser release --clean --snapshot
|
||||
|
||||
# Login to Docker, push Docker image snapshots + manifests.
|
||||
- /go/dockerlogin.sh
|
||||
|
@ -121,7 +121,7 @@ steps:
|
|||
- main
|
||||
|
||||
- name: release
|
||||
image: superseriousbusiness/gotosocial-drone-build:0.4.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
|
||||
image: superseriousbusiness/gotosocial-drone-build:0.6.0 # https://github.com/superseriousbusiness/gotosocial-drone-build
|
||||
volumes:
|
||||
- name: go-build-cache
|
||||
path: /root/.cache/go-build
|
||||
|
@ -136,7 +136,7 @@ steps:
|
|||
commands:
|
||||
- git fetch --tags
|
||||
- /go/dockerlogin.sh
|
||||
- goreleaser release --rm-dist
|
||||
- goreleaser release --clean
|
||||
when:
|
||||
event:
|
||||
include:
|
||||
|
@ -180,7 +180,7 @@ clone:
|
|||
|
||||
steps:
|
||||
- name: mirror
|
||||
image: superseriousbusiness/gotosocial-drone-build:0.4.0
|
||||
image: superseriousbusiness/gotosocial-drone-build:0.6.0
|
||||
environment:
|
||||
ORIGIN_REPO: https://github.com/superseriousbusiness/gotosocial
|
||||
TARGET_REPO: https://codeberg.org/superseriousbusiness/gotosocial
|
||||
|
@ -193,6 +193,6 @@ steps:
|
|||
|
||||
---
|
||||
kind: signature
|
||||
hmac: 4789cebf9156b2c3cb0f097311ea7620e709e4332f130dcae51a938515dc952e
|
||||
hmac: c07f32c63cbb8180c1a37e46ff1362c1c45586819b52c6de67366ae3504314e4
|
||||
|
||||
...
|
||||
|
|
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
|
@ -10,5 +10,14 @@
|
|||
},
|
||||
"eslint.workingDirectories": ["web/source"],
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.lintTask.options": "${workspaceFolder}/web/source"
|
||||
}
|
||||
"eslint.lintTask.options": "${workspaceFolder}/web/source",
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
# Dockerfile reference: https://docs.docker.com/engine/reference/builder/
|
||||
|
||||
# stage 1: generate up-to-date swagger.yaml to put in the final container
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.21-alpine AS swagger
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.22-alpine AS swagger
|
||||
|
||||
RUN \
|
||||
### Installs goswagger for building swagger definitions inside this container
|
||||
go install "github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5" && \
|
||||
go install "github.com/go-swagger/go-swagger/cmd/swagger@c46c303aaa02" && \
|
||||
# Makes swagger executable
|
||||
chmod +x /go/bin/swagger
|
||||
|
||||
|
@ -28,7 +28,7 @@ RUN yarn --cwd ./web/source install && \
|
|||
rm -rf ./web/source
|
||||
|
||||
# stage 3: build the executor container
|
||||
FROM --platform=${TARGETPLATFORM} alpine:3.17.2 as executor
|
||||
FROM --platform=${TARGETPLATFORM} alpine:3.19.1 as executor
|
||||
|
||||
# switch to non-root user:group for GtS
|
||||
USER 1000:1000
|
||||
|
|
|
@ -128,7 +128,7 @@ Plenty of [config options](./example/config.yaml) for admins to play around with
|
|||
|
||||
No external dependencies apart from a database (or just use SQLite!). Simply download the binary + assets (or Docker container), and run.
|
||||
|
||||
GoToSocial plays nice with lower-powered machines like Raspberry Pi, old laptops and tiny $5/month VPSes.
|
||||
GoToSocial plays nice with single-board computers, old laptops and tiny $5/month VPSes.
|
||||
|
||||
### Safety + security features
|
||||
|
||||
|
@ -277,10 +277,11 @@ The following open source libraries, frameworks, and tools are used by GoToSocia
|
|||
- [gruf/go-cache](https://codeberg.org/gruf/go-cache); LRU and TTL caches. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-debug](https://codeberg.org/gruf/go-debug); debug build tag. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-errors](https://codeberg.org/gruf/go-errors); context-like error w/ value wrapping [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-fastcopy](https://codeberg.org/gruf/go-fastcopy); performant pooled I/O copying [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-fastcopy](https://codeberg.org/gruf/go-fastcopy); performant (buffer pooled) I/O copying [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-kv](https://codeberg.org/gruf/go-kv); log field formatting. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-list](https://codeberg.org/gruf/go-list); generic doubly linked list. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-mutexes](https://codeberg.org/gruf/go-mutexes); safemutex & mutex map. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-runners](https://codeberg.org/gruf/go-runners); workerpools and synchronization. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-runners](https://codeberg.org/gruf/go-runners); synchronization utilities. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-sched](https://codeberg.org/gruf/go-sched); task scheduler. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-store](https://codeberg.org/gruf/go-store); file storage backend (local & s3). [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- [gruf/go-structr](https://codeberg.org/gruf/go-structr); struct caching + queueing with automated indexing by field. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
|
|
|
@ -39,7 +39,6 @@ func initState(ctx context.Context) (*state.State, error) {
|
|||
var state state.State
|
||||
state.Caches.Init()
|
||||
state.Caches.Start()
|
||||
state.Workers.Start()
|
||||
|
||||
// Set the state DB connection
|
||||
dbConn, err := bundb.NewBunDBService(ctx, &state)
|
||||
|
@ -53,7 +52,6 @@ func initState(ctx context.Context) (*state.State, error) {
|
|||
|
||||
func stopState(state *state.State) error {
|
||||
err := state.DB.Close()
|
||||
state.Workers.Stop()
|
||||
state.Caches.Stop()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -127,8 +127,6 @@ func setupList(ctx context.Context) (*list, error) {
|
|||
state.Caches.Init()
|
||||
state.Caches.Start()
|
||||
|
||||
state.Workers.Start()
|
||||
|
||||
dbService, err := bundb.NewBunDBService(ctx, &state)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating dbservice: %w", err)
|
||||
|
@ -148,7 +146,6 @@ func setupList(ctx context.Context) (*list, error) {
|
|||
func (l *list) shutdown() error {
|
||||
l.out.Flush()
|
||||
err := l.dbService.Close()
|
||||
l.state.Workers.Stop()
|
||||
l.state.Caches.Stop()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -44,7 +44,10 @@ func setupPrune(ctx context.Context) (*prune, error) {
|
|||
state.Caches.Init()
|
||||
state.Caches.Start()
|
||||
|
||||
state.Workers.Start()
|
||||
// Scheduler is required for the
|
||||
// claner, but no other workers
|
||||
// are needed for this CLI action.
|
||||
state.Workers.StartScheduler()
|
||||
|
||||
dbService, err := bundb.NewBunDBService(ctx, &state)
|
||||
if err != nil {
|
||||
|
@ -85,7 +88,7 @@ func (p *prune) shutdown() error {
|
|||
errs.Appendf("error stopping database: %w", err)
|
||||
}
|
||||
|
||||
p.state.Workers.Stop()
|
||||
p.state.Workers.Scheduler.Stop()
|
||||
p.state.Caches.Stop()
|
||||
|
||||
return errs.Combine()
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"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/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/metrics"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
|
||||
|
@ -128,25 +129,6 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
TLSInsecureSkipVerify: config.GetHTTPClientTLSInsecureSkipVerify(),
|
||||
})
|
||||
|
||||
// Initialize delivery worker with http client.
|
||||
state.Workers.Delivery.Init(client)
|
||||
|
||||
// Initialize workers.
|
||||
state.Workers.Start()
|
||||
defer state.Workers.Stop()
|
||||
|
||||
// Add a task to the scheduler to sweep caches.
|
||||
// Frequency = 1 * minute
|
||||
// Threshold = 80% capacity
|
||||
_ = state.Workers.Scheduler.AddRecurring(
|
||||
"@cachesweep", // id
|
||||
time.Time{}, // start
|
||||
time.Minute, // freq
|
||||
func(context.Context, time.Time) {
|
||||
state.Caches.Sweep(60)
|
||||
},
|
||||
)
|
||||
|
||||
// Build handlers used in later initializations.
|
||||
mediaManager := media.NewManager(&state)
|
||||
oauthServer := oauth.New(ctx, dbService)
|
||||
|
@ -195,10 +177,27 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
return fmt.Errorf("error starting list timeline: %s", err)
|
||||
}
|
||||
|
||||
// Create a media cleaner using the given state.
|
||||
// Start the job scheduler
|
||||
// (this is required for cleaner).
|
||||
state.Workers.StartScheduler()
|
||||
|
||||
// Add a task to the scheduler to sweep caches.
|
||||
// Frequency = 1 * minute
|
||||
// Threshold = 60% capacity
|
||||
_ = state.Workers.Scheduler.AddRecurring(
|
||||
"@cachesweep", // id
|
||||
time.Time{}, // start
|
||||
time.Minute, // freq
|
||||
func(context.Context, time.Time) {
|
||||
state.Caches.Sweep(60)
|
||||
},
|
||||
)
|
||||
|
||||
// Create background cleaner.
|
||||
cleaner := cleaner.New(&state)
|
||||
|
||||
// Create the processor using all the other services we've created so far.
|
||||
// Create the processor using all the
|
||||
// other services we've created so far.
|
||||
processor := processing.NewProcessor(
|
||||
cleaner,
|
||||
typeConverter,
|
||||
|
@ -209,13 +208,16 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
emailSender,
|
||||
)
|
||||
|
||||
// Set state client / federator asynchronous worker enqueue functions
|
||||
state.Workers.EnqueueClientAPI = processor.Workers().EnqueueClientAPI
|
||||
state.Workers.EnqueueFediAPI = processor.Workers().EnqueueFediAPI
|
||||
// Initialize the specialized workers.
|
||||
state.Workers.Client.Init(messages.ClientMsgIndices())
|
||||
state.Workers.Federator.Init(messages.FederatorMsgIndices())
|
||||
state.Workers.Delivery.Init(client)
|
||||
state.Workers.Client.Process = processor.Workers().ProcessFromClientAPI
|
||||
state.Workers.Federator.Process = processor.Workers().ProcessFromFediAPI
|
||||
|
||||
// Set state client / federator synchronous processing functions.
|
||||
state.Workers.ProcessFromClientAPI = processor.Workers().ProcessFromClientAPI
|
||||
state.Workers.ProcessFromFediAPI = processor.Workers().ProcessFromFediAPI
|
||||
// Initialize workers.
|
||||
state.Workers.Start()
|
||||
defer state.Workers.Stop()
|
||||
|
||||
// Schedule tasks for all existing poll expiries.
|
||||
if err := processor.Polls().ScheduleAll(ctx); err != nil {
|
||||
|
|
13
docs/admin/robots.md
Normal file
13
docs/admin/robots.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Robots.txt
|
||||
|
||||
GoToSocial serves a `robots.txt` file on the host domain. This file contains rules that attempt to block known AI scrapers, as well as some other indexers. It also includes some rules to ensure things like API endpoints aren't indexed by search engines since there really isn't any point to them.
|
||||
|
||||
## AI scrapers
|
||||
|
||||
The AI scrapers come from a [community maintained repository][airobots]. It's manually kept in sync for the time being. If you know of any missing robots, please send them a PR!
|
||||
|
||||
A number of AI scrapers are known to ignore entries in `robots.txt` even if it explicitly matches their User-Agent. This means the `robots.txt` file is not a foolproof way of ensuring AI scrapers don't grab your content.
|
||||
|
||||
If you want to block these things fully, you'll need to block based on the User-Agent header in a reverse proxy until GoToSocial can filter requests by User-Agent header.
|
||||
|
||||
[airobots]: https://github.com/ai-robots-txt/ai.robots.txt/
|
|
@ -3774,11 +3774,13 @@ paths:
|
|||
/api/v1/admin/accounts:
|
||||
get:
|
||||
description: |-
|
||||
Returned accounts will be ordered alphabetically (a-z) by domain + username.
|
||||
|
||||
The next and previous queries can be parsed from the returned Link header.
|
||||
Example:
|
||||
|
||||
```
|
||||
<https://example.org/api/v1/admin/accounts?limit=80&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/accounts?limit=80&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
<https://example.org/api/v1/admin/accounts?limit=80&max_id=example.org%2F%40someone>; rel="next", <https://example.org/api/v1/admin/accounts?limit=80&min_id=example.org%2F%40someone_else>; rel="prev"
|
||||
````
|
||||
operationId: adminAccountsGetV1
|
||||
parameters:
|
||||
|
@ -3847,19 +3849,15 @@ paths:
|
|||
in: query
|
||||
name: staff
|
||||
type: boolean
|
||||
- description: All results returned will be older than the item with this ID.
|
||||
- description: max_id in the form `[domain]/@[username]`. All results returned will be later in the alphabet than `[domain]/@[username]`. For example, if max_id = `example.org/@someone` then returned entries might contain `example.org/@someone_else`, `later.example.org/@someone`, etc. Local account IDs in this form use an empty string for the `[domain]` part, for example local account with username `someone` would be `/@someone`.
|
||||
in: query
|
||||
name: max_id
|
||||
type: string
|
||||
- description: All results returned will be newer than the item with this ID.
|
||||
in: query
|
||||
name: since_id
|
||||
type: string
|
||||
- description: Returns results immediately newer than the item with this ID.
|
||||
- description: min_id in the form `[domain]/@[username]`. All results returned will be earlier in the alphabet than `[domain]/@[username]`. For example, if min_id = `example.org/@someone` then returned entries might contain `example.org/@earlier_account`, `earlier.example.org/@someone`, etc. Local account IDs in this form use an empty string for the `[domain]` part, for example local account with username `someone` would be `/@someone`.
|
||||
in: query
|
||||
name: min_id
|
||||
type: string
|
||||
- default: 100
|
||||
- default: 50
|
||||
description: Maximum number of results to return.
|
||||
in: query
|
||||
maximum: 200
|
||||
|
@ -8463,11 +8461,13 @@ paths:
|
|||
/api/v2/admin/accounts:
|
||||
get:
|
||||
description: |-
|
||||
Returned accounts will be ordered alphabetically (a-z) by domain + username.
|
||||
|
||||
The next and previous queries can be parsed from the returned Link header.
|
||||
Example:
|
||||
|
||||
```
|
||||
<https://example.org/api/v2/admin/accounts?limit=80&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v2/admin/accounts?limit=80&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
<https://example.org/api/v2/admin/accounts?limit=80&max_id=example.org%2F%40someone>; rel="next", <https://example.org/api/v2/admin/accounts?limit=80&min_id=example.org%2F%40someone_else>; rel="prev"
|
||||
````
|
||||
operationId: adminAccountsGetV2
|
||||
parameters:
|
||||
|
@ -8513,19 +8513,15 @@ paths:
|
|||
in: query
|
||||
name: ip
|
||||
type: string
|
||||
- description: All results returned will be older than the item with this ID.
|
||||
- description: max_id in the form `[domain]/@[username]`. All results returned will be later in the alphabet than `[domain]/@[username]`. For example, if max_id = `example.org/@someone` then returned entries might contain `example.org/@someone_else`, `later.example.org/@someone`, etc. Local account IDs in this form use an empty string for the `[domain]` part, for example local account with username `someone` would be `/@someone`.
|
||||
in: query
|
||||
name: max_id
|
||||
type: string
|
||||
- description: All results returned will be newer than the item with this ID.
|
||||
in: query
|
||||
name: since_id
|
||||
type: string
|
||||
- description: Returns results immediately newer than the item with this ID.
|
||||
- description: min_id in the form `[domain]/@[username]`. All results returned will be earlier in the alphabet than `[domain]/@[username]`. For example, if min_id = `example.org/@someone` then returned entries might contain `example.org/@earlier_account`, `earlier.example.org/@someone`, etc. Local account IDs in this form use an empty string for the `[domain]` part, for example local account with username `someone` would be `/@someone`.
|
||||
in: query
|
||||
name: min_id
|
||||
type: string
|
||||
- default: 100
|
||||
- default: 50
|
||||
description: Maximum number of results to return.
|
||||
in: query
|
||||
maximum: 200
|
||||
|
|
|
@ -6,7 +6,7 @@ By default, GoToSocial will use Postgres, but this is easy to change.
|
|||
|
||||
## SQLite
|
||||
|
||||
SQLite, as the name implies, is the lightest database type that GoToSocial can use. It stores entries in a simple file format, usually in the same directory as the GoToSocial binary itself. SQLite is great for small instances and lower-powered machines like Raspberry Pi, where a dedicated database would be overkill.
|
||||
SQLite, as the name implies, is the lightest database type that GoToSocial can use. It stores entries in a simple file format, usually in the same directory as the GoToSocial binary itself. SQLite is great for small instances and single-board computers, where a dedicated database would be overkill.
|
||||
|
||||
To configure GoToSocial to use SQLite, change `db-type` to `sqlite`. The `address` setting will then be a filename instead of an address, so you will want to change it to `sqlite.db` or something similar.
|
||||
|
||||
|
|
82
go.mod
82
go.mod
|
@ -1,10 +1,10 @@
|
|||
module github.com/superseriousbusiness/gotosocial
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.7-concurrency-workaround
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround
|
||||
|
||||
toolchain go1.21.3
|
||||
toolchain go1.22.2
|
||||
|
||||
require (
|
||||
codeberg.org/gruf/go-bytes v1.0.2
|
||||
|
@ -16,15 +16,16 @@ require (
|
|||
codeberg.org/gruf/go-fastcopy v1.1.2
|
||||
codeberg.org/gruf/go-iotools v0.0.0-20230811115124-5d4223615a7f
|
||||
codeberg.org/gruf/go-kv v1.6.4
|
||||
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
|
||||
codeberg.org/gruf/go-logger/v2 v2.2.1
|
||||
codeberg.org/gruf/go-mutexes v1.4.0
|
||||
codeberg.org/gruf/go-mutexes v1.4.1
|
||||
codeberg.org/gruf/go-runners v1.6.2
|
||||
codeberg.org/gruf/go-sched v1.2.3
|
||||
codeberg.org/gruf/go-store/v2 v2.2.4
|
||||
codeberg.org/gruf/go-structr v0.6.2
|
||||
codeberg.org/gruf/go-structr v0.7.0
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.7.0
|
||||
github.com/DmitriyVTitov/size v1.5.0
|
||||
github.com/KimMachineGun/automemlimit v0.5.0
|
||||
github.com/KimMachineGun/automemlimit v0.6.0
|
||||
github.com/abema/go-mp4 v1.2.0
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.10.0
|
||||
|
@ -34,15 +35,15 @@ require (
|
|||
github.com/gin-contrib/sessions v1.0.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/form/v4 v4.2.1
|
||||
github.com/go-swagger/go-swagger v0.30.5
|
||||
github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/feeds v1.1.2
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/minio/minio-go/v7 v7.0.69
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/minio/minio-go/v7 v7.0.70
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/oklog/ulid v1.3.1
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
|
@ -52,7 +53,7 @@ require (
|
|||
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240408131430-247f7f7110f0
|
||||
github.com/superseriousbusiness/httpsig v1.2.0-SSB
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
|
||||
github.com/tdewolff/minify/v2 v2.20.19
|
||||
github.com/tdewolff/minify/v2 v2.20.20
|
||||
github.com/technologize/otel-go-contrib v1.1.1
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
|
@ -62,14 +63,14 @@ require (
|
|||
github.com/uptrace/bun/extra/bunotel v1.2.1
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/yuin/goldmark v1.7.1
|
||||
go.opentelemetry.io/otel v1.25.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0
|
||||
go.opentelemetry.io/otel v1.26.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.46.0
|
||||
go.opentelemetry.io/otel/metric v1.25.0
|
||||
go.opentelemetry.io/otel/sdk v1.25.0
|
||||
go.opentelemetry.io/otel/metric v1.26.0
|
||||
go.opentelemetry.io/otel/sdk v1.26.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.24.0
|
||||
go.opentelemetry.io/otel/trace v1.25.0
|
||||
go.opentelemetry.io/otel/trace v1.26.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/image v0.15.0
|
||||
|
@ -78,7 +79,7 @@ require (
|
|||
golang.org/x/text v0.14.0
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.29.5
|
||||
modernc.org/sqlite v1.29.8
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
|
@ -89,7 +90,7 @@ require (
|
|||
codeberg.org/gruf/go-mangler v1.3.0 // indirect
|
||||
codeberg.org/gruf/go-maps v1.0.3 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
|
@ -111,7 +112,7 @@ require (
|
|||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d // indirect
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
|
@ -120,17 +121,17 @@ require (
|
|||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.4 // indirect
|
||||
github.com/go-openapi/inflect v0.19.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/loads v0.21.2 // indirect
|
||||
github.com/go-openapi/runtime v0.26.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.7 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-openapi/validate v0.22.1 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
github.com/go-openapi/inflect v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/runtime v0.28.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
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.19.0 // indirect
|
||||
|
@ -141,14 +142,14 @@ require (
|
|||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
|
@ -167,7 +168,6 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
@ -183,11 +183,11 @@ require (
|
|||
github.com/prometheus/procfs v0.12.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.10.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
|
@ -196,7 +196,7 @@ require (
|
|||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
|
||||
github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.12 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.13 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
@ -205,18 +205,18 @@ require (
|
|||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
google.golang.org/grpc v1.63.0 // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
|
261
go.sum
261
go.sum
|
@ -56,6 +56,8 @@ codeberg.org/gruf/go-iotools v0.0.0-20230811115124-5d4223615a7f h1:Kazm/PInN2m1S
|
|||
codeberg.org/gruf/go-iotools v0.0.0-20230811115124-5d4223615a7f/go.mod h1:B8uq4yHtIcKXhBZT9C/SYisz25lldLHMVpwZPz4ADLQ=
|
||||
codeberg.org/gruf/go-kv v1.6.4 h1:3NZiW8HVdBM3kpOiLb7XfRiihnzZWMAixdCznguhILk=
|
||||
codeberg.org/gruf/go-kv v1.6.4/go.mod h1:O/YkSvKiS9XsRolM3rqCd9YJmND7dAXu9z+PrlYO4bc=
|
||||
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f h1:Ss6Z+vygy+jOGhj96d/GwsYYDd22QmIcH74zM7/nQkw=
|
||||
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f/go.mod h1:F9pl4h34iuVN7kucKam9fLwsItTc+9mmaKt7pNXRd/4=
|
||||
codeberg.org/gruf/go-logger/v2 v2.2.1 h1:RP2u059EQKTBFV3cN8X6xDxNk2RkzqdgXGKflKqB7Oc=
|
||||
codeberg.org/gruf/go-logger/v2 v2.2.1/go.mod h1:m/vBfG5jNUmYXI8Hg9aVSk7Pn8YgEBITQB/B/CzdRss=
|
||||
codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4 h1:IXwfoU7f2whT6+JKIKskNl/hBlmWmnF1vZd84Eb3cyA=
|
||||
|
@ -64,16 +66,16 @@ codeberg.org/gruf/go-mangler v1.3.0 h1:cf0vuuLJuEhoIukPHj+MUBIQSWxZcfEYt2Eo/r7Rs
|
|||
codeberg.org/gruf/go-mangler v1.3.0/go.mod h1:jnOA76AQoaO2kTHi0DlTTVaFYfRM+9fzs8f4XO6MsOk=
|
||||
codeberg.org/gruf/go-maps v1.0.3 h1:VDwhnnaVNUIy5O93CvkcE2IZXnMB1+IJjzfop9V12es=
|
||||
codeberg.org/gruf/go-maps v1.0.3/go.mod h1:D5LNDxlC9rsDuVQVM6JObaVGAdHB6g2dTdOdkh1aXWA=
|
||||
codeberg.org/gruf/go-mutexes v1.4.0 h1:53H6bFDRcG6rjk3iOTuGaStT/VTFdU5Uw8Dszy88a8g=
|
||||
codeberg.org/gruf/go-mutexes v1.4.0/go.mod h1:1j/6/MBeBQUedAtAtysLLnBKogfOZAxdym0E3wlaBD8=
|
||||
codeberg.org/gruf/go-mutexes v1.4.1 h1:SgsuktbbrkKYmgZ1reA5jMGEZbXJ6GQ54fSlKRN5iug=
|
||||
codeberg.org/gruf/go-mutexes v1.4.1/go.mod h1:1j/6/MBeBQUedAtAtysLLnBKogfOZAxdym0E3wlaBD8=
|
||||
codeberg.org/gruf/go-runners v1.6.2 h1:oQef9niahfHu/wch14xNxlRMP8i+ABXH1Cb9PzZ4oYo=
|
||||
codeberg.org/gruf/go-runners v1.6.2/go.mod h1:Tq5PrZ/m/rBXbLZz0u5if+yP3nG5Sf6S8O/GnyEePeQ=
|
||||
codeberg.org/gruf/go-sched v1.2.3 h1:H5ViDxxzOBR3uIyGBCf0eH8b1L8wMybOXcdtUUTXZHk=
|
||||
codeberg.org/gruf/go-sched v1.2.3/go.mod h1:vT9uB6KWFIIwnG9vcPY2a0alYNoqdL1mSzRM8I+PK7A=
|
||||
codeberg.org/gruf/go-store/v2 v2.2.4 h1:8HO1Jh2gg7boQKA3hsDAIXd9zwieu5uXwDXEcTOD9js=
|
||||
codeberg.org/gruf/go-store/v2 v2.2.4/go.mod h1:zI4VWe5CpXAktYMtaBMrgA5QmO0sQH53LBRvfn1huys=
|
||||
codeberg.org/gruf/go-structr v0.6.2 h1:1zs7UkPBsRGRDMHhrfFL7GrwAyPHxFXCchu8ADv/zuM=
|
||||
codeberg.org/gruf/go-structr v0.6.2/go.mod h1:K1FXkUyO6N/JKt8aWqyQ8rtW7Z9ZmXKWP8mFAQ2OJjE=
|
||||
codeberg.org/gruf/go-structr v0.7.0 h1:gy0/wD7718HwJDoBMeMumk4+7veLrkumgCEOnCyzS8w=
|
||||
codeberg.org/gruf/go-structr v0.7.0/go.mod h1:K1FXkUyO6N/JKt8aWqyQ8rtW7Z9ZmXKWP8mFAQ2OJjE=
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.7.0 h1:Y6VApSXhKqExG0H2hZ2JelRK4xmWdjDQjn13CpEfzko=
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.7.0/go.mod h1:gCWKduudUWFzsnixoMzu0FYVdxHWG+AbXnZ50DqxsUE=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
|
@ -81,16 +83,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g=
|
||||
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
|
||||
github.com/KimMachineGun/automemlimit v0.5.0 h1:BeOe+BbJc8L5chL3OwzVYjVzyvPALdd5wxVVOWuUZmQ=
|
||||
github.com/KimMachineGun/automemlimit v0.5.0/go.mod h1:di3GCKiu9Y+1fs92erCbUvKzPkNyViN3mA0vti/ykEQ=
|
||||
github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1CPDhLlA3Z37E=
|
||||
github.com/KimMachineGun/automemlimit v0.6.0/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
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/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/abema/go-mp4 v1.2.0 h1:gi4X8xg/m179N/J15Fn5ugywN9vtI6PLk6iLldHGLAk=
|
||||
github.com/abema/go-mp4 v1.2.0/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
|
@ -98,7 +99,6 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
|
|||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
|
@ -176,9 +176,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
|||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
|
@ -218,46 +217,28 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
|
||||
github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
|
||||
github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
|
||||
github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M=
|
||||
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
|
||||
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
|
||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
|
||||
github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
|
||||
github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
|
||||
github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc=
|
||||
github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
|
||||
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
|
||||
github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
|
||||
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
|
||||
github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k=
|
||||
github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU=
|
||||
github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
|
||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
|
||||
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
|
||||
github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk=
|
||||
github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
|
||||
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
|
||||
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
|
||||
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
|
@ -270,39 +251,12 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
|||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U=
|
||||
github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q=
|
||||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
|
||||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
|
||||
github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02 h1:J6YiT/eg3gAfKMdVCkWXe6khsO+nxa8W4URZ4AUqzbA=
|
||||
github.com/go-swagger/go-swagger v0.30.6-0.20240418033037-c46c303aaa02/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 h1:PSPmmucxGiFBtbQcttHTUc4LQ3P09AW+ldO2qspyKdY=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
|
||||
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
|
||||
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
|
||||
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
|
||||
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
|
||||
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
|
||||
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
|
||||
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
|
||||
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
|
||||
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
|
||||
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
|
||||
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
|
||||
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
|
||||
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
|
||||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
|
@ -342,7 +296,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
|||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -352,7 +305,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
|
@ -386,8 +338,8 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
|||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
|
||||
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
|
@ -395,8 +347,8 @@ github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8L
|
|||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
@ -406,15 +358,15 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
|
|||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
|
@ -430,7 +382,6 @@ github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LF
|
|||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
|
@ -440,12 +391,9 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
|||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
|
@ -453,10 +401,7 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
|||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
@ -468,13 +413,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
|
@ -485,21 +425,17 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls
|
|||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
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.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
|
||||
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
|
@ -510,12 +446,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
|
@ -530,12 +464,9 @@ github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e h1:s2RNOM/IGd
|
|||
github.com/orcaman/writerseeker v0.0.0-20200621085525-1d3f536ff85e/go.mod h1:nBdnFKj15wFbf94Rwfq4m30eAcyY9V/IyKAGQFtqkW0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -556,12 +487,10 @@ github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY
|
|||
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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
@ -572,11 +501,9 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR
|
|||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
|
@ -590,16 +517,13 @@ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNo
|
|||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/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=
|
||||
|
@ -628,10 +552,10 @@ github.com/superseriousbusiness/httpsig v1.2.0-SSB h1:BinBGKbf2LSuVT5+MuH0XynHN9
|
|||
github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28=
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/minify/v2 v2.20.20 h1:vhULb+VsW2twkplgsawAoUY957efb+EdiZ7zu5fUhhk=
|
||||
github.com/tdewolff/minify/v2 v2.20.20/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
|
||||
github.com/tdewolff/parse/v2 v2.7.13 h1:iSiwOUkCYLNfapHoqdLcqZVgvQ0jrsao8YYKP/UJYTI=
|
||||
github.com/tdewolff/parse/v2 v2.7.13/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
|
@ -691,11 +615,6 @@ github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSV
|
|||
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
|
@ -704,7 +623,6 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
|||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
|
||||
|
@ -716,11 +634,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.7-concurrency-workaround h1:N4h6T8jb9BZTor6d4XJYaKYEh3KNAydpuydR2N1hPRc=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.7-concurrency-workaround/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
|
||||
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
|
||||
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround h1:ESobxED9bfE0nOQP/WPv9+tMR8oZoDIWRKlNK2Vs4Ms=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -728,26 +643,26 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
|
||||
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 h1:vOL89uRfOCCNIjkisd0r7SEdJF3ZJFyCNY34fdZs8eU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs=
|
||||
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
|
||||
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
|
||||
go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo=
|
||||
go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
|
||||
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0=
|
||||
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
|
||||
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
|
||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
|
@ -757,16 +672,12 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
|
|||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
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.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
|
@ -780,8 +691,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
|
@ -838,8 +749,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
|
@ -856,27 +765,20 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20210220032951-036812b2e83c/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -903,8 +805,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -924,8 +824,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
|
@ -940,13 +838,9 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
@ -1054,8 +948,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
|||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
|
||||
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -1071,7 +965,6 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
|
|||
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=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
|
@ -1091,9 +984,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -161,8 +161,8 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() {
|
|||
"type": "OrderedCollectionPage",
|
||||
"id": targetStatus.URI + "/replies?limit=20&only_other_accounts=false",
|
||||
"partOf": targetStatus.URI + "/replies?only_other_accounts=false",
|
||||
"next": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false",
|
||||
"prev": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&max_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false",
|
||||
"next": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&max_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false",
|
||||
"prev": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false",
|
||||
"orderedItems": []string{"http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0"},
|
||||
"totalItems": 1,
|
||||
})
|
||||
|
|
|
@ -21,8 +21,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -42,9 +41,6 @@ import (
|
|||
"github.com/tomnomnom/linkheader"
|
||||
)
|
||||
|
||||
// random reader according to current-time source seed.
|
||||
var randRd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
type FollowTestSuite struct {
|
||||
AccountStandardTestSuite
|
||||
}
|
||||
|
@ -76,33 +72,33 @@ func (suite *FollowTestSuite) TestFollowSelf() {
|
|||
defer result.Body.Close()
|
||||
|
||||
// check the response
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
b, err := io.ReadAll(result.Body)
|
||||
_ = b
|
||||
assert.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit2() {
|
||||
suite.testGetFollowersPage(2, "backward")
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit2() {
|
||||
suite.testGetFollowersPage(2, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit4() {
|
||||
suite.testGetFollowersPage(4, "backward")
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit4() {
|
||||
suite.testGetFollowersPage(4, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit6() {
|
||||
suite.testGetFollowersPage(6, "backward")
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit6() {
|
||||
suite.testGetFollowersPage(6, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit2() {
|
||||
suite.testGetFollowersPage(2, "forward")
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit2() {
|
||||
suite.testGetFollowersPage(2, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit4() {
|
||||
suite.testGetFollowersPage(4, "forward")
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit4() {
|
||||
suite.testGetFollowersPage(4, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit6() {
|
||||
suite.testGetFollowersPage(6, "forward")
|
||||
func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit6() {
|
||||
suite.testGetFollowersPage(6, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) {
|
||||
|
@ -117,8 +113,11 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
|
||||
var i int
|
||||
|
||||
for _, targetAccount := range suite.testAccounts {
|
||||
if targetAccount.ID == requestingAccount.ID {
|
||||
// Have each account in the testrig follow the account
|
||||
// that is requesting their followers from the API.
|
||||
for _, account := range suite.testAccounts {
|
||||
targetAccount := requestingAccount
|
||||
if account.ID == targetAccount.ID {
|
||||
// we cannot be our own target...
|
||||
continue
|
||||
}
|
||||
|
@ -132,9 +131,9 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
ID: id,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
URI: fmt.Sprintf("%s/follow/%s", targetAccount.URI, id),
|
||||
AccountID: targetAccount.ID,
|
||||
TargetAccountID: requestingAccount.ID,
|
||||
URI: fmt.Sprintf("%s/follow/%s", account.URI, id),
|
||||
AccountID: account.ID,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
@ -152,15 +151,17 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
var query string
|
||||
|
||||
switch direction {
|
||||
case "backward":
|
||||
// Set the starting query to page backward from newest.
|
||||
case "newestToOldest":
|
||||
// Set the starting query to page from
|
||||
// newest (ie., first entry in slice).
|
||||
acc := expectAccounts[0].(*model.Account)
|
||||
newest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID)
|
||||
expectAccounts = expectAccounts[1:]
|
||||
query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID)
|
||||
|
||||
case "forward":
|
||||
// Set the starting query to page forward from the oldest.
|
||||
case "oldestToNewest":
|
||||
// Set the starting query to page from
|
||||
// oldest (ie., last entry in slice).
|
||||
acc := expectAccounts[len(expectAccounts)-1].(*model.Account)
|
||||
oldest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID)
|
||||
expectAccounts = expectAccounts[:len(expectAccounts)-1]
|
||||
|
@ -208,9 +209,9 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
)
|
||||
|
||||
switch direction {
|
||||
case "backward":
|
||||
// When paging backwards (DESC) we:
|
||||
// - iter from end of received accounts
|
||||
case "newestToOldest":
|
||||
// When paging newest to oldest (ie., first page to last page):
|
||||
// - iter from start of received accounts
|
||||
// - iterate backward through received accounts
|
||||
// - stop when we reach last index of received accounts
|
||||
// - compare each received with the first index of expected accounts
|
||||
|
@ -221,8 +222,8 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
expect = func(i []interface{}) interface{} { return i[0] }
|
||||
trunc = func(i []interface{}) []interface{} { return i[1:] }
|
||||
|
||||
case "forward":
|
||||
// When paging forwards (ASC) we:
|
||||
case "oldestToNewest":
|
||||
// When paging oldest to newest (ie., last page to first page):
|
||||
// - iter from end of received accounts
|
||||
// - iterate backward through received accounts
|
||||
// - stop when we reach first index of received accounts
|
||||
|
@ -230,7 +231,7 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
// - after each compare, drop the last index of expected accounts
|
||||
start = func(i []*model.Account) int { return len(i) - 1 }
|
||||
iter = func(i int) int { return i - 1 }
|
||||
check = func(idx int, i []*model.Account) bool { return idx >= 0 }
|
||||
check = func(idx int, _ []*model.Account) bool { return idx >= 0 }
|
||||
expect = func(i []interface{}) interface{} { return i[len(i)-1] }
|
||||
trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] }
|
||||
}
|
||||
|
@ -256,7 +257,14 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
// Parse response link header values.
|
||||
values := result.Header.Values("Link")
|
||||
links := linkheader.ParseMultiple(values)
|
||||
filteredLinks := links.FilterByRel("next")
|
||||
|
||||
var filteredLinks linkheader.Links
|
||||
if direction == "newestToOldest" {
|
||||
filteredLinks = links.FilterByRel("next")
|
||||
} else {
|
||||
filteredLinks = links.FilterByRel("prev")
|
||||
}
|
||||
|
||||
suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p)
|
||||
|
||||
// A ref link header was set.
|
||||
|
@ -271,28 +279,28 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
|
|||
}
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit2() {
|
||||
suite.testGetFollowingPage(2, "backward")
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit2() {
|
||||
suite.testGetFollowingPage(2, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit4() {
|
||||
suite.testGetFollowingPage(4, "backward")
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit4() {
|
||||
suite.testGetFollowingPage(4, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit6() {
|
||||
suite.testGetFollowingPage(6, "backward")
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit6() {
|
||||
suite.testGetFollowingPage(6, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit2() {
|
||||
suite.testGetFollowingPage(2, "forward")
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit2() {
|
||||
suite.testGetFollowingPage(2, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit4() {
|
||||
suite.testGetFollowingPage(4, "forward")
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit4() {
|
||||
suite.testGetFollowingPage(4, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit6() {
|
||||
suite.testGetFollowingPage(6, "forward")
|
||||
func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit6() {
|
||||
suite.testGetFollowingPage(6, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) {
|
||||
|
@ -307,8 +315,11 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
|
||||
var i int
|
||||
|
||||
// Have the account that is requesting their following
|
||||
// list from the API follow each account in the testrig.
|
||||
for _, targetAccount := range suite.testAccounts {
|
||||
if targetAccount.ID == requestingAccount.ID {
|
||||
account := requestingAccount
|
||||
if targetAccount.ID == account.ID {
|
||||
// we cannot be our own target...
|
||||
continue
|
||||
}
|
||||
|
@ -322,8 +333,8 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
ID: id,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
URI: fmt.Sprintf("%s/follow/%s", requestingAccount.URI, id),
|
||||
AccountID: requestingAccount.ID,
|
||||
URI: fmt.Sprintf("%s/follow/%s", account.URI, id),
|
||||
AccountID: account.ID,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
@ -342,15 +353,17 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
var query string
|
||||
|
||||
switch direction {
|
||||
case "backward":
|
||||
// Set the starting query to page backward from newest.
|
||||
case "newestToOldest":
|
||||
// Set the starting query to page from
|
||||
// newest (ie., first entry in slice).
|
||||
acc := expectAccounts[0].(*model.Account)
|
||||
newest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID)
|
||||
expectAccounts = expectAccounts[1:]
|
||||
query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID)
|
||||
|
||||
case "forward":
|
||||
// Set the starting query to page forward from the oldest.
|
||||
case "oldestToNewest":
|
||||
// Set the starting query to page from
|
||||
// oldest (ie., last entry in slice).
|
||||
acc := expectAccounts[len(expectAccounts)-1].(*model.Account)
|
||||
oldest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID)
|
||||
expectAccounts = expectAccounts[:len(expectAccounts)-1]
|
||||
|
@ -397,9 +410,9 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
)
|
||||
|
||||
switch direction {
|
||||
case "backward":
|
||||
// When paging backwards (DESC) we:
|
||||
// - iter from end of received accounts
|
||||
case "newestToOldest":
|
||||
// When paging newest to oldest (ie., first page to last page):
|
||||
// - iter from start of received accounts
|
||||
// - iterate backward through received accounts
|
||||
// - stop when we reach last index of received accounts
|
||||
// - compare each received with the first index of expected accounts
|
||||
|
@ -410,8 +423,8 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
expect = func(i []interface{}) interface{} { return i[0] }
|
||||
trunc = func(i []interface{}) []interface{} { return i[1:] }
|
||||
|
||||
case "forward":
|
||||
// When paging forwards (ASC) we:
|
||||
case "oldestToNewest":
|
||||
// When paging oldest to newest (ie., last page to first page):
|
||||
// - iter from end of received accounts
|
||||
// - iterate backward through received accounts
|
||||
// - stop when we reach first index of received accounts
|
||||
|
@ -419,7 +432,7 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
// - after each compare, drop the last index of expected accounts
|
||||
start = func(i []*model.Account) int { return len(i) - 1 }
|
||||
iter = func(i int) int { return i - 1 }
|
||||
check = func(idx int, i []*model.Account) bool { return idx >= 0 }
|
||||
check = func(idx int, _ []*model.Account) bool { return idx >= 0 }
|
||||
expect = func(i []interface{}) interface{} { return i[len(i)-1] }
|
||||
trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] }
|
||||
}
|
||||
|
@ -445,7 +458,14 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
|
|||
// Parse response link header values.
|
||||
values := result.Header.Values("Link")
|
||||
links := linkheader.ParseMultiple(values)
|
||||
filteredLinks := links.FilterByRel("next")
|
||||
|
||||
var filteredLinks linkheader.Links
|
||||
if direction == "newestToOldest" {
|
||||
filteredLinks = links.FilterByRel("next")
|
||||
} else {
|
||||
filteredLinks = links.FilterByRel("prev")
|
||||
}
|
||||
|
||||
suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p)
|
||||
|
||||
// A ref link header was set.
|
||||
|
|
|
@ -19,11 +19,13 @@
|
|||
//
|
||||
// View + page through known accounts according to given filters.
|
||||
//
|
||||
// Returned accounts will be ordered alphabetically (a-z) by domain + username.
|
||||
//
|
||||
// The next and previous queries can be parsed from the returned Link header.
|
||||
// Example:
|
||||
//
|
||||
// ```
|
||||
// <https://example.org/api/v1/admin/accounts?limit=80&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/accounts?limit=80&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
// <https://example.org/api/v1/admin/accounts?limit=80&max_id=example.org%2F%40someone>; rel="next", <https://example.org/api/v1/admin/accounts?limit=80&min_id=example.org%2F%40someone_else>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
|
@ -117,23 +119,30 @@
|
|||
// name: max_id
|
||||
// in: query
|
||||
// type: string
|
||||
// description: All results returned will be older than the item with this ID.
|
||||
// -
|
||||
// name: since_id
|
||||
// in: query
|
||||
// type: string
|
||||
// description: All results returned will be newer than the item with this ID.
|
||||
// description: >-
|
||||
// max_id in the form `[domain]/@[username]`.
|
||||
// All results returned will be later in the alphabet than `[domain]/@[username]`.
|
||||
// For example, if max_id = `example.org/@someone` then returned entries might
|
||||
// contain `example.org/@someone_else`, `later.example.org/@someone`, etc.
|
||||
// Local account IDs in this form use an empty string for the `[domain]` part,
|
||||
// for example local account with username `someone` would be `/@someone`.
|
||||
// -
|
||||
// name: min_id
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Returns results immediately newer than the item with this ID.
|
||||
// description: >-
|
||||
// min_id in the form `[domain]/@[username]`.
|
||||
// All results returned will be earlier in the alphabet than `[domain]/@[username]`.
|
||||
// For example, if min_id = `example.org/@someone` then returned entries might
|
||||
// contain `example.org/@earlier_account`, `earlier.example.org/@someone`, etc.
|
||||
// Local account IDs in this form use an empty string for the `[domain]` part,
|
||||
// for example local account with username `someone` would be `/@someone`.
|
||||
// -
|
||||
// name: limit
|
||||
// in: query
|
||||
// type: integer
|
||||
// description: Maximum number of results to return.
|
||||
// default: 100
|
||||
// default: 50
|
||||
// maximum: 200
|
||||
// minimum: 1
|
||||
//
|
||||
|
@ -200,7 +209,7 @@ func (m *Module) AccountsGETV1Handler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
page, errWithCode := paging.ParseIDPage(c, 1, 200, 100)
|
||||
page, errWithCode := paging.ParseIDPage(c, 1, 200, 50)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
@ -19,11 +19,13 @@
|
|||
//
|
||||
// View + page through known accounts according to given filters.
|
||||
//
|
||||
// Returned accounts will be ordered alphabetically (a-z) by domain + username.
|
||||
//
|
||||
// The next and previous queries can be parsed from the returned Link header.
|
||||
// Example:
|
||||
//
|
||||
// ```
|
||||
// <https://example.org/api/v2/admin/accounts?limit=80&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v2/admin/accounts?limit=80&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
// <https://example.org/api/v2/admin/accounts?limit=80&max_id=example.org%2F%40someone>; rel="next", <https://example.org/api/v2/admin/accounts?limit=80&min_id=example.org%2F%40someone_else>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
|
@ -90,23 +92,30 @@
|
|||
// name: max_id
|
||||
// in: query
|
||||
// type: string
|
||||
// description: All results returned will be older than the item with this ID.
|
||||
// -
|
||||
// name: since_id
|
||||
// in: query
|
||||
// type: string
|
||||
// description: All results returned will be newer than the item with this ID.
|
||||
// description: >-
|
||||
// max_id in the form `[domain]/@[username]`.
|
||||
// All results returned will be later in the alphabet than `[domain]/@[username]`.
|
||||
// For example, if max_id = `example.org/@someone` then returned entries might
|
||||
// contain `example.org/@someone_else`, `later.example.org/@someone`, etc.
|
||||
// Local account IDs in this form use an empty string for the `[domain]` part,
|
||||
// for example local account with username `someone` would be `/@someone`.
|
||||
// -
|
||||
// name: min_id
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Returns results immediately newer than the item with this ID.
|
||||
// description: >-
|
||||
// min_id in the form `[domain]/@[username]`.
|
||||
// All results returned will be earlier in the alphabet than `[domain]/@[username]`.
|
||||
// For example, if min_id = `example.org/@someone` then returned entries might
|
||||
// contain `example.org/@earlier_account`, `earlier.example.org/@someone`, etc.
|
||||
// Local account IDs in this form use an empty string for the `[domain]` part,
|
||||
// for example local account with username `someone` would be `/@someone`.
|
||||
// -
|
||||
// name: limit
|
||||
// in: query
|
||||
// type: integer
|
||||
// description: Maximum number of results to return.
|
||||
// default: 100
|
||||
// default: 50
|
||||
// maximum: 200
|
||||
// minimum: 1
|
||||
//
|
||||
|
@ -173,7 +182,7 @@ func (m *Module) AccountsGETV2Handler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
page, errWithCode := paging.ParseIDPage(c, 1, 200, 100)
|
||||
page, errWithCode := paging.ParseIDPage(c, 1, 200, 50)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
546
internal/api/client/admin/accountsgetv2_test.go
Normal file
546
internal/api/client/admin/accountsgetv2_test.go
Normal file
|
@ -0,0 +1,546 @@
|
|||
// 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 admin_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/admin"
|
||||
)
|
||||
|
||||
type AccountsGetTestSuite struct {
|
||||
AdminStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
path := admin.AccountsV2Path
|
||||
ctx := suite.newContext(recorder, http.MethodGet, nil, path, "application/json")
|
||||
|
||||
suite.adminModule.AccountsGETV2Handler(ctx)
|
||||
suite.Equal(http.StatusOK, recorder.Code)
|
||||
|
||||
b, err := io.ReadAll(recorder.Body)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotNil(b)
|
||||
|
||||
dst := new(bytes.Buffer)
|
||||
err = json.Indent(dst, b, "", " ")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
link := recorder.Header().Get("Link")
|
||||
suite.Equal(`<http://localhost:8080/api/v2/admin/accounts?limit=50&max_id=xn--xample-ova.org%2F%40%C3%BCser>; rel="next", <http://localhost:8080/api/v2/admin/accounts?limit=50&min_id=%2F%401happyturtle>; rel="prev"`, link)
|
||||
|
||||
suite.Equal(`[
|
||||
{
|
||||
"id": "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
"username": "1happyturtle",
|
||||
"domain": null,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"email": "tortle.dude@example.org",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": true,
|
||||
"approved": true,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||
"username": "1happyturtle",
|
||||
"acct": "1happyturtle",
|
||||
"display_name": "happy little turtle :3",
|
||||
"locked": true,
|
||||
"discoverable": false,
|
||||
"bot": false,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"note": "<p>i post about things that concern me</p>",
|
||||
"url": "http://localhost:8080/@1happyturtle",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"statuses_count": 8,
|
||||
"last_status_at": "2021-07-28T08:40:37.000Z",
|
||||
"emojis": [],
|
||||
"fields": [
|
||||
{
|
||||
"name": "should you follow me?",
|
||||
"value": "maybe!",
|
||||
"verified_at": null
|
||||
},
|
||||
{
|
||||
"name": "age",
|
||||
"value": "120",
|
||||
"verified_at": null
|
||||
}
|
||||
],
|
||||
"hide_collections": true,
|
||||
"role": {
|
||||
"name": "user"
|
||||
}
|
||||
},
|
||||
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
|
||||
},
|
||||
{
|
||||
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
"username": "admin",
|
||||
"domain": null,
|
||||
"created_at": "2022-05-17T13:10:59.000Z",
|
||||
"email": "admin@example.org",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "admin"
|
||||
},
|
||||
"confirmed": true,
|
||||
"approved": true,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
"username": "admin",
|
||||
"acct": "admin",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2022-05-17T13:10:59.000Z",
|
||||
"note": "",
|
||||
"url": "http://localhost:8080/@admin",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 1,
|
||||
"following_count": 1,
|
||||
"statuses_count": 4,
|
||||
"last_status_at": "2021-10-20T10:41:37.000Z",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"enable_rss": true,
|
||||
"role": {
|
||||
"name": "admin"
|
||||
}
|
||||
},
|
||||
"created_by_application_id": "01F8MGXQRHYF5QPMTMXP78QC2F"
|
||||
},
|
||||
{
|
||||
"id": "01AY6P665V14JJR0AFVRT7311Y",
|
||||
"username": "localhost:8080",
|
||||
"domain": null,
|
||||
"created_at": "2020-05-17T13:10:59.000Z",
|
||||
"email": "",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01AY6P665V14JJR0AFVRT7311Y",
|
||||
"username": "localhost:8080",
|
||||
"acct": "localhost:8080",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2020-05-17T13:10:59.000Z",
|
||||
"note": "",
|
||||
"url": "http://localhost:8080/@localhost:8080",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"last_status_at": null,
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
"username": "the_mighty_zork",
|
||||
"domain": null,
|
||||
"created_at": "2022-05-20T11:09:18.000Z",
|
||||
"email": "zork@example.org",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": "I wanna be on this damned webbed site so bad! Please! Wow",
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": true,
|
||||
"approved": true,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
"username": "the_mighty_zork",
|
||||
"acct": "the_mighty_zork",
|
||||
"display_name": "original zork (he/they)",
|
||||
"locked": false,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2022-05-20T11:09:18.000Z",
|
||||
"note": "<p>hey yo this is my profile!</p>",
|
||||
"url": "http://localhost:8080/@the_mighty_zork",
|
||||
"avatar": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg",
|
||||
"avatar_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpg",
|
||||
"header": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
|
||||
"header_static": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg",
|
||||
"followers_count": 2,
|
||||
"following_count": 2,
|
||||
"statuses_count": 7,
|
||||
"last_status_at": "2023-12-10T09:24:00.000Z",
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"enable_rss": true,
|
||||
"role": {
|
||||
"name": "user"
|
||||
}
|
||||
},
|
||||
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
|
||||
},
|
||||
{
|
||||
"id": "01F8MH0BBE4FHXPH513MBVFHB0",
|
||||
"username": "weed_lord420",
|
||||
"domain": null,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"email": "weed_lord420@example.org",
|
||||
"ip": "199.222.111.89",
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01F8MH0BBE4FHXPH513MBVFHB0",
|
||||
"username": "weed_lord420",
|
||||
"acct": "weed_lord420",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"discoverable": false,
|
||||
"bot": false,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"note": "",
|
||||
"url": "http://localhost:8080/@weed_lord420",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"last_status_at": null,
|
||||
"emojis": [],
|
||||
"fields": [],
|
||||
"role": {
|
||||
"name": "user"
|
||||
}
|
||||
},
|
||||
"created_by_application_id": "01F8MGY43H3N2C8EWPR2FPYEXG"
|
||||
},
|
||||
{
|
||||
"id": "01FHMQX3GAABWSM0S2VZEC2SWC",
|
||||
"username": "Some_User",
|
||||
"domain": "example.org",
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"email": "",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01FHMQX3GAABWSM0S2VZEC2SWC",
|
||||
"username": "Some_User",
|
||||
"acct": "Some_User@example.org",
|
||||
"display_name": "some user",
|
||||
"locked": true,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"note": "i'm a real son of a gun",
|
||||
"url": "http://example.org/@Some_User",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 1,
|
||||
"last_status_at": "2023-11-02T10:44:25.000Z",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
"domain": "fossbros-anonymous.io",
|
||||
"created_at": "2021-09-26T10:52:36.000Z",
|
||||
"email": "",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
"username": "foss_satan",
|
||||
"acct": "foss_satan@fossbros-anonymous.io",
|
||||
"display_name": "big gerald",
|
||||
"locked": false,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2021-09-26T10:52:36.000Z",
|
||||
"note": "i post about like, i dunno, stuff, or whatever!!!!",
|
||||
"url": "http://fossbros-anonymous.io/@foss_satan",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 3,
|
||||
"last_status_at": "2021-09-11T09:40:37.000Z",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "062G5WYKY35KKD12EMSM3F8PJ8",
|
||||
"username": "her_fuckin_maj",
|
||||
"domain": "thequeenisstillalive.technology",
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"email": "",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "062G5WYKY35KKD12EMSM3F8PJ8",
|
||||
"username": "her_fuckin_maj",
|
||||
"acct": "her_fuckin_maj@thequeenisstillalive.technology",
|
||||
"display_name": "lizzzieeeeeeeeeeee",
|
||||
"locked": true,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"note": "if i die blame charles don't let that fuck become king",
|
||||
"url": "http://thequeenisstillalive.technology/@her_fuckin_maj",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
|
||||
"header_static": "http://localhost:8080/fileserver/062G5WYKY35KKD12EMSM3F8PJ8/header/small/01PFPMWK2FF0D9WMHEJHR07C3R.jpg",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"last_status_at": null,
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "07GZRBAEMBNKGZ8Z9VSKSXKR98",
|
||||
"username": "üser",
|
||||
"domain": "ëxample.org",
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"email": "",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "07GZRBAEMBNKGZ8Z9VSKSXKR98",
|
||||
"username": "üser",
|
||||
"acct": "üser@ëxample.org",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"discoverable": false,
|
||||
"bot": false,
|
||||
"created_at": "2020-08-10T12:13:28.000Z",
|
||||
"note": "",
|
||||
"url": "https://xn--xample-ova.org/users/@%C3%BCser",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"last_status_at": null,
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
}
|
||||
]`, dst.String())
|
||||
}
|
||||
|
||||
func (suite *AccountsGetTestSuite) TestAccountsMinID() {
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
path := admin.AccountsV2Path + "?limit=1&min_id=/@the_mighty_zork"
|
||||
ctx := suite.newContext(recorder, http.MethodGet, nil, path, "application/json")
|
||||
|
||||
ctx.Params = gin.Params{
|
||||
{
|
||||
Key: "min_id",
|
||||
Value: "/@the_mighty_zork",
|
||||
},
|
||||
{
|
||||
Key: "limit",
|
||||
Value: "1",
|
||||
},
|
||||
}
|
||||
|
||||
suite.adminModule.AccountsGETV2Handler(ctx)
|
||||
suite.Equal(http.StatusOK, recorder.Code)
|
||||
|
||||
b, err := io.ReadAll(recorder.Body)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotNil(b)
|
||||
|
||||
dst := new(bytes.Buffer)
|
||||
err = json.Indent(dst, b, "", " ")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
link := recorder.Header().Get("Link")
|
||||
suite.Equal(`<http://localhost:8080/api/v2/admin/accounts?limit=1&max_id=%2F%40localhost%3A8080>; rel="next", <http://localhost:8080/api/v2/admin/accounts?limit=1&min_id=%2F%40localhost%3A8080>; rel="prev"`, link)
|
||||
|
||||
suite.Equal(`[
|
||||
{
|
||||
"id": "01AY6P665V14JJR0AFVRT7311Y",
|
||||
"username": "localhost:8080",
|
||||
"domain": null,
|
||||
"created_at": "2020-05-17T13:10:59.000Z",
|
||||
"email": "",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "",
|
||||
"invite_request": null,
|
||||
"role": {
|
||||
"name": "user"
|
||||
},
|
||||
"confirmed": false,
|
||||
"approved": false,
|
||||
"disabled": false,
|
||||
"silenced": false,
|
||||
"suspended": false,
|
||||
"account": {
|
||||
"id": "01AY6P665V14JJR0AFVRT7311Y",
|
||||
"username": "localhost:8080",
|
||||
"acct": "localhost:8080",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"discoverable": true,
|
||||
"bot": false,
|
||||
"created_at": "2020-05-17T13:10:59.000Z",
|
||||
"note": "",
|
||||
"url": "http://localhost:8080/@localhost:8080",
|
||||
"avatar": "",
|
||||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.png",
|
||||
"header_static": "http://localhost:8080/assets/default_header.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"last_status_at": null,
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
}
|
||||
]`, dst.String())
|
||||
}
|
||||
|
||||
func TestAccountsGetTestSuite(t *testing.T) {
|
||||
suite.Run(t, &AccountsGetTestSuite{})
|
||||
}
|
|
@ -107,28 +107,28 @@ func (suite *GetTestSuite) TestGet() {
|
|||
]`, dst.String())
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) TestGetPageBackwardLimit2() {
|
||||
suite.testGetPage(2, "backward")
|
||||
func (suite *GetTestSuite) TestGetPageNewestToOldestLimit2() {
|
||||
suite.testGetPage(2, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) TestGetPageBackwardLimit4() {
|
||||
suite.testGetPage(4, "backward")
|
||||
func (suite *GetTestSuite) TestGetPageNewestToOldestLimit4() {
|
||||
suite.testGetPage(4, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) TestGetPageBackwardLimit6() {
|
||||
suite.testGetPage(6, "backward")
|
||||
func (suite *GetTestSuite) TestGetPageNewestToOldestLimit6() {
|
||||
suite.testGetPage(6, "newestToOldest")
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) TestGetPageForwardLimit2() {
|
||||
suite.testGetPage(2, "forward")
|
||||
func (suite *GetTestSuite) TestGetPageOldestToNewestLimit2() {
|
||||
suite.testGetPage(2, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) TestGetPageForwardLimit4() {
|
||||
suite.testGetPage(4, "forward")
|
||||
func (suite *GetTestSuite) TestGetPageOldestToNewestLimit4() {
|
||||
suite.testGetPage(4, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) TestGetPageForwardLimit6() {
|
||||
suite.testGetPage(6, "forward")
|
||||
func (suite *GetTestSuite) TestGetPageOldestToNewestLimit6() {
|
||||
suite.testGetPage(6, "oldestToNewest")
|
||||
}
|
||||
|
||||
func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
||||
|
@ -143,8 +143,11 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
|
||||
var i int
|
||||
|
||||
for _, targetAccount := range suite.testAccounts {
|
||||
if targetAccount.ID == requestingAccount.ID {
|
||||
// Have each account in the testrig follow req the
|
||||
// account requesting their followers from the API.
|
||||
for _, account := range suite.testAccounts {
|
||||
targetAccount := requestingAccount
|
||||
if account.ID == requestingAccount.ID {
|
||||
// we cannot be our own target...
|
||||
continue
|
||||
}
|
||||
|
@ -158,9 +161,9 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
ID: id,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
URI: fmt.Sprintf("%s/follow/%s", targetAccount.URI, id),
|
||||
AccountID: targetAccount.ID,
|
||||
TargetAccountID: requestingAccount.ID,
|
||||
URI: fmt.Sprintf("%s/follow/%s", account.URI, id),
|
||||
AccountID: account.ID,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
@ -178,15 +181,17 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
var query string
|
||||
|
||||
switch direction {
|
||||
case "backward":
|
||||
// Set the starting query to page backward from newest.
|
||||
case "newestToOldest":
|
||||
// Set the starting query to page from
|
||||
// newest (ie., first entry in slice).
|
||||
acc := expectAccounts[0].(*model.Account)
|
||||
newest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID)
|
||||
expectAccounts = expectAccounts[1:]
|
||||
query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID)
|
||||
|
||||
case "forward":
|
||||
// Set the starting query to page forward from the oldest.
|
||||
case "oldestToNewest":
|
||||
// Set the starting query to page from
|
||||
// oldest (ie., last entry in slice).
|
||||
acc := expectAccounts[len(expectAccounts)-1].(*model.Account)
|
||||
oldest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID)
|
||||
expectAccounts = expectAccounts[:len(expectAccounts)-1]
|
||||
|
@ -232,9 +237,9 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
)
|
||||
|
||||
switch direction {
|
||||
case "backward":
|
||||
// When paging backwards (DESC) we:
|
||||
// - iter from end of received accounts
|
||||
case "newestToOldest":
|
||||
// When paging newest to oldest (ie., first page to last page):
|
||||
// - iter from start of received accounts
|
||||
// - iterate backward through received accounts
|
||||
// - stop when we reach last index of received accounts
|
||||
// - compare each received with the first index of expected accounts
|
||||
|
@ -245,8 +250,8 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
expect = func(i []interface{}) interface{} { return i[0] }
|
||||
trunc = func(i []interface{}) []interface{} { return i[1:] }
|
||||
|
||||
case "forward":
|
||||
// When paging forwards (ASC) we:
|
||||
case "oldestToNewest":
|
||||
// When paging oldest to newest (ie., last page to first page):
|
||||
// - iter from end of received accounts
|
||||
// - iterate backward through received accounts
|
||||
// - stop when we reach first index of received accounts
|
||||
|
@ -254,7 +259,7 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
// - after each compare, drop the last index of expected accounts
|
||||
start = func(i []*model.Account) int { return len(i) - 1 }
|
||||
iter = func(i int) int { return i - 1 }
|
||||
check = func(idx int, i []*model.Account) bool { return idx >= 0 }
|
||||
check = func(idx int, _ []*model.Account) bool { return idx >= 0 }
|
||||
expect = func(i []interface{}) interface{} { return i[len(i)-1] }
|
||||
trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] }
|
||||
}
|
||||
|
@ -280,7 +285,14 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
|
|||
// Parse response link header values.
|
||||
values := result.Header.Values("Link")
|
||||
links := linkheader.ParseMultiple(values)
|
||||
filteredLinks := links.FilterByRel("next")
|
||||
|
||||
var filteredLinks linkheader.Links
|
||||
if direction == "newestToOldest" {
|
||||
filteredLinks = links.FilterByRel("next")
|
||||
} else {
|
||||
filteredLinks = links.FilterByRel("prev")
|
||||
}
|
||||
|
||||
suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p)
|
||||
|
||||
// A ref link header was set.
|
||||
|
|
|
@ -72,7 +72,6 @@ func (suite *StatusStandardTestSuite) SetupSuite() {
|
|||
|
||||
func (suite *StatusStandardTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
@ -98,6 +97,8 @@ func (suite *StatusStandardTestSuite) SetupTest() {
|
|||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.statusModule = statuses.New(suite.processor)
|
||||
|
||||
testrig.StartWorkers(&suite.state, suite.processor.Workers())
|
||||
}
|
||||
|
||||
func (suite *StatusStandardTestSuite) TearDownTest() {
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
@ -69,7 +69,7 @@ func (suite *StatusDeleteTestSuite) TestPostDelete() {
|
|||
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
b, err := io.ReadAll(result.Body)
|
||||
suite.NoError(err)
|
||||
|
||||
statusReply := &apimodel.Status{}
|
||||
|
|
|
@ -50,10 +50,10 @@ func (suite *StatusHistoryTestSuite) TestGetHistory() {
|
|||
|
||||
// Setup request.
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, target, nil)
|
||||
request := httptest.NewRequest(http.MethodGet, target, nil)
|
||||
request.Header.Set("accept", "application/json")
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, request)
|
||||
|
||||
|
||||
// Set auth + path params.
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, testApplication)
|
||||
ctx.Set(oauth.SessionAuthorizedToken, testToken)
|
||||
|
|
8
internal/cache/cache.go
vendored
8
internal/cache/cache.go
vendored
|
@ -125,8 +125,11 @@ func (c *Caches) Sweep(threshold float64) {
|
|||
c.GTS.AccountNote.Trim(threshold)
|
||||
c.GTS.AccountSettings.Trim(threshold)
|
||||
c.GTS.AccountStats.Trim(threshold)
|
||||
c.GTS.Application.Trim(threshold)
|
||||
c.GTS.Block.Trim(threshold)
|
||||
c.GTS.BlockIDs.Trim(threshold)
|
||||
c.GTS.BoostOfIDs.Trim(threshold)
|
||||
c.GTS.Client.Trim(threshold)
|
||||
c.GTS.Emoji.Trim(threshold)
|
||||
c.GTS.EmojiCategory.Trim(threshold)
|
||||
c.GTS.Filter.Trim(threshold)
|
||||
|
@ -136,6 +139,7 @@ func (c *Caches) Sweep(threshold float64) {
|
|||
c.GTS.FollowIDs.Trim(threshold)
|
||||
c.GTS.FollowRequest.Trim(threshold)
|
||||
c.GTS.FollowRequestIDs.Trim(threshold)
|
||||
c.GTS.InReplyToIDs.Trim(threshold)
|
||||
c.GTS.Instance.Trim(threshold)
|
||||
c.GTS.List.Trim(threshold)
|
||||
c.GTS.ListEntry.Trim(threshold)
|
||||
|
@ -145,11 +149,15 @@ func (c *Caches) Sweep(threshold float64) {
|
|||
c.GTS.Move.Trim(threshold)
|
||||
c.GTS.Notification.Trim(threshold)
|
||||
c.GTS.Poll.Trim(threshold)
|
||||
c.GTS.PollVote.Trim(threshold)
|
||||
c.GTS.PollVoteIDs.Trim(threshold)
|
||||
c.GTS.Report.Trim(threshold)
|
||||
c.GTS.Status.Trim(threshold)
|
||||
c.GTS.StatusFave.Trim(threshold)
|
||||
c.GTS.StatusFaveIDs.Trim(threshold)
|
||||
c.GTS.Tag.Trim(threshold)
|
||||
c.GTS.ThreadMute.Trim(threshold)
|
||||
c.GTS.Token.Trim(threshold)
|
||||
c.GTS.Tombstone.Trim(threshold)
|
||||
c.GTS.User.Trim(threshold)
|
||||
c.Visibility.Trim(threshold)
|
||||
|
|
|
@ -252,6 +252,32 @@ func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gts
|
|||
return a.GetAccountByUsernameDomain(ctx, username, domain)
|
||||
}
|
||||
|
||||
// GetAccounts selects accounts using the given parameters.
|
||||
// Unlike with other functions, the paging for GetAccounts
|
||||
// is done not by ID, but by a concatenation of `[domain]/@[username]`,
|
||||
// which allows callers to page through accounts in alphabetical
|
||||
// order (much more useful for an admin overview of accounts,
|
||||
// for example, than paging by ID (which is random) or by account
|
||||
// created at date, which is not particularly interesting).
|
||||
//
|
||||
// Generated queries will look something like this
|
||||
// (SQLite example, maxID was provided so we're paging down):
|
||||
//
|
||||
// SELECT "account"."id", (COALESCE("domain", '') || '/@' || "username") AS "domain_username"
|
||||
// FROM "accounts" AS "account"
|
||||
// WHERE ("domain_username" > '/@the_mighty_zork')
|
||||
// ORDER BY "domain_username" ASC
|
||||
//
|
||||
// **NOTE ABOUT POSTGRES**: Postgres ordering expressions in
|
||||
// this function specify COLLATE "C" to ensure that ordering
|
||||
// is similar to SQLite (which uses BINARY ordering by default).
|
||||
// This unfortunately means that A-Z > a-z, when ordering but
|
||||
// that's an acceptable tradeoff for a query like this.
|
||||
//
|
||||
// See:
|
||||
//
|
||||
// - https://www.postgresql.org/docs/current/collation.html#COLLATION-MANAGING-STANDARD
|
||||
// - https://sqlite.org/datatype3.html#collation
|
||||
func (a *accountDB) GetAccounts(
|
||||
ctx context.Context,
|
||||
origin string,
|
||||
|
@ -269,6 +295,11 @@ func (a *accountDB) GetAccounts(
|
|||
error,
|
||||
) {
|
||||
var (
|
||||
// We have to use different
|
||||
// syntax for this query
|
||||
// depending on dialect.
|
||||
dbDialect = a.db.Dialect().Name()
|
||||
|
||||
// local users lists,
|
||||
// required for some
|
||||
// limiting parameters.
|
||||
|
@ -287,10 +318,6 @@ func (a *accountDB) GetAccounts(
|
|||
}
|
||||
|
||||
// Get paging params.
|
||||
//
|
||||
// Note this may be min_id OR since_id
|
||||
// from the API, this gets handled below
|
||||
// when checking order to reverse slice.
|
||||
minID = page.GetMin()
|
||||
maxID = page.GetMax()
|
||||
limit = page.GetLimit()
|
||||
|
@ -309,32 +336,50 @@ func (a *accountDB) GetAccounts(
|
|||
// Select only IDs from table
|
||||
Column("account.id")
|
||||
|
||||
// Return only accounts OLDER
|
||||
// than account with maxID.
|
||||
if maxID != "" {
|
||||
maxIDAcct, err := a.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
maxID,
|
||||
var subQ *bun.RawQuery
|
||||
if dbDialect == dialect.SQLite {
|
||||
// For SQLite we can just select
|
||||
// our indexed expression once
|
||||
// as a column alias.
|
||||
q = q.ColumnExpr(
|
||||
"(COALESCE(?, ?) || ? || ?) AS ?",
|
||||
bun.Ident("domain"), "",
|
||||
"/@",
|
||||
bun.Ident("username"),
|
||||
bun.Ident("domain_username"),
|
||||
)
|
||||
} else {
|
||||
// Create a subquery for
|
||||
// Postgres to reuse.
|
||||
subQ = a.db.NewRaw(
|
||||
"(COALESCE(?, ?) || ? || ?) COLLATE ?",
|
||||
bun.Ident("domain"), "",
|
||||
"/@",
|
||||
bun.Ident("username"),
|
||||
bun.Ident("C"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting maxID account %s: %w", maxID, err)
|
||||
}
|
||||
|
||||
q = q.Where("? < ?", bun.Ident("account.created_at"), maxIDAcct.CreatedAt)
|
||||
}
|
||||
|
||||
// Return only accounts NEWER
|
||||
// than account with minID.
|
||||
if minID != "" {
|
||||
minIDAcct, err := a.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
minID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting minID account %s: %w", minID, err)
|
||||
// Return only accounts with `[domain]/@[username]`
|
||||
// later in the alphabet (a-z) than provided maxID.
|
||||
if maxID != "" {
|
||||
if dbDialect == dialect.SQLite {
|
||||
// Use aliased column.
|
||||
q = q.Where("? > ?", bun.Ident("domain_username"), maxID)
|
||||
} else {
|
||||
q = q.Where("? > ?", subQ, maxID)
|
||||
}
|
||||
}
|
||||
|
||||
q = q.Where("? > ?", bun.Ident("account.created_at"), minIDAcct.CreatedAt)
|
||||
// Return only accounts with `[domain]/@[username]`
|
||||
// earlier in the alphabet (a-z) than provided minID.
|
||||
if minID != "" {
|
||||
if dbDialect == dialect.SQLite {
|
||||
// Use aliased column.
|
||||
q = q.Where("? < ?", bun.Ident("domain_username"), minID)
|
||||
} else {
|
||||
q = q.Where("? < ?", subQ, minID)
|
||||
}
|
||||
}
|
||||
|
||||
switch status {
|
||||
|
@ -479,13 +524,29 @@ func (a *accountDB) GetAccounts(
|
|||
|
||||
if order == paging.OrderAscending {
|
||||
// Page up.
|
||||
q = q.Order("account.created_at ASC")
|
||||
// It's counterintuitive because it
|
||||
// says DESC in the query, but we're
|
||||
// going backwards in the alphabet,
|
||||
// and a < z in a string comparison.
|
||||
if dbDialect == dialect.SQLite {
|
||||
q = q.OrderExpr("? DESC", bun.Ident("domain_username"))
|
||||
} else {
|
||||
q = q.OrderExpr("(?) DESC", subQ)
|
||||
}
|
||||
} else {
|
||||
// Page down.
|
||||
q = q.Order("account.created_at DESC")
|
||||
// It's counterintuitive because it
|
||||
// says ASC in the query, but we're
|
||||
// going forwards in the alphabet,
|
||||
// and z > a in a string comparison.
|
||||
if dbDialect == dialect.SQLite {
|
||||
q = q.OrderExpr("? ASC", bun.Ident("domain_username"))
|
||||
} else {
|
||||
q = q.OrderExpr("? ASC", subQ)
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.Scan(ctx, &accountIDs); err != nil {
|
||||
if err := q.Scan(ctx, &accountIDs, new([]string)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -502,6 +502,80 @@ func (suite *AccountTestSuite) TestGetAccountsAll() {
|
|||
suite.Len(accounts, 9)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestGetAccountsMaxID() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
origin = ""
|
||||
status = ""
|
||||
mods = false
|
||||
invitedBy = ""
|
||||
username = ""
|
||||
displayName = ""
|
||||
domain = ""
|
||||
email = ""
|
||||
ip netip.Addr
|
||||
// Get accounts with `[domain]/@[username]`
|
||||
// later in the alphabet than `/@the_mighty_zork`.
|
||||
page = &paging.Page{Max: paging.MaxID("/@the_mighty_zork")}
|
||||
)
|
||||
|
||||
accounts, err := suite.db.GetAccounts(
|
||||
ctx,
|
||||
origin,
|
||||
status,
|
||||
mods,
|
||||
invitedBy,
|
||||
username,
|
||||
displayName,
|
||||
domain,
|
||||
email,
|
||||
ip,
|
||||
page,
|
||||
)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Len(accounts, 5)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestGetAccountsMinID() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
origin = ""
|
||||
status = ""
|
||||
mods = false
|
||||
invitedBy = ""
|
||||
username = ""
|
||||
displayName = ""
|
||||
domain = ""
|
||||
email = ""
|
||||
ip netip.Addr
|
||||
// Get accounts with `[domain]/@[username]`
|
||||
// earlier in the alphabet than `/@the_mighty_zork`.
|
||||
page = &paging.Page{Min: paging.MinID("/@the_mighty_zork")}
|
||||
)
|
||||
|
||||
accounts, err := suite.db.GetAccounts(
|
||||
ctx,
|
||||
origin,
|
||||
status,
|
||||
mods,
|
||||
invitedBy,
|
||||
username,
|
||||
displayName,
|
||||
domain,
|
||||
email,
|
||||
ip,
|
||||
page,
|
||||
)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Len(accounts, 3)
|
||||
}
|
||||
|
||||
func (suite *AccountTestSuite) TestGetAccountsModsOnly() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
// 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/log"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
log.Info(ctx, "reindexing accounts (accounts_paging_idx); this may take a few minutes, please don't interrupt this migration!")
|
||||
|
||||
q := db.NewCreateIndex().
|
||||
TableExpr("accounts").
|
||||
Index("accounts_paging_idx").
|
||||
IfNotExists()
|
||||
|
||||
switch d := db.Dialect().Name(); d {
|
||||
case dialect.SQLite:
|
||||
q = q.ColumnExpr(
|
||||
"COALESCE(?, ?) || ? || ?",
|
||||
bun.Ident("domain"), "",
|
||||
"/@",
|
||||
bun.Ident("username"),
|
||||
)
|
||||
|
||||
// Specify C collation for Postgres to ensure
|
||||
// alphabetic sort order is similar enough to
|
||||
// SQLite (which uses BINARY sort by default).
|
||||
//
|
||||
// See:
|
||||
//
|
||||
// - https://www.postgresql.org/docs/current/collation.html#COLLATION-MANAGING-STANDARD
|
||||
// - https://sqlite.org/datatype3.html#collation
|
||||
case dialect.PG:
|
||||
q = q.ColumnExpr(
|
||||
"(COALESCE(?, ?) || ? || ?) COLLATE ?",
|
||||
bun.Ident("domain"), "",
|
||||
"/@",
|
||||
bun.Ident("username"),
|
||||
bun.Ident("C"),
|
||||
)
|
||||
|
||||
default:
|
||||
log.Panicf(ctx, "dialect %s was neither postgres nor sqlite", d)
|
||||
}
|
||||
|
||||
if _, err := q.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)
|
||||
}
|
||||
}
|
|
@ -104,7 +104,7 @@ func (d *Dereferencer) GetAccountByURI(ctx context.Context, requestUser string,
|
|||
|
||||
if accountable != nil {
|
||||
// This account was updated, enqueue re-dereference featured posts.
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
if err := d.dereferenceAccountFeatured(ctx, requestUser, account); err != nil {
|
||||
log.Errorf(ctx, "error fetching account featured collection: %v", err)
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ func (d *Dereferencer) GetAccountByUsernameDomain(ctx context.Context, requestUs
|
|||
|
||||
if accountable != nil {
|
||||
// This account was updated, enqueue re-dereference featured posts.
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
if err := d.dereferenceAccountFeatured(ctx, requestUser, account); err != nil {
|
||||
log.Errorf(ctx, "error fetching account featured collection: %v", err)
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ func (d *Dereferencer) RefreshAccount(
|
|||
|
||||
if accountable != nil {
|
||||
// This account was updated, enqueue re-dereference featured posts.
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
if err := d.dereferenceAccountFeatured(ctx, requestUser, latest); err != nil {
|
||||
log.Errorf(ctx, "error fetching account featured collection: %v", err)
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ func (d *Dereferencer) RefreshAccountAsync(
|
|||
}
|
||||
|
||||
// Enqueue a worker function to enrich this account async.
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
latest, accountable, err := d.enrichAccountSafely(ctx, requestUser, uri, account, accountable)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error enriching remote account: %v", err)
|
||||
|
|
|
@ -255,7 +255,7 @@ func (d *Dereferencer) RefreshStatusAsync(
|
|||
}
|
||||
|
||||
// Enqueue a worker function to re-fetch this status entirely async.
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
latest, statusable, _, err := d.enrichStatusSafely(ctx,
|
||||
requestUser,
|
||||
uri,
|
||||
|
|
|
@ -56,14 +56,14 @@ func (d *Dereferencer) dereferenceThread(
|
|||
}
|
||||
|
||||
// Enqueue dereferencing remaining status thread, (children), asychronously .
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
if err := d.DereferenceStatusDescendants(ctx, requestUser, uri, statusable); err != nil {
|
||||
log.Error(ctx, err)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// This is an existing status, dereference the WHOLE thread asynchronously.
|
||||
d.state.Workers.Federator.MustEnqueueCtx(ctx, func(ctx context.Context) {
|
||||
d.state.Workers.Dereference.Queue.Push(func(ctx context.Context) {
|
||||
if err := d.DereferenceStatusAncestors(ctx, requestUser, status); err != nil {
|
||||
log.Error(ctx, err)
|
||||
}
|
||||
|
|
|
@ -89,13 +89,12 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
return err
|
||||
}
|
||||
|
||||
// Process side effects asynchronously.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -138,13 +137,12 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA
|
|||
return err
|
||||
}
|
||||
|
||||
// Process side effects asynchronously.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
|
||||
continue
|
||||
|
|
|
@ -81,12 +81,12 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre
|
|||
}
|
||||
|
||||
// This is a new boost. Process side effects asynchronously.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: boost,
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: boost,
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -19,6 +19,7 @@ package federatingdb_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
|
@ -42,7 +43,7 @@ func (suite *AnnounceTestSuite) TestNewAnnounce() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-suite.fromFederator
|
||||
msg, _ := suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ActivityAnnounce, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
|
||||
|
@ -69,7 +70,7 @@ func (suite *AnnounceTestSuite) TestAnnounceTwice() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-suite.fromFederator
|
||||
msg, _ := suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ActivityAnnounce, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
boost, ok := msg.GTSModel.(*gtsmodel.Status)
|
||||
|
@ -94,7 +95,8 @@ func (suite *AnnounceTestSuite) TestAnnounceTwice() {
|
|||
|
||||
// since this is a repeat announce with the same URI, just delivered to a different inbox,
|
||||
// we should have nothing in the messages channel...
|
||||
suite.Empty(suite.fromFederator)
|
||||
_, ok = suite.getFederatorMsg(time.Second)
|
||||
suite.False(ok)
|
||||
}
|
||||
|
||||
func TestAnnounceTestSuite(t *testing.T) {
|
||||
|
|
|
@ -99,7 +99,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
BLOCK HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
||||
func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requesting *gtsmodel.Account) error {
|
||||
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
||||
if !ok {
|
||||
return errors.New("activityBlock: could not convert type to block")
|
||||
|
@ -110,10 +110,10 @@ func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, rec
|
|||
return fmt.Errorf("activityBlock: could not convert Block to gts model block")
|
||||
}
|
||||
|
||||
if block.AccountID != requestingAccount.ID {
|
||||
if block.AccountID != requesting.ID {
|
||||
return fmt.Errorf(
|
||||
"activityBlock: requestingAccount %s is not Block actor account %s",
|
||||
requestingAccount.URI, block.Account.URI,
|
||||
requesting.URI, block.Account.URI,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -130,12 +130,12 @@ func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, rec
|
|||
return fmt.Errorf("activityBlock: database error inserting block: %s", err)
|
||||
}
|
||||
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
ReceivingAccount: receiving,
|
||||
RequestingAccount: requestingAccount,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
Receiving: receiving,
|
||||
Requesting: requesting,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -297,7 +297,7 @@ func (f *federatingDB) createPollOptionables(
|
|||
}
|
||||
|
||||
// Enqueue message to the fedi API worker with poll vote(s).
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectType: ap.ActivityQuestion,
|
||||
GTSModel: >smodel.PollVote{
|
||||
|
@ -308,8 +308,8 @@ func (f *federatingDB) createPollOptionables(
|
|||
PollID: inReplyTo.PollID,
|
||||
Poll: inReplyTo.Poll,
|
||||
},
|
||||
ReceivingAccount: receiver,
|
||||
RequestingAccount: requester,
|
||||
Receiving: receiver,
|
||||
Requesting: requester,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -377,28 +377,28 @@ func (f *federatingDB) createStatusable(
|
|||
|
||||
// Pass the statusable URI (APIri) into the processor
|
||||
// worker and do the rest of the processing asynchronously.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIri: ap.GetJSONLDId(statusable),
|
||||
APObjectModel: nil,
|
||||
GTSModel: nil,
|
||||
ReceivingAccount: receiver,
|
||||
RequestingAccount: requester,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIRI: ap.GetJSONLDId(statusable),
|
||||
APObject: nil,
|
||||
GTSModel: nil,
|
||||
Receiving: receiver,
|
||||
Requesting: requester,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do the rest of the processing asynchronously. The processor
|
||||
// will handle inserting/updating + further dereferencing the status.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIri: nil,
|
||||
GTSModel: nil,
|
||||
APObjectModel: statusable,
|
||||
ReceivingAccount: receiver,
|
||||
RequestingAccount: requester,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIRI: nil,
|
||||
GTSModel: nil,
|
||||
APObject: statusable,
|
||||
Receiving: receiver,
|
||||
Requesting: requester,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -439,12 +439,12 @@ func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, re
|
|||
return fmt.Errorf("activityFollow: database error inserting follow request: %s", err)
|
||||
}
|
||||
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: followRequest,
|
||||
ReceivingAccount: receivingAccount,
|
||||
RequestingAccount: requestingAccount,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: followRequest,
|
||||
Receiving: receivingAccount,
|
||||
Requesting: requestingAccount,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -484,12 +484,12 @@ func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, rece
|
|||
return fmt.Errorf("activityLike: database error inserting fave: %w", err)
|
||||
}
|
||||
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
ReceivingAccount: receivingAccount,
|
||||
RequestingAccount: requestingAccount,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
Receiving: receivingAccount,
|
||||
Requesting: requestingAccount,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -536,12 +536,12 @@ func (f *federatingDB) activityFlag(ctx context.Context, asType vocab.Type, rece
|
|||
return fmt.Errorf("activityFlag: database error inserting report: %w", err)
|
||||
}
|
||||
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFlag,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: report,
|
||||
ReceivingAccount: receivingAccount,
|
||||
RequestingAccount: requestingAccount,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFlag,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: report,
|
||||
Receiving: receivingAccount,
|
||||
Requesting: requestingAccount,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/activity/streams"
|
||||
|
@ -48,10 +49,10 @@ func (suite *CreateTestSuite) TestCreateNote() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-suite.fromFederator
|
||||
msg, _ := suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ObjectNote, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
suite.Equal(note, msg.APObjectModel)
|
||||
suite.Equal(note, msg.APObject)
|
||||
}
|
||||
|
||||
func (suite *CreateTestSuite) TestCreateNoteForward() {
|
||||
|
@ -79,15 +80,15 @@ func (suite *CreateTestSuite) TestCreateNoteForward() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-suite.fromFederator
|
||||
msg, _ := suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ObjectNote, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
|
||||
// nothing should be set as the model since this is a forward
|
||||
suite.Nil(msg.APObjectModel)
|
||||
suite.Nil(msg.APObject)
|
||||
|
||||
// but we should have a uri set
|
||||
suite.Equal("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1", msg.APIri.String())
|
||||
suite.Equal("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1", msg.APIRI.String())
|
||||
}
|
||||
|
||||
func (suite *CreateTestSuite) TestCreateFlag1() {
|
||||
|
@ -120,7 +121,7 @@ func (suite *CreateTestSuite) TestCreateFlag1() {
|
|||
}
|
||||
|
||||
// should be a message heading to the processor now, which we can intercept here
|
||||
msg := <-suite.fromFederator
|
||||
msg, _ := suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ActivityFlag, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityCreate, msg.APActivityType)
|
||||
|
||||
|
|
|
@ -19,10 +19,13 @@ package federatingdb
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
)
|
||||
|
@ -34,43 +37,130 @@ import (
|
|||
//
|
||||
// The library makes this call only after acquiring a lock first.
|
||||
func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
|
||||
l := log.WithContext(ctx).
|
||||
WithFields(kv.Fields{
|
||||
{"id", id},
|
||||
}...)
|
||||
l.Debug("entering Delete")
|
||||
|
||||
activityContext := getActivityContext(ctx)
|
||||
if activityContext.internal {
|
||||
return nil // Already processed.
|
||||
}
|
||||
|
||||
requestingAcct := activityContext.requestingAcct
|
||||
receivingAcct := activityContext.receivingAcct
|
||||
// Extract receiving / requesting accounts.
|
||||
requesting := activityContext.requestingAcct
|
||||
receiving := activityContext.receivingAcct
|
||||
|
||||
// in a delete we only get the URI, we can't know if we have a status or a profile or something else,
|
||||
// so we have to try a few different things...
|
||||
if s, err := f.state.DB.GetStatusByURI(ctx, id.String()); err == nil && requestingAcct.ID == s.AccountID {
|
||||
l.Debugf("deleting status: %s", s.ID)
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: s,
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
})
|
||||
// Serialize deleted ID URI.
|
||||
// (may be status OR account)
|
||||
uriStr := id.String()
|
||||
|
||||
var (
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
|
||||
// Try delete as an account URI.
|
||||
ok, err = f.deleteAccount(ctx,
|
||||
requesting,
|
||||
receiving,
|
||||
uriStr,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
// success!
|
||||
return nil
|
||||
}
|
||||
|
||||
if a, err := f.state.DB.GetAccountByURI(ctx, id.String()); err == nil && requestingAcct.ID == a.ID {
|
||||
l.Debugf("deleting account: %s", a.ID)
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: a,
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
})
|
||||
// Try delete as a status URI.
|
||||
ok, err = f.deleteStatus(ctx,
|
||||
requesting,
|
||||
receiving,
|
||||
uriStr,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
// success!
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log at debug level, as lots of these could indicate federation
|
||||
// issues between remote and this instance, or help with debugging.
|
||||
log.Debugf(ctx, "received delete for unknown target: %s", uriStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *federatingDB) deleteAccount(
|
||||
ctx context.Context,
|
||||
requesting *gtsmodel.Account,
|
||||
receiving *gtsmodel.Account,
|
||||
uri string, // target account
|
||||
) (
|
||||
bool, // success?
|
||||
error, // any error
|
||||
) {
|
||||
account, err := f.state.DB.GetAccountByURI(ctx, uri)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return false, gtserror.Newf("error getting account: %w", err)
|
||||
}
|
||||
|
||||
if account != nil {
|
||||
// Ensure requesting account is
|
||||
// only trying to delete itself.
|
||||
if account.ID != requesting.ID {
|
||||
|
||||
// TODO: handled forwarded deletes,
|
||||
// for now we silently drop this.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
log.Debugf(ctx, "deleting account: %s", account.URI)
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: account,
|
||||
Receiving: receiving,
|
||||
Requesting: requesting,
|
||||
})
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (f *federatingDB) deleteStatus(
|
||||
ctx context.Context,
|
||||
requesting *gtsmodel.Account,
|
||||
receiving *gtsmodel.Account,
|
||||
uri string, // target status
|
||||
) (
|
||||
bool, // success?
|
||||
error, // any error
|
||||
) {
|
||||
status, err := f.state.DB.GetStatusByURI(ctx, uri)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return false, gtserror.Newf("error getting status: %w", err)
|
||||
}
|
||||
|
||||
if status != nil {
|
||||
// Ensure requesting account is only
|
||||
// trying to delete its own statuses.
|
||||
if status.AccountID != requesting.ID {
|
||||
|
||||
// TODO: handled forwarded deletes,
|
||||
// for now we silently drop this.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
log.Debugf(ctx, "deleting status: %s", status.URI)
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: status,
|
||||
Receiving: receiving,
|
||||
Requesting: requesting,
|
||||
})
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package federatingdb_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
@ -34,11 +35,10 @@ import (
|
|||
|
||||
type FederatingDBTestSuite struct {
|
||||
suite.Suite
|
||||
db db.DB
|
||||
tc *typeutils.Converter
|
||||
fromFederator chan messages.FromFediAPI
|
||||
federatingDB federatingdb.DB
|
||||
state state.State
|
||||
db db.DB
|
||||
tc *typeutils.Converter
|
||||
federatingDB federatingdb.DB
|
||||
state state.State
|
||||
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
testClients map[string]*gtsmodel.Client
|
||||
|
@ -51,6 +51,13 @@ type FederatingDBTestSuite struct {
|
|||
testActivities map[string]testrig.ActivityWithSignature
|
||||
}
|
||||
|
||||
func (suite *FederatingDBTestSuite) getFederatorMsg(timeout time.Duration) (*messages.FromFediAPI, bool) {
|
||||
ctx := context.Background()
|
||||
ctx, cncl := context.WithTimeout(ctx, timeout)
|
||||
defer cncl()
|
||||
return suite.state.Workers.Federator.Queue.PopCtx(ctx)
|
||||
}
|
||||
|
||||
func (suite *FederatingDBTestSuite) SetupSuite() {
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
|
@ -69,13 +76,6 @@ func (suite *FederatingDBTestSuite) SetupTest() {
|
|||
suite.state.Caches.Init()
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
suite.fromFederator = make(chan messages.FromFediAPI, 10)
|
||||
suite.state.Workers.EnqueueFediAPI = func(ctx context.Context, msgs ...messages.FromFediAPI) {
|
||||
for _, msg := range msgs {
|
||||
suite.fromFederator <- msg
|
||||
}
|
||||
}
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
|
||||
suite.testActivities = testrig.NewTestActivities(suite.testAccounts)
|
||||
|
@ -96,13 +96,6 @@ func (suite *FederatingDBTestSuite) SetupTest() {
|
|||
func (suite *FederatingDBTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
for suite.fromFederator != nil {
|
||||
select {
|
||||
case <-suite.fromFederator:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTestContext(receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) context.Context {
|
||||
|
|
|
@ -170,12 +170,12 @@ func (f *federatingDB) Move(ctx context.Context, move vocab.ActivityStreamsMove)
|
|||
|
||||
// We had a Move already or stored a new Move.
|
||||
// Pass back to a worker for async processing.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityMove,
|
||||
GTSModel: stubMove,
|
||||
RequestingAccount: requestingAcct,
|
||||
ReceivingAccount: receivingAcct,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityMove,
|
||||
GTSModel: stubMove,
|
||||
Requesting: requestingAcct,
|
||||
Receiving: receivingAcct,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
)
|
||||
|
||||
type MoveTestSuite struct {
|
||||
|
@ -78,13 +77,7 @@ func (suite *MoveTestSuite) TestMove() {
|
|||
suite.move(receivingAcct, requestingAcct, moveStr1)
|
||||
|
||||
// Should be a message heading to the processor.
|
||||
var msg messages.FromFediAPI
|
||||
select {
|
||||
case msg = <-suite.fromFederator:
|
||||
// Fine.
|
||||
case <-time.After(5 * time.Second):
|
||||
suite.FailNow("", "timeout waiting for suite.fromFederator")
|
||||
}
|
||||
msg, _ := suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityMove, msg.APActivityType)
|
||||
|
||||
|
@ -101,12 +94,7 @@ func (suite *MoveTestSuite) TestMove() {
|
|||
|
||||
// Should be a message heading to the processor
|
||||
// since this is just a straight up retry.
|
||||
select {
|
||||
case msg = <-suite.fromFederator:
|
||||
// Fine.
|
||||
case <-time.After(5 * time.Second):
|
||||
suite.FailNow("", "timeout waiting for suite.fromFederator")
|
||||
}
|
||||
msg, _ = suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityMove, msg.APActivityType)
|
||||
|
||||
|
@ -126,12 +114,7 @@ func (suite *MoveTestSuite) TestMove() {
|
|||
|
||||
// Should be a message heading to the processor
|
||||
// since this is just a retry with a different ID.
|
||||
select {
|
||||
case msg = <-suite.fromFederator:
|
||||
// Fine.
|
||||
case <-time.After(5 * time.Second):
|
||||
suite.FailNow("", "timeout waiting for suite.fromFederator")
|
||||
}
|
||||
msg, _ = suite.getFederatorMsg(5 * time.Second)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
suite.Equal(ap.ActivityMove, msg.APActivityType)
|
||||
}
|
||||
|
|
|
@ -81,7 +81,8 @@ func (suite *RejectTestSuite) TestRejectFollowRequest() {
|
|||
suite.NoError(err)
|
||||
|
||||
// there should be nothing in the federator channel since nothing needs to be passed
|
||||
suite.Empty(suite.fromFederator)
|
||||
_, ok := suite.getFederatorMsg(time.Second)
|
||||
suite.False(ok)
|
||||
|
||||
// the follow request should not be in the database anymore -- it's been rejected
|
||||
err = suite.db.GetByID(ctx, fr.ID, >smodel.FollowRequest{})
|
||||
|
|
|
@ -98,13 +98,13 @@ func (f *federatingDB) updateAccountable(ctx context.Context, receivingAcct *gts
|
|||
// was delivered along with the Update, for further asynchronous
|
||||
// updating of eg., avatar/header, emojis, etc. The actual db
|
||||
// inserts/updates will take place there.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: requestingAcct,
|
||||
APObjectModel: accountable,
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: requestingAcct,
|
||||
APObject: accountable,
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -155,13 +155,13 @@ func (f *federatingDB) updateStatusable(ctx context.Context, receivingAcct *gtsm
|
|||
|
||||
// Queue an UPDATE NOTE activity to our fedi API worker,
|
||||
// this will handle necessary database insertions, etc.
|
||||
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: status, // original status
|
||||
APObjectModel: (ap.Statusable)(statusable),
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: status, // original status
|
||||
APObject: (ap.Statusable)(statusable),
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -59,9 +59,27 @@ var (
|
|||
// configuration values passed to initialized http.Transport{}
|
||||
// and http.Client{}, along with httpclient.Client{} specific.
|
||||
type Config struct {
|
||||
// MaxOpenConnsPerHost limits the max number of open connections to a host.
|
||||
|
||||
// MaxOpenConnsPerHost limits the max
|
||||
// number of open connections to a host.
|
||||
MaxOpenConnsPerHost int
|
||||
|
||||
// AllowRanges allows outgoing
|
||||
// communications to given IP nets.
|
||||
AllowRanges []netip.Prefix
|
||||
|
||||
// BlockRanges blocks outgoing
|
||||
// communiciations to given IP nets.
|
||||
BlockRanges []netip.Prefix
|
||||
|
||||
// TLSInsecureSkipVerify can be set to true to
|
||||
// skip validation of remote TLS certificates.
|
||||
//
|
||||
// THIS SHOULD BE USED FOR TESTING ONLY, IF YOU
|
||||
// TURN THIS ON WHILE RUNNING IN PRODUCTION YOU
|
||||
// ARE LEAVING YOUR SERVER WIDE OPEN TO ATTACKS!
|
||||
TLSInsecureSkipVerify bool
|
||||
|
||||
// MaxIdleConns: see http.Transport{}.MaxIdleConns.
|
||||
MaxIdleConns int
|
||||
|
||||
|
@ -79,20 +97,6 @@ type Config struct {
|
|||
|
||||
// DisableCompression: see http.Transport{}.DisableCompression.
|
||||
DisableCompression bool
|
||||
|
||||
// AllowRanges allows outgoing communications to given IP nets.
|
||||
AllowRanges []netip.Prefix
|
||||
|
||||
// BlockRanges blocks outgoing communiciations to given IP nets.
|
||||
BlockRanges []netip.Prefix
|
||||
|
||||
// TLSInsecureSkipVerify can be set to true to
|
||||
// skip validation of remote TLS certificates.
|
||||
//
|
||||
// THIS SHOULD BE USED FOR TESTING ONLY, IF YOU
|
||||
// TURN THIS ON WHILE RUNNING IN PRODUCTION YOU
|
||||
// ARE LEAVING YOUR SERVER WIDE OPEN TO ATTACKS!
|
||||
TLSInsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// Client wraps an underlying http.Client{} to provide the following:
|
||||
|
@ -138,7 +142,8 @@ func New(cfg Config) *Client {
|
|||
cfg.MaxBodySize = int64(40 * bytesize.MiB)
|
||||
}
|
||||
|
||||
// Protect dialer with IP range sanitizer.
|
||||
// Protect the dialer
|
||||
// with IP range sanitizer.
|
||||
d.Control = (&Sanitizer{
|
||||
Allow: cfg.AllowRanges,
|
||||
Block: cfg.BlockRanges,
|
||||
|
@ -148,7 +153,7 @@ func New(cfg Config) *Client {
|
|||
c.client.Timeout = cfg.Timeout
|
||||
c.bodyMax = cfg.MaxBodySize
|
||||
|
||||
// Prepare TLS config for transport.
|
||||
// Prepare transport TLS config.
|
||||
tlsClientConfig := &tls.Config{
|
||||
InsecureSkipVerify: cfg.TLSInsecureSkipVerify, //nolint:gosec
|
||||
}
|
||||
|
@ -334,7 +339,7 @@ func (c *Client) do(r *Request) (rsp *http.Response, retry bool, err error) {
|
|||
// A retryable error.
|
||||
return nil, true, err
|
||||
|
||||
} else if rsp.StatusCode > 500 ||
|
||||
} else if rsp.StatusCode >= 500 ||
|
||||
rsp.StatusCode == http.StatusTooManyRequests {
|
||||
|
||||
// Codes over 500 (and 429: too many requests)
|
||||
|
|
|
@ -50,10 +50,16 @@ type Request struct {
|
|||
func WrapRequest(r *http.Request) Request {
|
||||
var rr Request
|
||||
rr.Request = r
|
||||
rr.Entry = log.WithContext(r.Context()).
|
||||
WithField("method", r.Method).
|
||||
WithField("url", r.URL.String()).
|
||||
WithField("contentType", r.Header.Get("Content-Type"))
|
||||
entry := log.WithContext(r.Context())
|
||||
entry = entry.WithField("method", r.Method)
|
||||
entry = entry.WithField("url", r.URL.String())
|
||||
if r.Body != nil {
|
||||
// Only add content-type header if a request body exists.
|
||||
entry = entry.WithField("contentType", r.Header.Get("Content-Type"))
|
||||
}
|
||||
// note our formatting library follows ptr values
|
||||
entry = entry.WithField("attempt", &rr.attempts)
|
||||
rr.Entry = entry
|
||||
return rr
|
||||
}
|
||||
|
||||
|
|
|
@ -432,8 +432,8 @@ func (m *Manager) ProcessEmoji(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt to add this emoji processing item to the worker queue.
|
||||
_ = m.state.Workers.Media.MustEnqueueCtx(ctx, emoji.Process)
|
||||
// Attempt to add emoji item to the worker queue.
|
||||
m.state.Workers.Media.Queue.Push(emoji.Process)
|
||||
|
||||
return emoji, nil
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ func (p *ProcessingEmoji) LoadEmoji(ctx context.Context) (*gtsmodel.Emoji, error
|
|||
// Provided context was cancelled, e.g. request cancelled
|
||||
// early. Queue this item for asynchronous processing.
|
||||
log.Warnf(ctx, "reprocessing emoji %s after canceled ctx", p.emoji.ID)
|
||||
go p.mgr.state.Workers.Media.Enqueue(p.Process)
|
||||
p.mgr.state.Workers.Media.Queue.Push(p.Process)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
errorsv2 "codeberg.org/gruf/go-errors/v2"
|
||||
"codeberg.org/gruf/go-runners"
|
||||
"codeberg.org/superseriousbusiness/exif-terminator"
|
||||
terminator "codeberg.org/superseriousbusiness/exif-terminator"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/h2non/filetype"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
|
@ -82,7 +82,7 @@ func (p *ProcessingMedia) LoadAttachment(ctx context.Context) (*gtsmodel.MediaAt
|
|||
// asynchronous processing, which will
|
||||
// use a background context.
|
||||
log.Warnf(ctx, "reprocessing media %s after canceled ctx", p.media.ID)
|
||||
go p.mgr.state.Workers.Media.Enqueue(p.Process)
|
||||
p.mgr.state.Workers.Media.Queue.Push(p.Process)
|
||||
}
|
||||
|
||||
// Media could not be retrieved FULLY,
|
||||
|
|
|
@ -20,25 +20,84 @@ package messages
|
|||
import (
|
||||
"net/url"
|
||||
|
||||
"codeberg.org/gruf/go-structr"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// FromClientAPI wraps a message that travels from the client API into the processor.
|
||||
// FromClientAPI wraps a message that
|
||||
// travels from the client API into the processor.
|
||||
type FromClientAPI struct {
|
||||
APObjectType string
|
||||
|
||||
// APObjectType ...
|
||||
APObjectType string
|
||||
|
||||
// APActivityType ...
|
||||
APActivityType string
|
||||
GTSModel interface{}
|
||||
OriginAccount *gtsmodel.Account
|
||||
TargetAccount *gtsmodel.Account
|
||||
|
||||
// Optional GTS database model
|
||||
// of the Activity / Object.
|
||||
GTSModel interface{}
|
||||
|
||||
// Targeted object URI.
|
||||
TargetURI string
|
||||
|
||||
// Origin is the account that
|
||||
// this message originated from.
|
||||
Origin *gtsmodel.Account
|
||||
|
||||
// Target is the account that
|
||||
// this message is targeting.
|
||||
Target *gtsmodel.Account
|
||||
}
|
||||
|
||||
// FromFediAPI wraps a message that travels from the federating API into the processor.
|
||||
type FromFediAPI struct {
|
||||
APObjectType string
|
||||
APActivityType string
|
||||
APIri *url.URL
|
||||
APObjectModel interface{} // Optional AP model of the Object of the Activity. Should be Accountable or Statusable.
|
||||
GTSModel interface{} // Optional GTS model of the Activity or Object.
|
||||
RequestingAccount *gtsmodel.Account // Remote account that posted this Activity to the inbox.
|
||||
ReceivingAccount *gtsmodel.Account // Local account which owns the inbox that this Activity was posted to.
|
||||
// ClientMsgIndices defines queue indices this
|
||||
// message type should be accessible / stored under.
|
||||
func ClientMsgIndices() []structr.IndexConfig {
|
||||
return []structr.IndexConfig{
|
||||
{Fields: "TargetURI", Multiple: true},
|
||||
{Fields: "Origin.ID", Multiple: true},
|
||||
{Fields: "Target.ID", Multiple: true},
|
||||
}
|
||||
}
|
||||
|
||||
// FromFediAPI wraps a message that
|
||||
// travels from the federating API into the processor.
|
||||
type FromFediAPI struct {
|
||||
|
||||
// APObjectType ...
|
||||
APObjectType string
|
||||
|
||||
// APActivityType ...
|
||||
APActivityType string
|
||||
|
||||
// Optional ActivityPub ID (IRI)
|
||||
// and / or model of Activity / Object.
|
||||
APIRI *url.URL
|
||||
APObject interface{}
|
||||
|
||||
// Optional GTS database model
|
||||
// of the Activity / Object.
|
||||
GTSModel interface{}
|
||||
|
||||
// Targeted object URI.
|
||||
TargetURI string
|
||||
|
||||
// Remote account that posted
|
||||
// this Activity to the inbox.
|
||||
Requesting *gtsmodel.Account
|
||||
|
||||
// Local account which owns the inbox
|
||||
// that this Activity was posted to.
|
||||
Receiving *gtsmodel.Account
|
||||
}
|
||||
|
||||
// FederatorMsgIndices defines queue indices this
|
||||
// message type should be accessible / stored under.
|
||||
func FederatorMsgIndices() []structr.IndexConfig {
|
||||
return []structr.IndexConfig{
|
||||
{Fields: "APIRI", Multiple: true},
|
||||
{Fields: "TargetURI", Multiple: true},
|
||||
{Fields: "Requesting.ID", Multiple: true},
|
||||
{Fields: "Receiving.ID", Multiple: true},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
// 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 oauth
|
||||
|
||||
import (
|
||||
|
|
|
@ -23,26 +23,43 @@ package paging
|
|||
func EitherMinID(minID, sinceID string) Boundary {
|
||||
/*
|
||||
|
||||
Paging with `since_id` vs `min_id`:
|
||||
Paging with `since_id` vs `min_id`:
|
||||
|
||||
limit = 4 limit = 4
|
||||
+----------+ +----------+
|
||||
max_id--> |xxxxxxxxxx| | | <-- max_id
|
||||
+----------+ +----------+
|
||||
|xxxxxxxxxx| | |
|
||||
+----------+ +----------+
|
||||
|xxxxxxxxxx| | |
|
||||
+----------+ +----------+
|
||||
|xxxxxxxxxx| |xxxxxxxxxx|
|
||||
+----------+ +----------+
|
||||
| | |xxxxxxxxxx|
|
||||
+----------+ +----------+
|
||||
| | |xxxxxxxxxx|
|
||||
+----------+ +----------+
|
||||
since_id--> | | |xxxxxxxxxx| <-- min_id
|
||||
+----------+ +----------+
|
||||
| | | |
|
||||
+----------+ +----------+
|
||||
limit = 4 limit = 4
|
||||
+----------+ +----------+
|
||||
max_id--> |xxxxxxxxxx| | | <-- max_id
|
||||
+----------+ +----------+
|
||||
|xxxxxxxxxx| | |
|
||||
+----------+ +----------+
|
||||
|xxxxxxxxxx| | |
|
||||
+----------+ +----------+
|
||||
|xxxxxxxxxx| |xxxxxxxxxx|
|
||||
+----------+ +----------+
|
||||
| | |xxxxxxxxxx|
|
||||
+----------+ +----------+
|
||||
| | |xxxxxxxxxx|
|
||||
+----------+ +----------+
|
||||
since_id--> | | |xxxxxxxxxx| <-- min_id
|
||||
+----------+ +----------+
|
||||
| | | |
|
||||
+----------+ +----------+
|
||||
|
||||
To sum it up in words:
|
||||
|
||||
when paging with since_id, max_id is used as
|
||||
the cursor value, and since_id provides a
|
||||
limiting value to the results.
|
||||
|
||||
when paging with min_id, min_id is used as
|
||||
the cursor value, and max_id provides a
|
||||
limiting value to the results.
|
||||
|
||||
But to further complicate it...
|
||||
|
||||
The "next" and "prev" relative links provided
|
||||
in the link header are ALWAYS DESCENDING. Which
|
||||
means we will ALWAYS provide next=?max_id and
|
||||
prev=?min_id. *shakes fist at mastodon api*
|
||||
|
||||
*/
|
||||
switch {
|
||||
|
@ -57,7 +74,12 @@ func EitherMinID(minID, sinceID string) Boundary {
|
|||
// SinceID ...
|
||||
func SinceID(sinceID string) Boundary {
|
||||
return Boundary{
|
||||
Name: "since_id",
|
||||
// even when a since_id query is
|
||||
// provided, the next / prev rel
|
||||
// links are DESCENDING with
|
||||
// next:max_id and prev:min_id.
|
||||
// so ALWAYS use min_id as name.
|
||||
Name: "min_id",
|
||||
Value: sinceID,
|
||||
Order: OrderDescending,
|
||||
}
|
||||
|
|
|
@ -202,8 +202,9 @@ func Page_PageFunc[WithID any](p *Page, in []WithID, get func(WithID) string) []
|
|||
return in
|
||||
}
|
||||
|
||||
// Next creates a new instance for the next returnable page, using
|
||||
// given max value. This preserves original limit and max key name.
|
||||
// Prev creates a new instance for the next returnable page, using
|
||||
// given max value. This will always assume DESCENDING for Mastodon
|
||||
// API compatibility, but in case of change it can support both.
|
||||
func (p *Page) Next(lo, hi string) *Page {
|
||||
if p == nil || lo == "" || hi == "" {
|
||||
// no paging.
|
||||
|
@ -216,25 +217,22 @@ func (p *Page) Next(lo, hi string) *Page {
|
|||
// Set original limit.
|
||||
p2.Limit = p.Limit
|
||||
|
||||
if p.order().Ascending() {
|
||||
// When ascending, next page
|
||||
// needs to start with min at
|
||||
// the next highest value.
|
||||
p2.Min = p.Min.new(hi)
|
||||
p2.Max = p.Max.new("")
|
||||
} else {
|
||||
// When descending, next page
|
||||
// needs to start with max at
|
||||
// the next lowest value.
|
||||
p2.Min = p.Min.new("")
|
||||
p2.Max = p.Max.new(lo)
|
||||
}
|
||||
// NOTE:
|
||||
// We ALWAYS assume the order
|
||||
// when creating next / prev
|
||||
// links is DESCENDING. It will
|
||||
// always use prev: ?max_name
|
||||
p2.Min = p.Min.new("")
|
||||
p2.Max = p.Max.new(lo)
|
||||
p2.Min.Order = OrderDescending
|
||||
p2.Max.Order = OrderDescending
|
||||
|
||||
return p2
|
||||
}
|
||||
|
||||
// Prev creates a new instance for the prev returnable page, using
|
||||
// given min value. This preserves original limit and min key name.
|
||||
// given min value. This will always assume DESCENDING for Mastodon
|
||||
// API compatibility, but in case of change it can support both.
|
||||
func (p *Page) Prev(lo, hi string) *Page {
|
||||
if p == nil || lo == "" || hi == "" {
|
||||
// no paging.
|
||||
|
@ -247,19 +245,15 @@ func (p *Page) Prev(lo, hi string) *Page {
|
|||
// Set original limit.
|
||||
p2.Limit = p.Limit
|
||||
|
||||
if p.order().Ascending() {
|
||||
// When ascending, prev page
|
||||
// needs to start with max at
|
||||
// the next lowest value.
|
||||
p2.Min = p.Min.new("")
|
||||
p2.Max = p.Max.new(lo)
|
||||
} else {
|
||||
// When descending, next page
|
||||
// needs to start with max at
|
||||
// the next lowest value.
|
||||
p2.Min = p.Min.new(hi)
|
||||
p2.Max = p.Max.new("")
|
||||
}
|
||||
// NOTE:
|
||||
// We ALWAYS assume the order
|
||||
// when creating next / prev
|
||||
// links is DESCENDING. It will
|
||||
// always use prev: ?min_name
|
||||
p2.Min = p.Min.new(hi)
|
||||
p2.Max = p.Max.new("")
|
||||
p2.Min.Order = OrderDescending
|
||||
p2.Max.Order = OrderDescending
|
||||
|
||||
return p2
|
||||
}
|
||||
|
@ -290,12 +284,12 @@ func (p *Page) ToLinkURL(proto, host, path string, queryParams url.Values) *url.
|
|||
}
|
||||
|
||||
if p.Min.Value != "" {
|
||||
// A page-minimum query parameter is available.
|
||||
// Set page-minimum cursor value.
|
||||
queryParams.Set(p.Min.Name, p.Min.Value)
|
||||
}
|
||||
|
||||
if p.Max.Value != "" {
|
||||
// A page-maximum query parameter is available.
|
||||
// Set page-maximum cursor value.
|
||||
queryParams.Set(p.Max.Name, p.Max.Value)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,16 +20,14 @@ package paging_test
|
|||
import (
|
||||
"math/rand"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
// random reader according to current-time source seed.
|
||||
var randRd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
type Case struct {
|
||||
// Name is the test case name.
|
||||
Name string
|
||||
|
@ -63,13 +61,9 @@ func TestPage(t *testing.T) {
|
|||
// Page the input slice.
|
||||
out := c.Page.Page(c.Input)
|
||||
|
||||
// Log the results for case of error returns.
|
||||
t.Logf("%s\npage=%+v input=%v expect=%v output=%v", c.Name, c.Page, c.Input, c.Expect, out)
|
||||
|
||||
// Check paged output is as expected.
|
||||
if !slices.Equal(out, c.Expect) {
|
||||
t.Error("unexpected paged output")
|
||||
}
|
||||
// Check paged output is expected.
|
||||
assert.Equal(t, c.Expect, out,
|
||||
"input=%#v page=%v", c.Input, c.Page)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -80,8 +74,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random indices in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
maxIdx := randRd.Intn(len(ids))
|
||||
minIdx, maxIdx, _ := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
minID := ids[minIdx]
|
||||
|
@ -104,9 +97,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random parameters in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
maxIdx := randRd.Intn(len(ids))
|
||||
limit := randRd.Intn(len(ids)) + 1
|
||||
minIdx, maxIdx, limit := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
minID := ids[minIdx]
|
||||
|
@ -116,12 +107,10 @@ var cases = []Case{
|
|||
expect := slices.Clone(ids)
|
||||
expect = cutLower(expect, minID)
|
||||
expect = cutUpper(expect, maxID)
|
||||
slices.Reverse(expect)
|
||||
|
||||
// Now limit the slice.
|
||||
if limit < len(expect) {
|
||||
expect = expect[:limit]
|
||||
}
|
||||
slices.Reverse(expect)
|
||||
|
||||
// Return page and expected IDs.
|
||||
return ids, &paging.Page{
|
||||
|
@ -135,8 +124,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random parameters in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
maxIdx := randRd.Intn(len(ids))
|
||||
minIdx, maxIdx, _ := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
minID := ids[minIdx]
|
||||
|
@ -160,8 +148,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, descending)
|
||||
|
||||
// Select random indices in slice.
|
||||
sinceIdx := randRd.Intn(len(ids))
|
||||
maxIdx := randRd.Intn(len(ids))
|
||||
sinceIdx, maxIdx, _ := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
sinceID := ids[sinceIdx]
|
||||
|
@ -183,7 +170,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, descending)
|
||||
|
||||
// Select random indices in slice.
|
||||
maxIdx := randRd.Intn(len(ids))
|
||||
_, maxIdx, _ := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
maxID := ids[maxIdx]
|
||||
|
@ -202,7 +189,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, descending)
|
||||
|
||||
// Select random indices in slice.
|
||||
sinceIdx := randRd.Intn(len(ids))
|
||||
sinceIdx, _, _ := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
sinceID := ids[sinceIdx]
|
||||
|
@ -221,7 +208,7 @@ var cases = []Case{
|
|||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random indices in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
minIdx, _, _ := generateParams(len(ids))
|
||||
|
||||
// Select the boundaries.
|
||||
minID := ids[minIdx]
|
||||
|
@ -258,32 +245,34 @@ func cutUpper(in []string, bound string) []string {
|
|||
return in
|
||||
}
|
||||
|
||||
// random reader according to current-time source seed.
|
||||
var randRd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
// generateParams ...
|
||||
func generateParams(n int) (minIdx int, maxIdx int, limit int) {
|
||||
maxIdx = max(1, randRd.Intn(n))
|
||||
minIdx = randRd.Intn(maxIdx)
|
||||
limit = randRd.Intn(max(1, maxIdx-minIdx)) + 1
|
||||
return
|
||||
}
|
||||
|
||||
// generateSlice generates a new slice of len containing ascending sorted slice.
|
||||
func generateSlice(len int) []string {
|
||||
if len <= 0 {
|
||||
if len <= 1 {
|
||||
// minimum testable
|
||||
// pageable amount
|
||||
len = 2
|
||||
}
|
||||
now := time.Now()
|
||||
in := make([]string, len)
|
||||
for i := 0; i < len; i++ {
|
||||
// Convert now to timestamp.
|
||||
t := ulid.Timestamp(now)
|
||||
|
||||
// Create anew ulid for now.
|
||||
u := ulid.MustNew(t, randRd)
|
||||
|
||||
// Add to slice.
|
||||
in[i] = u.String()
|
||||
|
||||
// Bump now by 1 second.
|
||||
now = now.Add(time.Second)
|
||||
in[i] = strconv.Itoa(i)
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func ascending(a, b string) int {
|
||||
func ascending(sa, sb string) int {
|
||||
a, _ := strconv.Atoi(sa)
|
||||
b, _ := strconv.Atoi(sb)
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
|
@ -292,7 +281,9 @@ func ascending(a, b string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
func descending(a, b string) int {
|
||||
func descending(sa, sb string) int {
|
||||
a, _ := strconv.Atoi(sa)
|
||||
b, _ := strconv.Atoi(sb)
|
||||
if a < b {
|
||||
return 1
|
||||
} else if a > b {
|
||||
|
|
|
@ -19,6 +19,7 @@ package account_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
@ -48,7 +49,6 @@ type AccountStandardTestSuite struct {
|
|||
state state.State
|
||||
mediaManager *media.Manager
|
||||
oauthServer oauth.Server
|
||||
fromClientAPIChan chan messages.FromClientAPI
|
||||
transportController transport.Controller
|
||||
federator *federation.Federator
|
||||
emailSender email.Sender
|
||||
|
@ -68,6 +68,13 @@ type AccountStandardTestSuite struct {
|
|||
accountProcessor account.Processor
|
||||
}
|
||||
|
||||
func (suite *AccountStandardTestSuite) getClientMsg(timeout time.Duration) (*messages.FromClientAPI, bool) {
|
||||
ctx := context.Background()
|
||||
ctx, cncl := context.WithTimeout(ctx, timeout)
|
||||
defer cncl()
|
||||
return suite.state.Workers.Client.Queue.PopCtx(ctx)
|
||||
}
|
||||
|
||||
func (suite *AccountStandardTestSuite) SetupSuite() {
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
|
@ -101,13 +108,6 @@ func (suite *AccountStandardTestSuite) SetupTest() {
|
|||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
|
||||
suite.fromClientAPIChan = make(chan messages.FromClientAPI, 100)
|
||||
suite.state.Workers.EnqueueClientAPI = func(ctx context.Context, msgs ...messages.FromClientAPI) {
|
||||
for _, msg := range msgs {
|
||||
suite.fromClientAPIChan <- msg
|
||||
}
|
||||
}
|
||||
|
||||
suite.transportController = testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../testrig/media"))
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, suite.transportController, suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
|
|
|
@ -83,16 +83,16 @@ func (p *Processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel
|
|||
}
|
||||
|
||||
// Process block side effects (federation etc).
|
||||
msgs = append(msgs, messages.FromClientAPI{
|
||||
msgs = append(msgs, &messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: block,
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetAccount,
|
||||
Origin: requestingAccount,
|
||||
Target: targetAccount,
|
||||
})
|
||||
|
||||
// Batch queue accreted client api messages.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, msgs...)
|
||||
p.state.Workers.Client.Queue.Push(msgs...)
|
||||
|
||||
return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
|
||||
}
|
||||
|
@ -120,12 +120,12 @@ func (p *Processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel
|
|||
existingBlock.TargetAccount = targetAccount
|
||||
|
||||
// Process block removal side effects (federation etc).
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityBlock,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: existingBlock,
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetAccount,
|
||||
Origin: requestingAccount,
|
||||
Target: targetAccount,
|
||||
})
|
||||
|
||||
return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
|
||||
|
|
|
@ -126,11 +126,11 @@ func (p *Processor) Create(
|
|||
|
||||
// There are side effects for creating a new account
|
||||
// (confirmation emails etc), perform these async.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: user,
|
||||
OriginAccount: user.Account,
|
||||
Origin: user.Account,
|
||||
})
|
||||
|
||||
return user, nil
|
||||
|
|
|
@ -102,16 +102,13 @@ func (p *Processor) Delete(
|
|||
// and the above Delete function will be called afterwards from the processor, to clear
|
||||
// out the account's bits and bobs, and stubbify it.
|
||||
func (p *Processor) DeleteSelf(ctx context.Context, account *gtsmodel.Account) gtserror.WithCode {
|
||||
fromClientAPIMessage := messages.FromClientAPI{
|
||||
// Process the delete side effects asynchronously.
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActorPerson,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
OriginAccount: account,
|
||||
TargetAccount: account,
|
||||
}
|
||||
|
||||
// Process the delete side effects asynchronously.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, fromClientAPIMessage)
|
||||
|
||||
Origin: account,
|
||||
Target: account,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -193,7 +190,8 @@ func (p *Processor) deleteAccountFollows(ctx context.Context, account *gtsmodel.
|
|||
|
||||
var (
|
||||
// Use this slice to batch unfollow messages.
|
||||
msgs = []messages.FromClientAPI{}
|
||||
msgs = []*messages.FromClientAPI{}
|
||||
|
||||
// To avoid checking if account is local over + over
|
||||
// inside the subsequent loops, just generate static
|
||||
// side effects function once now.
|
||||
|
@ -214,7 +212,7 @@ func (p *Processor) deleteAccountFollows(ctx context.Context, account *gtsmodel.
|
|||
}
|
||||
if msg := unfollowSideEffects(ctx, account, follow); msg != nil {
|
||||
// There was a side effect to process.
|
||||
msgs = append(msgs, *msg)
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,13 +242,13 @@ func (p *Processor) deleteAccountFollows(ctx context.Context, account *gtsmodel.
|
|||
|
||||
if msg := unfollowSideEffects(ctx, account, follow); msg != nil {
|
||||
// There was a side effect to process.
|
||||
msgs = append(msgs, *msg)
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Process accreted messages in serial.
|
||||
for _, msg := range msgs {
|
||||
if err := p.state.Workers.ProcessFromClientAPI(ctx, msg); err != nil {
|
||||
if err := p.state.Workers.Client.Process(ctx, msg); err != nil {
|
||||
log.Errorf(
|
||||
ctx,
|
||||
"error processing %s of %s during Delete of account %s: %v",
|
||||
|
@ -306,8 +304,8 @@ func (p *Processor) unfollowSideEffectsFunc(local bool) func(
|
|||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: follow,
|
||||
OriginAccount: account,
|
||||
TargetAccount: follow.TargetAccount,
|
||||
Origin: account,
|
||||
Target: follow.TargetAccount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -337,7 +335,7 @@ func (p *Processor) deleteAccountStatuses(
|
|||
statuses []*gtsmodel.Status
|
||||
err error
|
||||
maxID string
|
||||
msgs = []messages.FromClientAPI{}
|
||||
msgs = []*messages.FromClientAPI{}
|
||||
)
|
||||
|
||||
statusLoop:
|
||||
|
@ -404,29 +402,29 @@ statusLoop:
|
|||
continue
|
||||
}
|
||||
|
||||
msgs = append(msgs, messages.FromClientAPI{
|
||||
msgs = append(msgs, &messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: status,
|
||||
OriginAccount: boost.Account,
|
||||
TargetAccount: account,
|
||||
Origin: boost.Account,
|
||||
Target: account,
|
||||
})
|
||||
}
|
||||
|
||||
// Now prepare to Delete status.
|
||||
msgs = append(msgs, messages.FromClientAPI{
|
||||
msgs = append(msgs, &messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: status,
|
||||
OriginAccount: account,
|
||||
TargetAccount: account,
|
||||
Origin: account,
|
||||
Target: account,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Process accreted messages in serial.
|
||||
for _, msg := range msgs {
|
||||
if err := p.state.Workers.ProcessFromClientAPI(ctx, msg); err != nil {
|
||||
if err := p.state.Workers.Client.Process(ctx, msg); err != nil {
|
||||
log.Errorf(
|
||||
ctx,
|
||||
"error processing %s of %s during Delete of account %s: %v",
|
||||
|
|
|
@ -117,12 +117,12 @@ func (p *Processor) FollowCreate(ctx context.Context, requestingAccount *gtsmode
|
|||
} else {
|
||||
// Otherwise we leave the follow request as it is,
|
||||
// and we handle the rest of the process async.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fr,
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetAccount,
|
||||
Origin: requestingAccount,
|
||||
Target: targetAccount,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ func (p *Processor) FollowRemove(ctx context.Context, requestingAccount *gtsmode
|
|||
}
|
||||
|
||||
// Batch queue accreted client api messages.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, msgs...)
|
||||
p.state.Workers.Client.Queue.Push(msgs...)
|
||||
|
||||
return p.RelationshipGet(ctx, requestingAccount, targetAccountID)
|
||||
}
|
||||
|
@ -225,8 +225,8 @@ func (p *Processor) getFollowTarget(ctx context.Context, requester *gtsmodel.Acc
|
|||
// If a follow and/or follow request was removed this way, one or two
|
||||
// messages will be returned which should then be processed by a client
|
||||
// api worker.
|
||||
func (p *Processor) unfollow(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) ([]messages.FromClientAPI, error) {
|
||||
var msgs []messages.FromClientAPI
|
||||
func (p *Processor) unfollow(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) ([]*messages.FromClientAPI, error) {
|
||||
var msgs []*messages.FromClientAPI
|
||||
|
||||
// Get follow from requesting account to target account.
|
||||
follow, err := p.state.DB.GetFollow(ctx, requestingAccount.ID, targetAccount.ID)
|
||||
|
@ -251,7 +251,7 @@ func (p *Processor) unfollow(ctx context.Context, requestingAccount *gtsmodel.Ac
|
|||
}
|
||||
|
||||
// Follow status changed, process side effects.
|
||||
msgs = append(msgs, messages.FromClientAPI{
|
||||
msgs = append(msgs, &messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: >smodel.Follow{
|
||||
|
@ -259,8 +259,8 @@ func (p *Processor) unfollow(ctx context.Context, requestingAccount *gtsmodel.Ac
|
|||
TargetAccountID: targetAccount.ID,
|
||||
URI: follow.URI,
|
||||
},
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetAccount,
|
||||
Origin: requestingAccount,
|
||||
Target: targetAccount,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ func (p *Processor) unfollow(ctx context.Context, requestingAccount *gtsmodel.Ac
|
|||
}
|
||||
|
||||
// Follow status changed, process side effects.
|
||||
msgs = append(msgs, messages.FromClientAPI{
|
||||
msgs = append(msgs, &messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: >smodel.Follow{
|
||||
|
@ -295,8 +295,8 @@ func (p *Processor) unfollow(ctx context.Context, requestingAccount *gtsmodel.Ac
|
|||
TargetAccountID: targetAccount.ID,
|
||||
URI: followReq.URI,
|
||||
},
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetAccount,
|
||||
Origin: requestingAccount,
|
||||
Target: targetAccount,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -40,12 +40,12 @@ func (p *Processor) FollowRequestAccept(ctx context.Context, requestingAccount *
|
|||
if follow.Account != nil {
|
||||
// Only enqueue work in the case we have a request creating account stored.
|
||||
// NOTE: due to how AcceptFollowRequest works, the inverse shouldn't be possible.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: follow,
|
||||
OriginAccount: follow.Account,
|
||||
TargetAccount: follow.TargetAccount,
|
||||
Origin: follow.Account,
|
||||
Target: follow.TargetAccount,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -67,12 +67,12 @@ func (p *Processor) FollowRequestReject(ctx context.Context, requestingAccount *
|
|||
if followRequest.Account != nil {
|
||||
// Only enqueue work in the case we have a request creating account stored.
|
||||
// NOTE: due to how GetFollowRequest works, the inverse shouldn't be possible.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityReject,
|
||||
GTSModel: followRequest,
|
||||
OriginAccount: followRequest.Account,
|
||||
TargetAccount: followRequest.TargetAccount,
|
||||
Origin: followRequest.Account,
|
||||
Target: followRequest.TargetAccount,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
|
@ -152,18 +151,11 @@ func (suite *FollowTestSuite) TestFollowRequestLocal() {
|
|||
}
|
||||
|
||||
// There should be a message going to the worker.
|
||||
var cMsg messages.FromClientAPI
|
||||
select {
|
||||
case cMsg = <-suite.fromClientAPIChan:
|
||||
// No problem.
|
||||
case <-time.After(5 * time.Second):
|
||||
suite.FailNow("timed out waiting for message")
|
||||
}
|
||||
|
||||
cMsg, _ := suite.getClientMsg(5 * time.Second)
|
||||
suite.Equal(ap.ActivityCreate, cMsg.APActivityType)
|
||||
suite.Equal(ap.ActivityFollow, cMsg.APObjectType)
|
||||
suite.Equal(requestingAccount.ID, cMsg.OriginAccount.ID)
|
||||
suite.Equal(targetAccount.ID, cMsg.TargetAccount.ID)
|
||||
suite.Equal(requestingAccount.ID, cMsg.Origin.ID)
|
||||
suite.Equal(targetAccount.ID, cMsg.Target.ID)
|
||||
}
|
||||
|
||||
func TestFollowTestS(t *testing.T) {
|
||||
|
|
|
@ -113,7 +113,7 @@ func (p *Processor) MoveSelf(
|
|||
// in quick succession, so get a lock on
|
||||
// this account.
|
||||
lockKey := originAcct.URI
|
||||
unlock := p.state.AccountLocks.Lock(lockKey)
|
||||
unlock := p.state.ProcessingLocks.Lock(lockKey)
|
||||
defer unlock()
|
||||
|
||||
// Ensure we have a valid, up-to-date representation of the target account.
|
||||
|
@ -317,12 +317,12 @@ func (p *Processor) MoveSelf(
|
|||
}
|
||||
|
||||
// Everything seems OK, process Move side effects async.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActorPerson,
|
||||
APActivityType: ap.ActivityMove,
|
||||
GTSModel: move,
|
||||
OriginAccount: originAcct,
|
||||
TargetAccount: targetAcct,
|
||||
Origin: originAcct,
|
||||
Target: targetAcct,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -70,29 +70,23 @@ func (suite *MoveTestSuite) TestMoveAccountOK() {
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// There should be a msg heading back to fromClientAPI.
|
||||
select {
|
||||
case msg := <-suite.fromClientAPIChan:
|
||||
move, ok := msg.GTSModel.(*gtsmodel.Move)
|
||||
if !ok {
|
||||
suite.FailNow("", "could not cast %T to *gtsmodel.Move", move)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
suite.WithinDuration(now, move.CreatedAt, 5*time.Second)
|
||||
suite.WithinDuration(now, move.UpdatedAt, 5*time.Second)
|
||||
suite.WithinDuration(now, move.AttemptedAt, 5*time.Second)
|
||||
suite.Zero(move.SucceededAt)
|
||||
suite.NotZero(move.ID)
|
||||
suite.Equal(requestingAcct.URI, move.OriginURI)
|
||||
suite.NotNil(move.Origin)
|
||||
suite.Equal(targetAcct.URI, move.TargetURI)
|
||||
suite.NotNil(move.Target)
|
||||
suite.NotZero(move.URI)
|
||||
|
||||
case <-time.After(5 * time.Second):
|
||||
suite.FailNow("time out waiting for message")
|
||||
// There should be a message going to the worker.
|
||||
cMsg, _ := suite.getClientMsg(5 * time.Second)
|
||||
move, ok := cMsg.GTSModel.(*gtsmodel.Move)
|
||||
if !ok {
|
||||
suite.FailNow("", "could not cast %T to *gtsmodel.Move", move)
|
||||
}
|
||||
now := time.Now()
|
||||
suite.WithinDuration(now, move.CreatedAt, 5*time.Second)
|
||||
suite.WithinDuration(now, move.UpdatedAt, 5*time.Second)
|
||||
suite.WithinDuration(now, move.AttemptedAt, 5*time.Second)
|
||||
suite.Zero(move.SucceededAt)
|
||||
suite.NotZero(move.ID)
|
||||
suite.Equal(requestingAcct.URI, move.OriginURI)
|
||||
suite.NotNil(move.Origin)
|
||||
suite.Equal(targetAcct.URI, move.TargetURI)
|
||||
suite.NotNil(move.Target)
|
||||
suite.NotZero(move.URI)
|
||||
|
||||
// Move should be in the database now.
|
||||
move, err := suite.state.DB.GetMoveByOriginTarget(
|
||||
|
|
|
@ -296,11 +296,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("could not update account settings %s: %s", account.ID, err))
|
||||
}
|
||||
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: account,
|
||||
OriginAccount: account,
|
||||
Origin: account,
|
||||
})
|
||||
|
||||
acctSensitive, err := p.converter.AccountToAPIAccountSensitive(ctx, account)
|
||||
|
|
|
@ -20,6 +20,7 @@ package account_test
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
|
@ -31,20 +32,6 @@ type AccountUpdateTestSuite struct {
|
|||
AccountStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *AccountStandardTestSuite) checkClientAPIChan(accountID string) {
|
||||
msg := <-suite.fromClientAPIChan
|
||||
|
||||
// Profile update.
|
||||
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
|
||||
// Correct account updated.
|
||||
if msg.OriginAccount == nil {
|
||||
suite.FailNow("expected msg.OriginAccount not to be nil")
|
||||
}
|
||||
suite.Equal(accountID, msg.OriginAccount.ID)
|
||||
}
|
||||
|
||||
func (suite *AccountUpdateTestSuite) TestAccountUpdateSimple() {
|
||||
testAccount := >smodel.Account{}
|
||||
*testAccount = *suite.testAccounts["local_account_1"]
|
||||
|
@ -73,7 +60,17 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateSimple() {
|
|||
suite.Equal(noteExpected, apiAccount.Note)
|
||||
|
||||
// We should have an update in the client api channel.
|
||||
suite.checkClientAPIChan(testAccount.ID)
|
||||
msg, _ := suite.getClientMsg(5 * time.Second)
|
||||
|
||||
// Profile update.
|
||||
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
|
||||
// Correct account updated.
|
||||
if msg.Origin == nil {
|
||||
suite.FailNow("expected msg.OriginAccount not to be nil")
|
||||
}
|
||||
suite.Equal(testAccount.ID, msg.Origin.ID)
|
||||
|
||||
// Check database model of account as well.
|
||||
dbAccount, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
|
@ -113,7 +110,17 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMention() {
|
|||
suite.Equal(noteExpected, apiAccount.Note)
|
||||
|
||||
// We should have an update in the client api channel.
|
||||
suite.checkClientAPIChan(testAccount.ID)
|
||||
msg, _ := suite.getClientMsg(5 * time.Second)
|
||||
|
||||
// Profile update.
|
||||
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
|
||||
// Correct account updated.
|
||||
if msg.Origin == nil {
|
||||
suite.FailNow("expected msg.OriginAccount not to be nil")
|
||||
}
|
||||
suite.Equal(testAccount.ID, msg.Origin.ID)
|
||||
|
||||
// Check database model of account as well.
|
||||
dbAccount, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
|
@ -159,7 +166,17 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithMarkdownNote() {
|
|||
suite.Equal(noteExpected, apiAccount.Note)
|
||||
|
||||
// We should have an update in the client api channel.
|
||||
suite.checkClientAPIChan(testAccount.ID)
|
||||
msg, _ := suite.getClientMsg(5 * time.Second)
|
||||
|
||||
// Profile update.
|
||||
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
|
||||
// Correct account updated.
|
||||
if msg.Origin == nil {
|
||||
suite.FailNow("expected msg.OriginAccount not to be nil")
|
||||
}
|
||||
suite.Equal(testAccount.ID, msg.Origin.ID)
|
||||
|
||||
// Check database model of account as well.
|
||||
dbAccount, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
|
@ -234,7 +251,17 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateWithFields() {
|
|||
suite.EqualValues(emojisExpected, apiAccount.Emojis)
|
||||
|
||||
// We should have an update in the client api channel.
|
||||
suite.checkClientAPIChan(testAccount.ID)
|
||||
msg, _ := suite.getClientMsg(5 * time.Second)
|
||||
|
||||
// Profile update.
|
||||
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
|
||||
// Correct account updated.
|
||||
if msg.Origin == nil {
|
||||
suite.FailNow("expected msg.OriginAccount not to be nil")
|
||||
}
|
||||
suite.Equal(testAccount.ID, msg.Origin.ID)
|
||||
|
||||
// Check database model of account as well.
|
||||
dbAccount, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
|
@ -281,7 +308,17 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateNoteNotFields() {
|
|||
suite.Equal(fieldsBefore, len(apiAccount.Fields))
|
||||
|
||||
// We should have an update in the client api channel.
|
||||
suite.checkClientAPIChan(testAccount.ID)
|
||||
msg, _ := suite.getClientMsg(5 * time.Second)
|
||||
|
||||
// Profile update.
|
||||
suite.Equal(ap.ActivityUpdate, msg.APActivityType)
|
||||
suite.Equal(ap.ObjectProfile, msg.APObjectType)
|
||||
|
||||
// Correct account updated.
|
||||
if msg.Origin == nil {
|
||||
suite.FailNow("expected msg.OriginAccount not to be nil")
|
||||
}
|
||||
suite.Equal(testAccount.ID, msg.Origin.ID)
|
||||
|
||||
// Check database model of account as well.
|
||||
dbAccount, err := suite.db.GetAccountByID(ctx, testAccount.ID)
|
||||
|
|
|
@ -80,13 +80,13 @@ func (p *Processor) accountActionSuspend(
|
|||
Text: text,
|
||||
},
|
||||
func(ctx context.Context) gtserror.MultiError {
|
||||
if err := p.state.Workers.ProcessFromClientAPI(
|
||||
if err := p.state.Workers.Client.Process(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ActorPerson,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
OriginAccount: adminAcct,
|
||||
TargetAccount: targetAcct,
|
||||
Origin: adminAcct,
|
||||
Target: targetAcct,
|
||||
},
|
||||
); err != nil {
|
||||
errs := gtserror.NewMultiError(1)
|
||||
|
|
|
@ -49,17 +49,17 @@ func (p *Processor) AccountApprove(
|
|||
// Get a lock on the account URI,
|
||||
// to ensure it's not also being
|
||||
// rejected at the same time!
|
||||
unlock := p.state.AccountLocks.Lock(user.Account.URI)
|
||||
unlock := p.state.ProcessingLocks.Lock(user.Account.URI)
|
||||
defer unlock()
|
||||
|
||||
if !*user.Approved {
|
||||
// Process approval side effects asynschronously.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActorPerson,
|
||||
APActivityType: ap.ActivityAccept,
|
||||
GTSModel: user,
|
||||
OriginAccount: adminAcct,
|
||||
TargetAccount: user.Account,
|
||||
Origin: adminAcct,
|
||||
Target: user.Account,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ func (p *Processor) AccountReject(
|
|||
// Get a lock on the account URI,
|
||||
// since we're going to be deleting
|
||||
// it and its associated user.
|
||||
unlock := p.state.AccountLocks.Lock(user.Account.URI)
|
||||
unlock := p.state.ProcessingLocks.Lock(user.Account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Can't reject an account with a
|
||||
|
@ -101,12 +101,12 @@ func (p *Processor) AccountReject(
|
|||
}
|
||||
|
||||
// Process rejection side effects asynschronously.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActorPerson,
|
||||
APActivityType: ap.ActivityReject,
|
||||
GTSModel: deniedUser,
|
||||
OriginAccount: adminAcct,
|
||||
TargetAccount: user.Account,
|
||||
Origin: adminAcct,
|
||||
Target: user.Account,
|
||||
})
|
||||
|
||||
return apiAccount, nil
|
||||
|
|
|
@ -115,8 +115,12 @@ func (p *Processor) AccountsGet(
|
|||
return paging.EmptyResponse(), nil
|
||||
}
|
||||
|
||||
hi := accounts[count-1].ID
|
||||
lo := accounts[0].ID
|
||||
var (
|
||||
loAcct = accounts[count-1]
|
||||
hiAcct = accounts[0]
|
||||
lo = loAcct.Domain + "/@" + loAcct.Username
|
||||
hi = hiAcct.Domain + "/@" + hiAcct.Username
|
||||
)
|
||||
|
||||
items := make([]interface{}, 0, count)
|
||||
for _, account := range accounts {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -97,8 +98,10 @@ func (a *Actions) Run(
|
|||
// we're done modifying it for now.
|
||||
a.m.Unlock()
|
||||
|
||||
// Do the rest of the work asynchronously.
|
||||
a.state.Workers.ClientAPI.Enqueue(func(ctx context.Context) {
|
||||
go func() {
|
||||
// Use a background context with existing values.
|
||||
ctx = gtscontext.WithValues(context.Background(), ctx)
|
||||
|
||||
// Run the thing and collect errors.
|
||||
if errs := f(ctx); errs != nil {
|
||||
action.Errors = make([]string, 0, len(errs))
|
||||
|
@ -119,7 +122,7 @@ func (a *Actions) Run(
|
|||
if err := a.state.DB.UpdateAdminAction(ctx, action, "completed_at", "errors"); err != nil {
|
||||
log.Errorf(ctx, "db error marking action %s as completed: %q", actionKey, err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -201,15 +201,13 @@ func (p *Processor) domainBlockSideEffects(
|
|||
// process an account delete message to remove
|
||||
// that account's posts, media, etc.
|
||||
if err := p.rangeDomainAccounts(ctx, block.Domain, func(account *gtsmodel.Account) {
|
||||
cMsg := messages.FromClientAPI{
|
||||
if err := p.state.Workers.Client.Process(ctx, &messages.FromClientAPI{
|
||||
APObjectType: ap.ActorPerson,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: block,
|
||||
OriginAccount: account,
|
||||
TargetAccount: account,
|
||||
}
|
||||
|
||||
if err := p.state.Workers.ProcessFromClientAPI(ctx, cMsg); err != nil {
|
||||
Origin: account,
|
||||
Target: account,
|
||||
}); err != nil {
|
||||
errs.Append(err)
|
||||
}
|
||||
}); err != nil {
|
||||
|
|
|
@ -140,12 +140,12 @@ func (p *Processor) ReportResolve(ctx context.Context, account *gtsmodel.Account
|
|||
}
|
||||
|
||||
// Process side effects of closing the report.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityFlag,
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
GTSModel: report,
|
||||
OriginAccount: account,
|
||||
TargetAccount: report.Account,
|
||||
Origin: account,
|
||||
Target: report.Account,
|
||||
})
|
||||
|
||||
apimodelReport, err := p.converter.ReportToAdminAPIReport(ctx, updatedReport, account)
|
||||
|
|
|
@ -62,7 +62,7 @@ func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusI
|
|||
}
|
||||
|
||||
if !visible {
|
||||
const text = "status not vising to requesting account"
|
||||
const text = "status not visible to requesting account"
|
||||
return nil, gtserror.NewErrorNotFound(errors.New(text))
|
||||
}
|
||||
|
||||
|
|
|
@ -116,11 +116,11 @@ func (p *Processor) onExpiry(pollID string) func(context.Context, time.Time) {
|
|||
|
||||
// Enqueue a status update operation to the client API worker,
|
||||
// this will asynchronously send an update with the Poll close time.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
APObjectType: ap.ObjectNote,
|
||||
GTSModel: status,
|
||||
OriginAccount: status.Account,
|
||||
Origin: status.Account,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,11 +96,11 @@ func (p *Processor) PollVote(ctx context.Context, requester *gtsmodel.Account, p
|
|||
poll.IncrementVotes(choices)
|
||||
|
||||
// Enqueue worker task to handle side-effects of user poll vote(s).
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectType: ap.ActivityQuestion,
|
||||
GTSModel: vote, // the vote choices
|
||||
OriginAccount: requester,
|
||||
Origin: requester,
|
||||
})
|
||||
|
||||
// Return converted API model poll.
|
||||
|
|
|
@ -95,7 +95,6 @@ func (suite *ProcessingStandardTestSuite) SetupSuite() {
|
|||
|
||||
func (suite *ProcessingStandardTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
@ -124,8 +123,7 @@ func (suite *ProcessingStandardTestSuite) SetupTest() {
|
|||
suite.emailSender = testrig.NewEmailSender("../../web/template/", nil)
|
||||
|
||||
suite.processor = processing.NewProcessor(cleaner.New(&suite.state), suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaManager, &suite.state, suite.emailSender)
|
||||
suite.state.Workers.EnqueueClientAPI = suite.processor.Workers().EnqueueClientAPI
|
||||
suite.state.Workers.EnqueueFediAPI = suite.processor.Workers().EnqueueFediAPI
|
||||
testrig.StartWorkers(&suite.state, suite.processor.Workers())
|
||||
|
||||
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../testrig/media")
|
||||
|
|
|
@ -91,12 +91,12 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityFlag,
|
||||
GTSModel: report,
|
||||
OriginAccount: account,
|
||||
TargetAccount: targetAccount,
|
||||
Origin: account,
|
||||
Target: targetAccount,
|
||||
})
|
||||
|
||||
apiReport, err := p.converter.ReportToAPIReport(ctx, report)
|
||||
|
|
|
@ -89,12 +89,12 @@ func (p *Processor) BoostCreate(
|
|||
}
|
||||
|
||||
// Process side effects asynchronously.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: boost,
|
||||
OriginAccount: requester,
|
||||
TargetAccount: target.Account,
|
||||
Origin: requester,
|
||||
Target: target.Account,
|
||||
})
|
||||
|
||||
return p.c.GetAPIStatus(ctx, requester, boost)
|
||||
|
@ -141,12 +141,12 @@ func (p *Processor) BoostRemove(
|
|||
|
||||
if boost != nil {
|
||||
// Status was boosted. Process unboost side effects asynchronously.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: boost,
|
||||
OriginAccount: requester,
|
||||
TargetAccount: target.Account,
|
||||
Origin: requester,
|
||||
Target: target.Account,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -143,11 +143,11 @@ func (p *Processor) Create(
|
|||
}
|
||||
|
||||
// send it back to the client API worker for async side-effects.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: requester,
|
||||
Origin: requester,
|
||||
})
|
||||
|
||||
if status.Poll != nil {
|
||||
|
|
|
@ -51,12 +51,12 @@ func (p *Processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Acco
|
|||
}
|
||||
|
||||
// Process delete side effects.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: targetStatus,
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: requestingAccount,
|
||||
Origin: requestingAccount,
|
||||
Target: requestingAccount,
|
||||
})
|
||||
|
||||
return apiStatus, nil
|
||||
|
|
|
@ -107,12 +107,12 @@ func (p *Processor) FaveCreate(ctx context.Context, requestingAccount *gtsmodel.
|
|||
}
|
||||
|
||||
// Process new status fave side effects.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: gtsFave,
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetStatus.Account,
|
||||
Origin: requestingAccount,
|
||||
Target: targetStatus.Account,
|
||||
})
|
||||
|
||||
return p.c.GetAPIStatus(ctx, requestingAccount, targetStatus)
|
||||
|
@ -137,12 +137,12 @@ func (p *Processor) FaveRemove(ctx context.Context, requestingAccount *gtsmodel.
|
|||
}
|
||||
|
||||
// Process remove status fave side effects.
|
||||
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityUndo,
|
||||
GTSModel: existingFave,
|
||||
OriginAccount: requestingAccount,
|
||||
TargetAccount: targetStatus.Account,
|
||||
Origin: requestingAccount,
|
||||
Target: targetStatus.Account,
|
||||
})
|
||||
|
||||
return p.c.GetAPIStatus(ctx, requestingAccount, targetStatus)
|
||||
|
|
|
@ -83,7 +83,7 @@ func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.A
|
|||
}
|
||||
|
||||
// Get a lock on this account.
|
||||
unlock := p.state.AccountLocks.Lock(requestingAccount.URI)
|
||||
unlock := p.state.ProcessingLocks.Lock(requestingAccount.URI)
|
||||
defer unlock()
|
||||
|
||||
if !targetStatus.PinnedAt.IsZero() {
|
||||
|
@ -148,7 +148,7 @@ func (p *Processor) PinRemove(ctx context.Context, requestingAccount *gtsmodel.A
|
|||
}
|
||||
|
||||
// Get a lock on this account.
|
||||
unlock := p.state.AccountLocks.Lock(requestingAccount.URI)
|
||||
unlock := p.state.ProcessingLocks.Lock(requestingAccount.URI)
|
||||
defer unlock()
|
||||
|
||||
if targetStatus.PinnedAt.IsZero() {
|
||||
|
|
|
@ -75,12 +75,6 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
|
|||
return nil
|
||||
}
|
||||
|
||||
// Drop any queued outgoing AP requests to / from account,
|
||||
// (this stops any queued likes, boosts, creates etc).
|
||||
f.state.Workers.Delivery.Queue.Delete("ActorID", account.URI)
|
||||
f.state.Workers.Delivery.Queue.Delete("ObjectID", account.URI)
|
||||
f.state.Workers.Delivery.Queue.Delete("TargetID", account.URI)
|
||||
|
||||
// Parse relevant URI(s).
|
||||
outboxIRI, err := parseURI(account.OutboxURI)
|
||||
if err != nil {
|
||||
|
@ -228,16 +222,6 @@ func (f *federate) DeleteStatus(ctx context.Context, status *gtsmodel.Status) er
|
|||
return nil
|
||||
}
|
||||
|
||||
// Drop any queued outgoing http requests for status,
|
||||
// (this stops any queued likes, boosts, creates etc).
|
||||
f.state.Workers.Delivery.Queue.Delete("ObjectID", status.URI)
|
||||
f.state.Workers.Delivery.Queue.Delete("TargetID", status.URI)
|
||||
|
||||
// Ensure the status model is fully populated.
|
||||
if err := f.state.DB.PopulateStatus(ctx, status); err != nil {
|
||||
return gtserror.Newf("error populating status: %w", err)
|
||||
}
|
||||
|
||||
// Parse the outbox URI of the status author.
|
||||
outboxIRI, err := parseURI(status.Account.OutboxURI)
|
||||
if err != nil {
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"codeberg.org/gruf/go-logger/v2/level"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -42,32 +41,18 @@ import (
|
|||
type clientAPI struct {
|
||||
state *state.State
|
||||
converter *typeutils.Converter
|
||||
surface *surface
|
||||
surface *Surface
|
||||
federate *federate
|
||||
account *account.Processor
|
||||
utilF *utilF
|
||||
utils *utils
|
||||
}
|
||||
|
||||
func (p *Processor) EnqueueClientAPI(cctx context.Context, msgs ...messages.FromClientAPI) {
|
||||
_ = p.workers.ClientAPI.MustEnqueueCtx(cctx, func(wctx context.Context) {
|
||||
// Copy caller ctx values to worker's.
|
||||
wctx = gtscontext.WithValues(wctx, cctx)
|
||||
|
||||
// Process worker messages.
|
||||
for _, msg := range msgs {
|
||||
if err := p.ProcessFromClientAPI(wctx, msg); err != nil {
|
||||
log.Errorf(wctx, "error processing client API message: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
// Allocate new log fields slice
|
||||
fields := make([]kv.Field, 3, 4)
|
||||
fields[0] = kv.Field{"activityType", cMsg.APActivityType}
|
||||
fields[1] = kv.Field{"objectType", cMsg.APObjectType}
|
||||
fields[2] = kv.Field{"fromAccount", cMsg.OriginAccount.Username}
|
||||
fields[2] = kv.Field{"fromAccount", cMsg.Origin.Username}
|
||||
|
||||
// Include GTSModel in logs if appropriate.
|
||||
if cMsg.GTSModel != nil &&
|
||||
|
@ -217,7 +202,7 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg messages.From
|
|||
return gtserror.Newf("unhandled: %s %s", cMsg.APActivityType, cMsg.APObjectType)
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreateAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreateAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
newUser, ok := cMsg.GTSModel.(*gtsmodel.User)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.User", cMsg.GTSModel)
|
||||
|
@ -241,14 +226,14 @@ func (p *clientAPI) CreateAccount(ctx context.Context, cMsg messages.FromClientA
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreateStatus(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
status, ok := cMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the actor account.
|
||||
if err := p.utilF.incrementStatusesCount(ctx, cMsg.OriginAccount, status); err != nil {
|
||||
if err := p.utils.incrementStatusesCount(ctx, cMsg.Origin, status); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -269,7 +254,7 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg messages.FromClientAP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreatePollVote(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreatePollVote(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
// Cast the create poll vote attached to message.
|
||||
vote, ok := cMsg.GTSModel.(*gtsmodel.PollVote)
|
||||
if !ok {
|
||||
|
@ -310,14 +295,14 @@ func (p *clientAPI) CreatePollVote(ctx context.Context, cMsg messages.FromClient
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreateFollowReq(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreateFollowReq(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
followRequest, ok := cMsg.GTSModel.(*gtsmodel.FollowRequest)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.FollowRequest", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the target account.
|
||||
if err := p.utilF.incrementFollowRequestsCount(ctx, cMsg.TargetAccount); err != nil {
|
||||
if err := p.utils.incrementFollowRequestsCount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -338,7 +323,7 @@ func (p *clientAPI) CreateFollowReq(ctx context.Context, cMsg messages.FromClien
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreateLike(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
fave, ok := cMsg.GTSModel.(*gtsmodel.StatusFave)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.StatusFave", cMsg.GTSModel)
|
||||
|
@ -364,14 +349,14 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg messages.FromClientAPI)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
boost, ok := cMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the actor account.
|
||||
if err := p.utilF.incrementStatusesCount(ctx, cMsg.OriginAccount, boost); err != nil {
|
||||
if err := p.utils.incrementStatusesCount(ctx, cMsg.Origin, boost); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -396,7 +381,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg messages.FromClient
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) CreateBlock(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) CreateBlock(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
block, ok := cMsg.GTSModel.(*gtsmodel.Block)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", cMsg.GTSModel)
|
||||
|
@ -430,7 +415,7 @@ func (p *clientAPI) CreateBlock(ctx context.Context, cMsg messages.FromClientAPI
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UpdateStatus(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UpdateStatus(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
// Cast the updated Status model attached to msg.
|
||||
status, ok := cMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
|
@ -462,7 +447,7 @@ func (p *clientAPI) UpdateStatus(ctx context.Context, cMsg messages.FromClientAP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UpdateAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UpdateAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
account, ok := cMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return gtserror.Newf("cannot cast %T -> *gtsmodel.Account", cMsg.GTSModel)
|
||||
|
@ -475,7 +460,7 @@ func (p *clientAPI) UpdateAccount(ctx context.Context, cMsg messages.FromClientA
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UpdateReport(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UpdateReport(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
report, ok := cMsg.GTSModel.(*gtsmodel.Report)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Report", cMsg.GTSModel)
|
||||
|
@ -494,23 +479,23 @@ func (p *clientAPI) UpdateReport(ctx context.Context, cMsg messages.FromClientAP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) AcceptFollow(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) AcceptFollow(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
follow, ok := cMsg.GTSModel.(*gtsmodel.Follow)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Follow", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the target account.
|
||||
if err := p.utilF.decrementFollowRequestsCount(ctx, cMsg.TargetAccount); err != nil {
|
||||
if err := p.utils.decrementFollowRequestsCount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
if err := p.utilF.incrementFollowersCount(ctx, cMsg.TargetAccount); err != nil {
|
||||
if err := p.utils.incrementFollowersCount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
// Update stats for the origin account.
|
||||
if err := p.utilF.incrementFollowingCount(ctx, cMsg.OriginAccount); err != nil {
|
||||
if err := p.utils.incrementFollowingCount(ctx, cMsg.Origin); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -525,14 +510,14 @@ func (p *clientAPI) AcceptFollow(ctx context.Context, cMsg messages.FromClientAP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) RejectFollowRequest(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) RejectFollowRequest(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
followReq, ok := cMsg.GTSModel.(*gtsmodel.FollowRequest)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.FollowRequest", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the target account.
|
||||
if err := p.utilF.decrementFollowRequestsCount(ctx, cMsg.TargetAccount); err != nil {
|
||||
if err := p.utils.decrementFollowRequestsCount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -546,19 +531,19 @@ func (p *clientAPI) RejectFollowRequest(ctx context.Context, cMsg messages.FromC
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UndoFollow(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UndoFollow(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
follow, ok := cMsg.GTSModel.(*gtsmodel.Follow)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Follow", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update stats for the origin account.
|
||||
if err := p.utilF.decrementFollowingCount(ctx, cMsg.OriginAccount); err != nil {
|
||||
if err := p.utils.decrementFollowingCount(ctx, cMsg.Origin); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
// Update stats for the target account.
|
||||
if err := p.utilF.decrementFollowersCount(ctx, cMsg.TargetAccount); err != nil {
|
||||
if err := p.utils.decrementFollowersCount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -569,7 +554,7 @@ func (p *clientAPI) UndoFollow(ctx context.Context, cMsg messages.FromClientAPI)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UndoBlock(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UndoBlock(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
block, ok := cMsg.GTSModel.(*gtsmodel.Block)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", cMsg.GTSModel)
|
||||
|
@ -582,7 +567,7 @@ func (p *clientAPI) UndoBlock(ctx context.Context, cMsg messages.FromClientAPI)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UndoFave(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UndoFave(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
statusFave, ok := cMsg.GTSModel.(*gtsmodel.StatusFave)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.StatusFave", cMsg.GTSModel)
|
||||
|
@ -599,7 +584,7 @@ func (p *clientAPI) UndoFave(ctx context.Context, cMsg messages.FromClientAPI) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) UndoAnnounce(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) UndoAnnounce(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
status, ok := cMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", cMsg.GTSModel)
|
||||
|
@ -610,7 +595,7 @@ func (p *clientAPI) UndoAnnounce(ctx context.Context, cMsg messages.FromClientAP
|
|||
}
|
||||
|
||||
// Update stats for the origin account.
|
||||
if err := p.utilF.decrementStatusesCount(ctx, cMsg.OriginAccount); err != nil {
|
||||
if err := p.utils.decrementStatusesCount(ctx, cMsg.Origin); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -629,7 +614,7 @@ func (p *clientAPI) UndoAnnounce(ctx context.Context, cMsg messages.FromClientAP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) DeleteStatus(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) DeleteStatus(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
// Don't delete attachments, just unattach them:
|
||||
// this request comes from the client API and the
|
||||
// poster may want to use attachments again later.
|
||||
|
@ -648,12 +633,26 @@ func (p *clientAPI) DeleteStatus(ctx context.Context, cMsg messages.FromClientAP
|
|||
return gtserror.Newf("db error populating status: %w", err)
|
||||
}
|
||||
|
||||
if err := p.utilF.wipeStatus(ctx, status, deleteAttachments); err != nil {
|
||||
// Drop any outgoing queued AP requests about / targeting
|
||||
// this status, (stops queued likes, boosts, creates etc).
|
||||
p.state.Workers.Delivery.Queue.Delete("ObjectID", status.URI)
|
||||
p.state.Workers.Delivery.Queue.Delete("TargetID", status.URI)
|
||||
|
||||
// Drop any incoming queued client messages about / targeting
|
||||
// status, (stops processing of local origin data for status).
|
||||
p.state.Workers.Client.Queue.Delete("TargetURI", status.URI)
|
||||
|
||||
// Drop any incoming queued federator messages targeting status,
|
||||
// (stops processing of remote origin data targeting this status).
|
||||
p.state.Workers.Federator.Queue.Delete("TargetURI", status.URI)
|
||||
|
||||
// First perform the actual status deletion.
|
||||
if err := p.utils.wipeStatus(ctx, status, deleteAttachments); err != nil {
|
||||
log.Errorf(ctx, "error wiping status: %v", err)
|
||||
}
|
||||
|
||||
// Update stats for the origin account.
|
||||
if err := p.utilF.decrementStatusesCount(ctx, cMsg.OriginAccount); err != nil {
|
||||
if err := p.utils.decrementStatusesCount(ctx, cMsg.Origin); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -670,7 +669,7 @@ func (p *clientAPI) DeleteStatus(ctx context.Context, cMsg messages.FromClientAP
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) DeleteAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) DeleteAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
// The originID of the delete, one of:
|
||||
// - ID of a domain block, for which
|
||||
// this account delete is a side effect.
|
||||
|
@ -684,21 +683,41 @@ func (p *clientAPI) DeleteAccount(ctx context.Context, cMsg messages.FromClientA
|
|||
} else {
|
||||
// Origin is whichever account
|
||||
// originated this message.
|
||||
originID = cMsg.OriginAccount.ID
|
||||
originID = cMsg.Origin.ID
|
||||
}
|
||||
|
||||
if err := p.federate.DeleteAccount(ctx, cMsg.TargetAccount); err != nil {
|
||||
// Extract target account.
|
||||
account := cMsg.Target
|
||||
|
||||
// Drop any outgoing queued AP requests to / from / targeting
|
||||
// this account, (stops queued likes, boosts, creates etc).
|
||||
p.state.Workers.Delivery.Queue.Delete("ActorID", account.URI)
|
||||
p.state.Workers.Delivery.Queue.Delete("ObjectID", account.URI)
|
||||
p.state.Workers.Delivery.Queue.Delete("TargetID", account.URI)
|
||||
|
||||
// Drop any incoming queued client messages to / from this
|
||||
// account, (stops processing of local origin data for acccount).
|
||||
p.state.Workers.Client.Queue.Delete("Origin.ID", account.ID)
|
||||
p.state.Workers.Client.Queue.Delete("Target.ID", account.ID)
|
||||
p.state.Workers.Client.Queue.Delete("TargetURI", account.URI)
|
||||
|
||||
// Drop any incoming queued federator messages to this account,
|
||||
// (stops processing of remote origin data targeting this account).
|
||||
p.state.Workers.Federator.Queue.Delete("Receiving.ID", account.ID)
|
||||
p.state.Workers.Federator.Queue.Delete("TargetURI", account.URI)
|
||||
|
||||
if err := p.federate.DeleteAccount(ctx, cMsg.Target); err != nil {
|
||||
log.Errorf(ctx, "error federating account delete: %v", err)
|
||||
}
|
||||
|
||||
if err := p.account.Delete(ctx, cMsg.TargetAccount, originID); err != nil {
|
||||
if err := p.account.Delete(ctx, cMsg.Target, originID); err != nil {
|
||||
log.Errorf(ctx, "error deleting account: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) ReportAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) ReportAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
report, ok := cMsg.GTSModel.(*gtsmodel.Report)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Report", cMsg.GTSModel)
|
||||
|
@ -719,28 +738,28 @@ func (p *clientAPI) ReportAccount(ctx context.Context, cMsg messages.FromClientA
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) MoveAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) MoveAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
// Redirect each local follower of
|
||||
// OriginAccount to follow move target.
|
||||
p.utilF.redirectFollowers(ctx, cMsg.OriginAccount, cMsg.TargetAccount)
|
||||
p.utils.redirectFollowers(ctx, cMsg.Origin, cMsg.Target)
|
||||
|
||||
// At this point, we know OriginAccount has the
|
||||
// Move set on it. Just make sure it's populated.
|
||||
if err := p.state.DB.PopulateMove(ctx, cMsg.OriginAccount.Move); err != nil {
|
||||
if err := p.state.DB.PopulateMove(ctx, cMsg.Origin.Move); err != nil {
|
||||
return gtserror.Newf("error populating Move: %w", err)
|
||||
}
|
||||
|
||||
// Now send the Move message out to
|
||||
// OriginAccount's (remote) followers.
|
||||
if err := p.federate.MoveAccount(ctx, cMsg.OriginAccount); err != nil {
|
||||
if err := p.federate.MoveAccount(ctx, cMsg.Origin); err != nil {
|
||||
return gtserror.Newf("error federating account move: %w", err)
|
||||
}
|
||||
|
||||
// Mark the move attempt as successful.
|
||||
cMsg.OriginAccount.Move.SucceededAt = cMsg.OriginAccount.Move.AttemptedAt
|
||||
cMsg.Origin.Move.SucceededAt = cMsg.Origin.Move.AttemptedAt
|
||||
if err := p.state.DB.UpdateMove(
|
||||
ctx,
|
||||
cMsg.OriginAccount.Move,
|
||||
cMsg.Origin.Move,
|
||||
"succeeded_at",
|
||||
); err != nil {
|
||||
return gtserror.Newf("error marking move as successful: %w", err)
|
||||
|
@ -749,7 +768,7 @@ func (p *clientAPI) MoveAccount(ctx context.Context, cMsg messages.FromClientAPI
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) AcceptAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) AcceptAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
newUser, ok := cMsg.GTSModel.(*gtsmodel.User)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.User", cMsg.GTSModel)
|
||||
|
@ -772,17 +791,17 @@ func (p *clientAPI) AcceptAccount(ctx context.Context, cMsg messages.FromClientA
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *clientAPI) RejectAccount(ctx context.Context, cMsg messages.FromClientAPI) error {
|
||||
func (p *clientAPI) RejectAccount(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
deniedUser, ok := cMsg.GTSModel.(*gtsmodel.DeniedUser)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.DeniedUser", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Remove the account.
|
||||
if err := p.state.DB.DeleteAccount(ctx, cMsg.TargetAccount.ID); err != nil {
|
||||
if err := p.state.DB.DeleteAccount(ctx, cMsg.Target.ID); err != nil {
|
||||
log.Errorf(ctx,
|
||||
"db error deleting account %s: %v",
|
||||
cMsg.TargetAccount.ID, err,
|
||||
cMsg.Target.ID, err,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,9 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
@ -43,6 +45,7 @@ type FromClientAPITestSuite struct {
|
|||
|
||||
func (suite *FromClientAPITestSuite) newStatus(
|
||||
ctx context.Context,
|
||||
state *state.State,
|
||||
account *gtsmodel.Account,
|
||||
visibility gtsmodel.Visibility,
|
||||
replyToStatus *gtsmodel.Status,
|
||||
|
@ -87,7 +90,7 @@ func (suite *FromClientAPITestSuite) newStatus(
|
|||
TargetAccountID: replyToStatus.AccountID,
|
||||
}
|
||||
|
||||
if err := suite.db.PutMention(ctx, mention); err != nil {
|
||||
if err := state.DB.PutMention(ctx, mention); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
newStatus.Mentions = []*gtsmodel.Mention{mention}
|
||||
|
@ -104,7 +107,7 @@ func (suite *FromClientAPITestSuite) newStatus(
|
|||
|
||||
// Put the status in the db, to mimic what would
|
||||
// have already happened earlier up the flow.
|
||||
if err := suite.db.PutStatus(ctx, newStatus); err != nil {
|
||||
if err := state.DB.PutStatus(ctx, newStatus); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
|
@ -144,10 +147,11 @@ func (suite *FromClientAPITestSuite) checkStreamed(
|
|||
|
||||
func (suite *FromClientAPITestSuite) statusJSON(
|
||||
ctx context.Context,
|
||||
typeConverter *typeutils.Converter,
|
||||
status *gtsmodel.Status,
|
||||
requestingAccount *gtsmodel.Account,
|
||||
) string {
|
||||
apiStatus, err := suite.typeconverter.StatusToAPIStatus(
|
||||
apiStatus, err := typeConverter.StatusToAPIStatus(
|
||||
ctx,
|
||||
status,
|
||||
requestingAccount,
|
||||
|
@ -167,19 +171,27 @@ func (suite *FromClientAPITestSuite) statusJSON(
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
testList = suite.testLists["local_account_1_list_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
notifStream = streams[stream.TimelineNotifications]
|
||||
streams = suite.openStreams(ctx,
|
||||
testStructs.Processor,
|
||||
receivingAccount,
|
||||
[]string{testList.ID},
|
||||
)
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
notifStream = streams[stream.TimelineNotifications]
|
||||
|
||||
// Admin account posts a new top-level status.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
nil,
|
||||
|
@ -193,18 +205,18 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
|||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
|
||||
follow.Notify = util.Ptr(true)
|
||||
if err := suite.db.UpdateFollow(ctx, follow); err != nil {
|
||||
if err := testStructs.State.DB.UpdateFollow(ctx, follow); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -212,6 +224,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
|||
|
||||
statusJSON := suite.statusJSON(
|
||||
ctx,
|
||||
testStructs.TypeConverter,
|
||||
status,
|
||||
receivingAccount,
|
||||
)
|
||||
|
@ -236,7 +249,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
|||
var notif *gtsmodel.Notification
|
||||
if !testrig.WaitFor(func() bool {
|
||||
var err error
|
||||
notif, err = suite.db.GetNotification(
|
||||
notif, err = testStructs.State.DB.GetNotification(
|
||||
ctx,
|
||||
gtsmodel.NotificationStatus,
|
||||
receivingAccount.ID,
|
||||
|
@ -248,7 +261,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
|||
suite.FailNow("timed out waiting for new status notification")
|
||||
}
|
||||
|
||||
apiNotif, err := suite.typeconverter.NotificationToAPINotification(ctx, notif, nil)
|
||||
apiNotif, err := testStructs.TypeConverter.NotificationToAPINotification(ctx, notif, nil)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -268,12 +281,15 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
testList = suite.testLists["local_account_1_list_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
|
||||
|
@ -284,6 +300,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
|
|||
// post should also show in the list stream.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
suite.testStatuses["local_account_2_status_1"],
|
||||
|
@ -292,13 +309,13 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
|
|||
)
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -306,6 +323,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
|
|||
|
||||
statusJSON := suite.statusJSON(
|
||||
ctx,
|
||||
testStructs.TypeConverter,
|
||||
status,
|
||||
receivingAccount,
|
||||
)
|
||||
|
@ -328,6 +346,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyMuted() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
|
@ -338,6 +359,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyMuted() {
|
|||
// for this, but zork mutes this thread.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
suite.testStatuses["local_account_1_status_1"],
|
||||
|
@ -351,25 +373,25 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyMuted() {
|
|||
)
|
||||
|
||||
// Store the thread mute before processing new status.
|
||||
if err := suite.db.PutThreadMute(ctx, threadMute); err != nil {
|
||||
if err := testStructs.State.DB.PutThreadMute(ctx, threadMute); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Ensure no notification received.
|
||||
notif, err := suite.db.GetNotification(
|
||||
notif, err := testStructs.State.DB.GetNotification(
|
||||
ctx,
|
||||
gtsmodel.NotificationMention,
|
||||
receivingAccount.ID,
|
||||
|
@ -382,6 +404,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyMuted() {
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostMuted() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
|
@ -392,6 +417,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostMuted() {
|
|||
// for this, but zork mutes this thread.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
nil,
|
||||
|
@ -405,25 +431,25 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostMuted() {
|
|||
)
|
||||
|
||||
// Store the thread mute before processing new status.
|
||||
if err := suite.db.PutThreadMute(ctx, threadMute); err != nil {
|
||||
if err := testStructs.State.DB.PutThreadMute(ctx, threadMute); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Ensure no notification received.
|
||||
notif, err := suite.db.GetNotification(
|
||||
notif, err := testStructs.State.DB.GetNotification(
|
||||
ctx,
|
||||
gtsmodel.NotificationReblog,
|
||||
receivingAccount.ID,
|
||||
|
@ -436,6 +462,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostMuted() {
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyListOnlyOK() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
// We're modifying the test list so take a copy.
|
||||
testList := new(gtsmodel.List)
|
||||
*testList = *suite.testLists["local_account_1_list_1"]
|
||||
|
@ -444,13 +473,14 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
|
||||
// Admin account posts a reply to turtle.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
suite.testStatuses["local_account_2_status_1"],
|
||||
|
@ -463,18 +493,18 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
// and admin are in the same list, this means the reply
|
||||
// should be shown in the list.
|
||||
testList.RepliesPolicy = gtsmodel.RepliesPolicyList
|
||||
if err := suite.db.UpdateList(ctx, testList, "replies_policy"); err != nil {
|
||||
if err := testStructs.State.DB.UpdateList(ctx, testList, "replies_policy"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -482,6 +512,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
|
||||
statusJSON := suite.statusJSON(
|
||||
ctx,
|
||||
testStructs.TypeConverter,
|
||||
status,
|
||||
receivingAccount,
|
||||
)
|
||||
|
@ -504,6 +535,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyListOnlyNo() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
// We're modifying the test list so take a copy.
|
||||
testList := new(gtsmodel.List)
|
||||
*testList = *suite.testLists["local_account_1_list_1"]
|
||||
|
@ -512,13 +546,14 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
|
||||
// Admin account posts a reply to turtle.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
suite.testStatuses["local_account_2_status_1"],
|
||||
|
@ -531,23 +566,23 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
// about to remove turtle from the same list as admin,
|
||||
// so the new post should not be streamed to the list.
|
||||
testList.RepliesPolicy = gtsmodel.RepliesPolicyList
|
||||
if err := suite.db.UpdateList(ctx, testList, "replies_policy"); err != nil {
|
||||
if err := testStructs.State.DB.UpdateList(ctx, testList, "replies_policy"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Remove turtle from the list.
|
||||
if err := suite.db.DeleteListEntry(ctx, suite.testListEntries["local_account_1_list_1_entry_1"].ID); err != nil {
|
||||
if err := testStructs.State.DB.DeleteListEntry(ctx, suite.testListEntries["local_account_1_list_1_entry_1"].ID); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -555,6 +590,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
|
||||
statusJSON := suite.statusJSON(
|
||||
ctx,
|
||||
testStructs.TypeConverter,
|
||||
status,
|
||||
receivingAccount,
|
||||
)
|
||||
|
@ -577,6 +613,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPolicyNone() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
// We're modifying the test list so take a copy.
|
||||
testList := new(gtsmodel.List)
|
||||
*testList = *suite.testLists["local_account_1_list_1"]
|
||||
|
@ -585,13 +624,14 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPoli
|
|||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
|
||||
// Admin account posts a reply to turtle.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
suite.testStatuses["local_account_2_status_1"],
|
||||
|
@ -604,18 +644,18 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPoli
|
|||
// show any replies, the post should not
|
||||
// be streamed to the list.
|
||||
testList.RepliesPolicy = gtsmodel.RepliesPolicyNone
|
||||
if err := suite.db.UpdateList(ctx, testList, "replies_policy"); err != nil {
|
||||
if err := testStructs.State.DB.UpdateList(ctx, testList, "replies_policy"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -623,6 +663,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPoli
|
|||
|
||||
statusJSON := suite.statusJSON(
|
||||
ctx,
|
||||
testStructs.TypeConverter,
|
||||
status,
|
||||
receivingAccount,
|
||||
)
|
||||
|
@ -645,18 +686,22 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPoli
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoost() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
testList = suite.testLists["local_account_1_list_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
|
||||
// Admin account boosts a post by turtle.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
nil,
|
||||
|
@ -665,13 +710,13 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoost() {
|
|||
)
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -679,6 +724,7 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoost() {
|
|||
|
||||
statusJSON := suite.statusJSON(
|
||||
ctx,
|
||||
testStructs.TypeConverter,
|
||||
status,
|
||||
receivingAccount,
|
||||
)
|
||||
|
@ -701,18 +747,22 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoost() {
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostNoReblogs() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
testList = suite.testLists["local_account_1_list_1"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID})
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, []string{testList.ID})
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
|
||||
// Admin account boosts a post by turtle.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
nil,
|
||||
|
@ -725,18 +775,18 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostNoReblogs() {
|
|||
follow := new(gtsmodel.Follow)
|
||||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
follow.ShowReblogs = util.Ptr(false)
|
||||
if err := suite.db.UpdateFollow(ctx, follow, "show_reblogs"); err != nil {
|
||||
if err := testStructs.State.DB.UpdateFollow(ctx, follow, "show_reblogs"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
OriginAccount: postingAccount,
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -760,30 +810,33 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostNoReblogs() {
|
|||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessStatusDelete() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
deletingAccount = suite.testAccounts["local_account_1"]
|
||||
receivingAccount = suite.testAccounts["local_account_2"]
|
||||
deletedStatus = suite.testStatuses["local_account_1_status_1"]
|
||||
boostOfDeletedStatus = suite.testStatuses["admin_account_status_4"]
|
||||
streams = suite.openStreams(ctx, receivingAccount, nil)
|
||||
streams = suite.openStreams(ctx, testStructs.Processor, receivingAccount, nil)
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
)
|
||||
|
||||
// Delete the status from the db first, to mimic what
|
||||
// would have already happened earlier up the flow
|
||||
if err := suite.db.DeleteStatusByID(ctx, deletedStatus.ID); err != nil {
|
||||
if err := testStructs.State.DB.DeleteStatusByID(ctx, deletedStatus.ID); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the status delete.
|
||||
if err := suite.processor.Workers().ProcessFromClientAPI(
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
messages.FromClientAPI{
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: deletedStatus,
|
||||
OriginAccount: deletingAccount,
|
||||
Origin: deletingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
@ -809,7 +862,7 @@ func (suite *FromClientAPITestSuite) TestProcessStatusDelete() {
|
|||
|
||||
// Boost should no longer be in the database.
|
||||
if !testrig.WaitFor(func() bool {
|
||||
_, err := suite.db.GetStatusByID(ctx, boostOfDeletedStatus.ID)
|
||||
_, err := testStructs.State.DB.GetStatusByID(ctx, boostOfDeletedStatus.ID)
|
||||
return errors.Is(err, db.ErrNoEntries)
|
||||
}) {
|
||||
suite.FailNow("timed out waiting for status delete")
|
||||
|
|
|
@ -19,13 +19,14 @@ package workers
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
"codeberg.org/gruf/go-logger/v2/level"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -40,37 +41,23 @@ import (
|
|||
// from the federation/ActivityPub API.
|
||||
type fediAPI struct {
|
||||
state *state.State
|
||||
surface *surface
|
||||
surface *Surface
|
||||
federate *federate
|
||||
account *account.Processor
|
||||
utilF *utilF
|
||||
utils *utils
|
||||
}
|
||||
|
||||
func (p *Processor) EnqueueFediAPI(cctx context.Context, msgs ...messages.FromFediAPI) {
|
||||
_ = p.workers.Federator.MustEnqueueCtx(cctx, func(wctx context.Context) {
|
||||
// Copy caller ctx values to worker's.
|
||||
wctx = gtscontext.WithValues(wctx, cctx)
|
||||
|
||||
// Process worker messages.
|
||||
for _, msg := range msgs {
|
||||
if err := p.ProcessFromFediAPI(wctx, msg); err != nil {
|
||||
log.Errorf(wctx, "error processing fedi API message: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Allocate new log fields slice
|
||||
fields := make([]kv.Field, 3, 5)
|
||||
fields[0] = kv.Field{"activityType", fMsg.APActivityType}
|
||||
fields[1] = kv.Field{"objectType", fMsg.APObjectType}
|
||||
fields[2] = kv.Field{"toAccount", fMsg.ReceivingAccount.Username}
|
||||
fields[2] = kv.Field{"toAccount", fMsg.Receiving.Username}
|
||||
|
||||
if fMsg.APIri != nil {
|
||||
if fMsg.APIRI != nil {
|
||||
// An IRI was supplied, append to log
|
||||
fields = append(fields, kv.Field{
|
||||
"iri", fMsg.APIri,
|
||||
"iri", fMsg.APIRI,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -168,7 +155,7 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg messages.FromFe
|
|||
return gtserror.Newf("unhandled: %s %s", fMsg.APActivityType, fMsg.APObjectType)
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreateStatus(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
var (
|
||||
status *gtsmodel.Status
|
||||
statusable ap.Statusable
|
||||
|
@ -178,11 +165,11 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
var ok bool
|
||||
|
||||
switch {
|
||||
case fMsg.APObjectModel != nil:
|
||||
case fMsg.APObject != nil:
|
||||
// A model was provided, extract this from message.
|
||||
statusable, ok = fMsg.APObjectModel.(ap.Statusable)
|
||||
statusable, ok = fMsg.APObject.(ap.Statusable)
|
||||
if !ok {
|
||||
return gtserror.Newf("cannot cast %T -> ap.Statusable", fMsg.APObjectModel)
|
||||
return gtserror.Newf("cannot cast %T -> ap.Statusable", fMsg.APObject)
|
||||
}
|
||||
|
||||
// Create bare-bones model to pass
|
||||
|
@ -196,7 +183,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
// statusable model, which it will use to further flesh out
|
||||
// the bare bones model and insert it into the database.
|
||||
status, statusable, err = p.federate.RefreshStatus(ctx,
|
||||
fMsg.ReceivingAccount.Username,
|
||||
fMsg.Receiving.Username,
|
||||
bareStatus,
|
||||
statusable,
|
||||
// Force refresh within 5min window.
|
||||
|
@ -206,15 +193,15 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
return gtserror.Newf("error processing new status %s: %w", bareStatus.URI, err)
|
||||
}
|
||||
|
||||
case fMsg.APIri != nil:
|
||||
case fMsg.APIRI != nil:
|
||||
// Model was not set, deref with IRI (this is a forward).
|
||||
// This will also cause the status to be inserted into the db.
|
||||
status, statusable, err = p.federate.GetStatusByURI(ctx,
|
||||
fMsg.ReceivingAccount.Username,
|
||||
fMsg.APIri,
|
||||
fMsg.Receiving.Username,
|
||||
fMsg.APIRI,
|
||||
)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error dereferencing forwarded status %s: %w", fMsg.APIri, err)
|
||||
return gtserror.Newf("error dereferencing forwarded status %s: %w", fMsg.APIRI, err)
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -230,7 +217,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
}
|
||||
|
||||
// Update stats for the remote account.
|
||||
if err := p.utilF.incrementStatusesCount(ctx, fMsg.RequestingAccount, status); err != nil {
|
||||
if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, status); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -248,7 +235,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Cast poll vote type from the worker message.
|
||||
vote, ok := fMsg.GTSModel.(*gtsmodel.PollVote)
|
||||
if !ok {
|
||||
|
@ -293,7 +280,7 @@ func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg messages.FromFediAPI)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
followRequest, ok := fMsg.GTSModel.(*gtsmodel.FollowRequest)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.FollowRequest", fMsg.GTSModel)
|
||||
|
@ -310,7 +297,7 @@ func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg messages.FromFediAPI
|
|||
}
|
||||
|
||||
// And update stats for the local account.
|
||||
if err := p.utilF.incrementFollowRequestsCount(ctx, fMsg.ReceivingAccount); err != nil {
|
||||
if err := p.utils.incrementFollowRequestsCount(ctx, fMsg.Receiving); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -330,12 +317,12 @@ func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg messages.FromFediAPI
|
|||
}
|
||||
|
||||
// Update stats for the local account.
|
||||
if err := p.utilF.incrementFollowersCount(ctx, fMsg.ReceivingAccount); err != nil {
|
||||
if err := p.utils.incrementFollowersCount(ctx, fMsg.Receiving); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
// Update stats for the remote account.
|
||||
if err := p.utilF.incrementFollowingCount(ctx, fMsg.RequestingAccount); err != nil {
|
||||
if err := p.utils.incrementFollowingCount(ctx, fMsg.Requesting); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -350,7 +337,7 @@ func (p *fediAPI) CreateFollowReq(ctx context.Context, fMsg messages.FromFediAPI
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreateLike(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
fave, ok := fMsg.GTSModel.(*gtsmodel.StatusFave)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.StatusFave", fMsg.GTSModel)
|
||||
|
@ -372,7 +359,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg messages.FromFediAPI) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
|
||||
|
@ -386,7 +373,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg messages.FromFediAPI)
|
|||
boost, err = p.federate.EnrichAnnounce(
|
||||
ctx,
|
||||
boost,
|
||||
fMsg.ReceivingAccount.Username,
|
||||
fMsg.Receiving.Username,
|
||||
)
|
||||
if err != nil {
|
||||
if gtserror.IsUnretrievable(err) {
|
||||
|
@ -400,7 +387,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg messages.FromFediAPI)
|
|||
}
|
||||
|
||||
// Update stats for the remote account.
|
||||
if err := p.utilF.incrementStatusesCount(ctx, fMsg.RequestingAccount, boost); err != nil {
|
||||
if err := p.utils.incrementStatusesCount(ctx, fMsg.Requesting, boost); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -420,7 +407,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg messages.FromFediAPI)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreateBlock(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreateBlock(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
block, ok := fMsg.GTSModel.(*gtsmodel.Block)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Block", fMsg.GTSModel)
|
||||
|
@ -499,7 +486,7 @@ func (p *fediAPI) CreateBlock(ctx context.Context, fMsg messages.FromFediAPI) er
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) CreateFlag(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) CreateFlag(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
incomingReport, ok := fMsg.GTSModel.(*gtsmodel.Report)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Report", fMsg.GTSModel)
|
||||
|
@ -515,7 +502,7 @@ func (p *fediAPI) CreateFlag(ctx context.Context, fMsg messages.FromFediAPI) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UpdateAccount(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) UpdateAccount(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Parse the old/existing account model.
|
||||
account, ok := fMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
|
@ -523,15 +510,15 @@ func (p *fediAPI) UpdateAccount(ctx context.Context, fMsg messages.FromFediAPI)
|
|||
}
|
||||
|
||||
// Because this was an Update, the new Accountable should be set on the message.
|
||||
apubAcc, ok := fMsg.APObjectModel.(ap.Accountable)
|
||||
apubAcc, ok := fMsg.APObject.(ap.Accountable)
|
||||
if !ok {
|
||||
return gtserror.Newf("cannot cast %T -> ap.Accountable", fMsg.APObjectModel)
|
||||
return gtserror.Newf("cannot cast %T -> ap.Accountable", fMsg.APObject)
|
||||
}
|
||||
|
||||
// Fetch up-to-date bio, avatar, header, etc.
|
||||
_, _, err := p.federate.RefreshAccount(
|
||||
ctx,
|
||||
fMsg.ReceivingAccount.Username,
|
||||
fMsg.Receiving.Username,
|
||||
account,
|
||||
apubAcc,
|
||||
// Force refresh within 5min window.
|
||||
|
@ -544,25 +531,25 @@ func (p *fediAPI) UpdateAccount(ctx context.Context, fMsg messages.FromFediAPI)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptFollow(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) AcceptFollow(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Update stats for the remote account.
|
||||
if err := p.utilF.decrementFollowRequestsCount(ctx, fMsg.RequestingAccount); err != nil {
|
||||
if err := p.utils.decrementFollowRequestsCount(ctx, fMsg.Requesting); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
if err := p.utilF.incrementFollowersCount(ctx, fMsg.RequestingAccount); err != nil {
|
||||
if err := p.utils.incrementFollowersCount(ctx, fMsg.Requesting); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
// Update stats for the local account.
|
||||
if err := p.utilF.incrementFollowingCount(ctx, fMsg.ReceivingAccount); err != nil {
|
||||
if err := p.utils.incrementFollowingCount(ctx, fMsg.Receiving); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Cast the existing Status model attached to msg.
|
||||
existing, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
|
@ -570,12 +557,12 @@ func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
}
|
||||
|
||||
// Cast the updated ActivityPub statusable object .
|
||||
apStatus, _ := fMsg.APObjectModel.(ap.Statusable)
|
||||
apStatus, _ := fMsg.APObject.(ap.Statusable)
|
||||
|
||||
// Fetch up-to-date attach status attachments, etc.
|
||||
status, _, err := p.federate.RefreshStatus(
|
||||
ctx,
|
||||
fMsg.ReceivingAccount.Username,
|
||||
fMsg.Receiving.Username,
|
||||
existing,
|
||||
apStatus,
|
||||
// Force refresh within 5min window.
|
||||
|
@ -605,7 +592,7 @@ func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) DeleteStatus(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) DeleteStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Delete attachments from this status, since this request
|
||||
// comes from the federating API, and there's no way the
|
||||
// poster can do a delete + redraft for it on our instance.
|
||||
|
@ -616,12 +603,34 @@ func (p *fediAPI) DeleteStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
if err := p.utilF.wipeStatus(ctx, status, deleteAttachments); err != nil {
|
||||
// Try to populate status structs if possible,
|
||||
// in order to more thoroughly remove them.
|
||||
if err := p.state.DB.PopulateStatus(
|
||||
ctx, status,
|
||||
); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("db error populating status: %w", err)
|
||||
}
|
||||
|
||||
// Drop any outgoing queued AP requests about / targeting
|
||||
// this status, (stops queued likes, boosts, creates etc).
|
||||
p.state.Workers.Delivery.Queue.Delete("ObjectID", status.URI)
|
||||
p.state.Workers.Delivery.Queue.Delete("TargetID", status.URI)
|
||||
|
||||
// Drop any incoming queued client messages about / targeting
|
||||
// status, (stops processing of local origin data for status).
|
||||
p.state.Workers.Client.Queue.Delete("TargetURI", status.URI)
|
||||
|
||||
// Drop any incoming queued federator messages targeting status,
|
||||
// (stops processing of remote origin data targeting this status).
|
||||
p.state.Workers.Federator.Queue.Delete("TargetURI", status.URI)
|
||||
|
||||
// First perform the actual status deletion.
|
||||
if err := p.utils.wipeStatus(ctx, status, deleteAttachments); err != nil {
|
||||
log.Errorf(ctx, "error wiping status: %v", err)
|
||||
}
|
||||
|
||||
// Update stats for the remote account.
|
||||
if err := p.utilF.decrementStatusesCount(ctx, fMsg.RequestingAccount); err != nil {
|
||||
if err := p.utils.decrementStatusesCount(ctx, fMsg.Requesting); err != nil {
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
|
@ -634,12 +643,28 @@ func (p *fediAPI) DeleteStatus(ctx context.Context, fMsg messages.FromFediAPI) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) DeleteAccount(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
func (p *fediAPI) DeleteAccount(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
account, ok := fMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Account", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Drop any outgoing queued AP requests to / from / targeting
|
||||
// this account, (stops queued likes, boosts, creates etc).
|
||||
p.state.Workers.Delivery.Queue.Delete("ObjectID", account.URI)
|
||||
p.state.Workers.Delivery.Queue.Delete("TargetID", account.URI)
|
||||
|
||||
// Drop any incoming queued client messages to / from this
|
||||
// account, (stops processing of local origin data for acccount).
|
||||
p.state.Workers.Client.Queue.Delete("Target.ID", account.ID)
|
||||
p.state.Workers.Client.Queue.Delete("TargetURI", account.URI)
|
||||
|
||||
// Drop any incoming queued federator messages to this account,
|
||||
// (stops processing of remote origin data targeting this account).
|
||||
p.state.Workers.Federator.Queue.Delete("Requesting.ID", account.ID)
|
||||
p.state.Workers.Federator.Queue.Delete("TargetURI", account.URI)
|
||||
|
||||
// First perform the actual account deletion.
|
||||
if err := p.account.Delete(ctx, account, account.ID); err != nil {
|
||||
log.Errorf(ctx, "error deleting account: %v", err)
|
||||
}
|
||||
|
|
|
@ -220,10 +220,7 @@ func (p *fediAPI) GetOrCreateMove(
|
|||
// APActivityType: "Move"
|
||||
// GTSModel: stub *gtsmodel.Move.
|
||||
// ReceivingAccount: Account of inbox owner receiving the Move.
|
||||
func (p *fediAPI) MoveAccount(ctx context.Context, fMsg messages.FromFediAPI) error {
|
||||
// The account who received the Move message.
|
||||
receiver := fMsg.ReceivingAccount
|
||||
|
||||
func (p *fediAPI) MoveAccount(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// *gtsmodel.Move activity.
|
||||
stubMove, ok := fMsg.GTSModel.(*gtsmodel.Move)
|
||||
if !ok {
|
||||
|
@ -236,7 +233,7 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg messages.FromFediAPI) er
|
|||
// Move origin and target info.
|
||||
var (
|
||||
originAcctURIStr = stubMove.OriginURI
|
||||
originAcct = fMsg.RequestingAccount
|
||||
originAcct = fMsg.Requesting
|
||||
targetAcctURIStr = stubMove.TargetURI
|
||||
targetAcctURI = stubMove.Target
|
||||
)
|
||||
|
@ -308,7 +305,7 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg messages.FromFediAPI) er
|
|||
// Account to which the Move is taking place.
|
||||
targetAcct, targetAcctable, err := p.federate.GetAccountByURI(
|
||||
ctx,
|
||||
receiver.Username,
|
||||
fMsg.Receiving.Username,
|
||||
targetAcctURI,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -340,7 +337,7 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg messages.FromFediAPI) er
|
|||
// Force refresh Move target account
|
||||
// to ensure we have up-to-date version.
|
||||
targetAcct, _, err = p.federate.RefreshAccount(ctx,
|
||||
receiver.Username,
|
||||
fMsg.Receiving.Username,
|
||||
targetAcct,
|
||||
targetAcctable,
|
||||
dereferencing.Freshest,
|
||||
|
@ -379,7 +376,7 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg messages.FromFediAPI) er
|
|||
|
||||
// Transfer originAcct's followers
|
||||
// on this instance to targetAcct.
|
||||
redirectOK := p.utilF.redirectFollowers(
|
||||
redirectOK := p.utils.redirectFollowers(
|
||||
ctx,
|
||||
originAcct,
|
||||
targetAcct,
|
||||
|
|
|
@ -42,6 +42,9 @@ type FromFediAPITestSuite struct {
|
|||
|
||||
// remote_account_1 boosts the first status of local_account_1
|
||||
func (suite *FromFediAPITestSuite) TestProcessFederationAnnounce() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
boostedStatus := suite.testStatuses["local_account_1_status_1"]
|
||||
boostingAccount := suite.testAccounts["remote_account_1"]
|
||||
announceStatus := >smodel.Status{}
|
||||
|
@ -54,19 +57,19 @@ func (suite *FromFediAPITestSuite) TestProcessFederationAnnounce() {
|
|||
announceStatus.Account = boostingAccount
|
||||
announceStatus.Visibility = boostedStatus.Visibility
|
||||
|
||||
err := suite.processor.Workers().ProcessFromFediAPI(context.Background(), messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: announceStatus,
|
||||
ReceivingAccount: suite.testAccounts["local_account_1"],
|
||||
RequestingAccount: boostingAccount,
|
||||
err := testStructs.Processor.Workers().ProcessFromFediAPI(context.Background(), &messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityAnnounce,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: announceStatus,
|
||||
Receiving: suite.testAccounts["local_account_1"],
|
||||
Requesting: boostingAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// side effects should be triggered
|
||||
// 1. status should have an ID, and be in the database
|
||||
suite.NotEmpty(announceStatus.ID)
|
||||
_, err = suite.db.GetStatusByID(context.Background(), announceStatus.ID)
|
||||
_, err = testStructs.State.DB.GetStatusByID(context.Background(), announceStatus.ID)
|
||||
suite.NoError(err)
|
||||
|
||||
// 2. a notification should exist for the announce
|
||||
|
@ -77,7 +80,7 @@ func (suite *FromFediAPITestSuite) TestProcessFederationAnnounce() {
|
|||
},
|
||||
}
|
||||
notif := >smodel.Notification{}
|
||||
err = suite.db.GetWhere(context.Background(), where, notif)
|
||||
err = testStructs.State.DB.GetWhere(context.Background(), where, notif)
|
||||
suite.NoError(err)
|
||||
suite.Equal(gtsmodel.NotificationReblog, notif.NotificationType)
|
||||
suite.Equal(boostedStatus.AccountID, notif.TargetAccountID)
|
||||
|
@ -87,6 +90,9 @@ func (suite *FromFediAPITestSuite) TestProcessFederationAnnounce() {
|
|||
}
|
||||
|
||||
func (suite *FromFediAPITestSuite) TestProcessReplyMention() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
repliedAccount := suite.testAccounts["local_account_1"]
|
||||
repliedStatus := suite.testStatuses["local_account_1_status_1"]
|
||||
replyingAccount := suite.testAccounts["remote_account_1"]
|
||||
|
@ -97,7 +103,7 @@ func (suite *FromFediAPITestSuite) TestProcessReplyMention() {
|
|||
replyingAccount.FetchedAt = time.Now()
|
||||
replyingAccount.SuspendedAt = time.Time{}
|
||||
replyingAccount.SuspensionOrigin = ""
|
||||
err := suite.state.DB.UpdateAccount(context.Background(),
|
||||
err := testStructs.State.DB.UpdateAccount(context.Background(),
|
||||
replyingAccount,
|
||||
"fetched_at",
|
||||
"suspended_at",
|
||||
|
@ -111,27 +117,27 @@ func (suite *FromFediAPITestSuite) TestProcessReplyMention() {
|
|||
ap.AppendInReplyTo(replyingStatusable, testrig.URLMustParse(repliedStatus.URI))
|
||||
|
||||
// Open a websocket stream to later test the streamed status reply.
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome)
|
||||
wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
// Send the replied status off to the fedi worker to be further processed.
|
||||
err = suite.processor.Workers().ProcessFromFediAPI(context.Background(), messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectModel: replyingStatusable,
|
||||
ReceivingAccount: repliedAccount,
|
||||
RequestingAccount: replyingAccount,
|
||||
err = testStructs.Processor.Workers().ProcessFromFediAPI(context.Background(), &messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObject: replyingStatusable,
|
||||
Receiving: repliedAccount,
|
||||
Requesting: replyingAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// side effects should be triggered
|
||||
// 1. status should be in the database
|
||||
replyingStatus, err := suite.state.DB.GetStatusByURI(context.Background(), replyingURI)
|
||||
replyingStatus, err := testStructs.State.DB.GetStatusByURI(context.Background(), replyingURI)
|
||||
suite.NoError(err)
|
||||
|
||||
// 2. a notification should exist for the mention
|
||||
var notif gtsmodel.Notification
|
||||
err = suite.db.GetWhere(context.Background(), []db.Where{
|
||||
err = testStructs.State.DB.GetWhere(context.Background(), []db.Where{
|
||||
{Key: "status_id", Value: replyingStatus.ID},
|
||||
}, ¬if)
|
||||
suite.NoError(err)
|
||||
|
@ -156,11 +162,14 @@ func (suite *FromFediAPITestSuite) TestProcessReplyMention() {
|
|||
}
|
||||
|
||||
func (suite *FromFediAPITestSuite) TestProcessFave() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
favedAccount := suite.testAccounts["local_account_1"]
|
||||
favedStatus := suite.testStatuses["local_account_1_status_1"]
|
||||
favingAccount := suite.testAccounts["remote_account_1"]
|
||||
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), favedAccount, stream.TimelineNotifications)
|
||||
wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), favedAccount, stream.TimelineNotifications)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
fave := >smodel.StatusFave{
|
||||
|
@ -176,15 +185,15 @@ func (suite *FromFediAPITestSuite) TestProcessFave() {
|
|||
URI: favingAccount.URI + "/faves/aaaaaaaaaaaa",
|
||||
}
|
||||
|
||||
err := suite.db.Put(context.Background(), fave)
|
||||
err := testStructs.State.DB.Put(context.Background(), fave)
|
||||
suite.NoError(err)
|
||||
|
||||
err = suite.processor.Workers().ProcessFromFediAPI(context.Background(), messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
ReceivingAccount: favedAccount,
|
||||
RequestingAccount: favingAccount,
|
||||
err = testStructs.Processor.Workers().ProcessFromFediAPI(context.Background(), &messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
Receiving: favedAccount,
|
||||
Requesting: favingAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
@ -202,7 +211,7 @@ func (suite *FromFediAPITestSuite) TestProcessFave() {
|
|||
}
|
||||
|
||||
notif := >smodel.Notification{}
|
||||
err = suite.db.GetWhere(context.Background(), where, notif)
|
||||
err = testStructs.State.DB.GetWhere(context.Background(), where, notif)
|
||||
suite.NoError(err)
|
||||
suite.Equal(gtsmodel.NotificationFave, notif.NotificationType)
|
||||
suite.Equal(fave.TargetAccountID, notif.TargetAccountID)
|
||||
|
@ -225,12 +234,15 @@ func (suite *FromFediAPITestSuite) TestProcessFave() {
|
|||
// This tests for an issue we were seeing where Misskey sends out faves to inboxes of people that don't own
|
||||
// the fave, but just follow the actor who received the fave.
|
||||
func (suite *FromFediAPITestSuite) TestProcessFaveWithDifferentReceivingAccount() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
receivingAccount := suite.testAccounts["local_account_2"]
|
||||
favedAccount := suite.testAccounts["local_account_1"]
|
||||
favedStatus := suite.testStatuses["local_account_1_status_1"]
|
||||
favingAccount := suite.testAccounts["remote_account_1"]
|
||||
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), receivingAccount, stream.TimelineHome)
|
||||
wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), receivingAccount, stream.TimelineHome)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
fave := >smodel.StatusFave{
|
||||
|
@ -246,15 +258,15 @@ func (suite *FromFediAPITestSuite) TestProcessFaveWithDifferentReceivingAccount(
|
|||
URI: favingAccount.URI + "/faves/aaaaaaaaaaaa",
|
||||
}
|
||||
|
||||
err := suite.db.Put(context.Background(), fave)
|
||||
err := testStructs.State.DB.Put(context.Background(), fave)
|
||||
suite.NoError(err)
|
||||
|
||||
err = suite.processor.Workers().ProcessFromFediAPI(context.Background(), messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
ReceivingAccount: receivingAccount,
|
||||
RequestingAccount: favingAccount,
|
||||
err = testStructs.Processor.Workers().ProcessFromFediAPI(context.Background(), &messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityLike,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: fave,
|
||||
Receiving: receivingAccount,
|
||||
Requesting: favingAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
@ -272,7 +284,7 @@ func (suite *FromFediAPITestSuite) TestProcessFaveWithDifferentReceivingAccount(
|
|||
}
|
||||
|
||||
notif := >smodel.Notification{}
|
||||
err = suite.db.GetWhere(context.Background(), where, notif)
|
||||
err = testStructs.State.DB.GetWhere(context.Background(), where, notif)
|
||||
suite.NoError(err)
|
||||
suite.Equal(gtsmodel.NotificationFave, notif.NotificationType)
|
||||
suite.Equal(fave.TargetAccountID, notif.TargetAccountID)
|
||||
|
@ -287,6 +299,9 @@ func (suite *FromFediAPITestSuite) TestProcessFaveWithDifferentReceivingAccount(
|
|||
}
|
||||
|
||||
func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
deletedAccount := suite.testAccounts["remote_account_1"]
|
||||
|
@ -304,7 +319,7 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
|
|||
URI: fmt.Sprintf("%s/follows/01FGRY72ASHBSET64353DPHK9T", deletedAccount.URI),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
err := suite.db.Put(ctx, zorkFollowSatan)
|
||||
err := testStructs.State.DB.Put(ctx, zorkFollowSatan)
|
||||
suite.NoError(err)
|
||||
|
||||
satanFollowZork := >smodel.Follow{
|
||||
|
@ -317,36 +332,36 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
|
|||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", receivingAccount.URI),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
err = suite.db.Put(ctx, satanFollowZork)
|
||||
err = testStructs.State.DB.Put(ctx, satanFollowZork)
|
||||
suite.NoError(err)
|
||||
|
||||
// now they are mufos!
|
||||
err = suite.processor.Workers().ProcessFromFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: deletedAccount,
|
||||
ReceivingAccount: receivingAccount,
|
||||
RequestingAccount: deletedAccount,
|
||||
err = testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityDelete,
|
||||
GTSModel: deletedAccount,
|
||||
Receiving: receivingAccount,
|
||||
Requesting: deletedAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// local account 2 blocked foss_satan, that block should be gone now
|
||||
testBlock := suite.testBlocks["local_account_2_block_remote_account_1"]
|
||||
dbBlock := >smodel.Block{}
|
||||
err = suite.db.GetByID(ctx, testBlock.ID, dbBlock)
|
||||
err = testStructs.State.DB.GetByID(ctx, testBlock.ID, dbBlock)
|
||||
suite.ErrorIs(err, db.ErrNoEntries)
|
||||
|
||||
// the mufos should be gone now too
|
||||
satanFollowsZork, err := suite.db.IsFollowing(ctx, deletedAccount.ID, receivingAccount.ID)
|
||||
satanFollowsZork, err := testStructs.State.DB.IsFollowing(ctx, deletedAccount.ID, receivingAccount.ID)
|
||||
suite.NoError(err)
|
||||
suite.False(satanFollowsZork)
|
||||
zorkFollowsSatan, err := suite.db.IsFollowing(ctx, receivingAccount.ID, deletedAccount.ID)
|
||||
zorkFollowsSatan, err := testStructs.State.DB.IsFollowing(ctx, receivingAccount.ID, deletedAccount.ID)
|
||||
suite.NoError(err)
|
||||
suite.False(zorkFollowsSatan)
|
||||
|
||||
// no statuses from foss satan should be left in the database
|
||||
if !testrig.WaitFor(func() bool {
|
||||
s, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false)
|
||||
s, err := testStructs.State.DB.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false)
|
||||
return s == nil && err == db.ErrNoEntries
|
||||
}) {
|
||||
suite.FailNow("timeout waiting for statuses to be deleted")
|
||||
|
@ -356,7 +371,7 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
|
|||
|
||||
// account data should be zeroed.
|
||||
if !testrig.WaitFor(func() bool {
|
||||
dbAccount, err = suite.db.GetAccountByID(ctx, deletedAccount.ID)
|
||||
dbAccount, err = testStructs.State.DB.GetAccountByID(ctx, deletedAccount.ID)
|
||||
return err == nil && dbAccount.DisplayName == ""
|
||||
}) {
|
||||
suite.FailNow("timeout waiting for statuses to be deleted")
|
||||
|
@ -375,6 +390,9 @@ func (suite *FromFediAPITestSuite) TestProcessAccountDelete() {
|
|||
}
|
||||
|
||||
func (suite *FromFediAPITestSuite) TestProcessFollowRequestLocked() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
originAccount := suite.testAccounts["remote_account_1"]
|
||||
|
@ -382,7 +400,7 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestLocked() {
|
|||
// target is a locked account
|
||||
targetAccount := suite.testAccounts["local_account_2"]
|
||||
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), targetAccount, stream.TimelineHome)
|
||||
wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), targetAccount, stream.TimelineHome)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
// put the follow request in the database as though it had passed through the federating db already
|
||||
|
@ -399,15 +417,15 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestLocked() {
|
|||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
err := suite.db.Put(ctx, satanFollowRequestTurtle)
|
||||
err := testStructs.State.DB.Put(ctx, satanFollowRequestTurtle)
|
||||
suite.NoError(err)
|
||||
|
||||
err = suite.processor.Workers().ProcessFromFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: satanFollowRequestTurtle,
|
||||
ReceivingAccount: targetAccount,
|
||||
RequestingAccount: originAccount,
|
||||
err = testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: satanFollowRequestTurtle,
|
||||
Receiving: targetAccount,
|
||||
Requesting: originAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
@ -425,10 +443,13 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestLocked() {
|
|||
suite.Equal(originAccount.ID, notif.Account.ID)
|
||||
|
||||
// no messages should have been sent out, since we didn't need to federate an accept
|
||||
suite.Empty(&suite.httpClient.SentMessages)
|
||||
suite.Empty(testStructs.HTTPClient.SentMessages)
|
||||
}
|
||||
|
||||
func (suite *FromFediAPITestSuite) TestProcessFollowRequestUnlocked() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
originAccount := suite.testAccounts["remote_account_1"]
|
||||
|
@ -436,7 +457,7 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestUnlocked() {
|
|||
// target is an unlocked account
|
||||
targetAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), targetAccount, stream.TimelineHome)
|
||||
wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), targetAccount, stream.TimelineHome)
|
||||
suite.NoError(errWithCode)
|
||||
|
||||
// put the follow request in the database as though it had passed through the federating db already
|
||||
|
@ -453,15 +474,15 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestUnlocked() {
|
|||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
err := suite.db.Put(ctx, satanFollowRequestTurtle)
|
||||
err := testStructs.State.DB.Put(ctx, satanFollowRequestTurtle)
|
||||
suite.NoError(err)
|
||||
|
||||
err = suite.processor.Workers().ProcessFromFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: satanFollowRequestTurtle,
|
||||
ReceivingAccount: targetAccount,
|
||||
RequestingAccount: originAccount,
|
||||
err = testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
|
||||
APObjectType: ap.ActivityFollow,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: satanFollowRequestTurtle,
|
||||
Receiving: targetAccount,
|
||||
Requesting: originAccount,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
@ -482,7 +503,7 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestUnlocked() {
|
|||
// an accept message should be sent to satan's inbox
|
||||
var sent []byte
|
||||
if !testrig.WaitFor(func() bool {
|
||||
delivery, ok := suite.state.Workers.Delivery.Queue.Pop()
|
||||
delivery, ok := testStructs.State.Workers.Delivery.Queue.Pop()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
@ -527,28 +548,34 @@ func (suite *FromFediAPITestSuite) TestProcessFollowRequestUnlocked() {
|
|||
|
||||
// TestCreateStatusFromIRI checks if a forwarded status can be dereferenced by the processor.
|
||||
func (suite *FromFediAPITestSuite) TestCreateStatusFromIRI() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
receivingAccount := suite.testAccounts["local_account_1"]
|
||||
statusCreator := suite.testAccounts["remote_account_2"]
|
||||
|
||||
err := suite.processor.Workers().ProcessFromFediAPI(ctx, messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: nil, // gtsmodel is nil because this is a forwarded status -- we want to dereference it using the iri
|
||||
ReceivingAccount: receivingAccount,
|
||||
RequestingAccount: statusCreator,
|
||||
APIri: testrig.URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
|
||||
err := testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: nil, // gtsmodel is nil because this is a forwarded status -- we want to dereference it using the iri
|
||||
Receiving: receivingAccount,
|
||||
Requesting: statusCreator,
|
||||
APIRI: testrig.URLMustParse("http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1"),
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// status should now be in the database, attributed to remote_account_2
|
||||
s, err := suite.db.GetStatusByURI(context.Background(), "http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1")
|
||||
s, err := testStructs.State.DB.GetStatusByURI(context.Background(), "http://example.org/users/Some_User/statuses/afaba698-5740-4e32-a702-af61aa543bc1")
|
||||
suite.NoError(err)
|
||||
suite.Equal(statusCreator.URI, s.AccountURI)
|
||||
}
|
||||
|
||||
func (suite *FromFediAPITestSuite) TestMoveAccount() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
// We're gonna migrate foss_satan to our local admin account.
|
||||
ctx := context.Background()
|
||||
receivingAcct := suite.testAccounts["local_account_1"]
|
||||
|
@ -562,12 +589,12 @@ func (suite *FromFediAPITestSuite) TestMoveAccount() {
|
|||
|
||||
// Set alsoKnownAs on the admin account.
|
||||
targetAcct.AlsoKnownAsURIs = []string{requestingAcct.URI}
|
||||
if err := suite.state.DB.UpdateAccount(ctx, targetAcct, "also_known_as_uris"); err != nil {
|
||||
if err := testStructs.State.DB.UpdateAccount(ctx, targetAcct, "also_known_as_uris"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Remove existing follow from zork to admin account.
|
||||
if err := suite.state.DB.DeleteFollowByID(
|
||||
if err := testStructs.State.DB.DeleteFollowByID(
|
||||
ctx,
|
||||
suite.testFollows["local_account_1_admin_account"].ID,
|
||||
); err != nil {
|
||||
|
@ -575,7 +602,7 @@ func (suite *FromFediAPITestSuite) TestMoveAccount() {
|
|||
}
|
||||
|
||||
// Have Zork follow foss_satan instead.
|
||||
if err := suite.state.DB.PutFollow(ctx, >smodel.Follow{
|
||||
if err := testStructs.State.DB.PutFollow(ctx, >smodel.Follow{
|
||||
ID: "01HRA0XZYFZC5MNWTKEBR58SSE",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork/follows/01HRA0XZYFZC5MNWTKEBR58SSE",
|
||||
AccountID: receivingAcct.ID,
|
||||
|
@ -585,7 +612,7 @@ func (suite *FromFediAPITestSuite) TestMoveAccount() {
|
|||
}
|
||||
|
||||
// Process the Move.
|
||||
err := suite.processor.Workers().ProcessFromFediAPI(ctx, messages.FromFediAPI{
|
||||
err := testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
|
||||
APObjectType: ap.ObjectProfile,
|
||||
APActivityType: ap.ActivityMove,
|
||||
GTSModel: >smodel.Move{
|
||||
|
@ -595,20 +622,20 @@ func (suite *FromFediAPITestSuite) TestMoveAccount() {
|
|||
Target: testrig.URLMustParse(targetAcct.URI),
|
||||
URI: "https://fossbros-anonymous.io/users/foss_satan/moves/01HRA064871MR8HGVSAFJ333GM",
|
||||
},
|
||||
ReceivingAccount: receivingAcct,
|
||||
RequestingAccount: requestingAcct,
|
||||
Receiving: receivingAcct,
|
||||
Requesting: requestingAcct,
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// Zork should now be following admin account.
|
||||
follows, err := suite.state.DB.IsFollowing(ctx, receivingAcct.ID, targetAcct.ID)
|
||||
follows, err := testStructs.State.DB.IsFollowing(ctx, receivingAcct.ID, targetAcct.ID)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.True(follows)
|
||||
|
||||
// Move should be in the DB.
|
||||
move, err := suite.state.DB.GetMoveByURI(ctx, "https://fossbros-anonymous.io/users/foss_satan/moves/01HRA064871MR8HGVSAFJ333GM")
|
||||
move, err := testStructs.State.DB.GetMoveByURI(ctx, "https://fossbros-anonymous.io/users/foss_satan/moves/01HRA064871MR8HGVSAFJ333GM")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -25,16 +25,16 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
)
|
||||
|
||||
// surface wraps functions for 'surfacing' the result
|
||||
// Surface wraps functions for 'surfacing' the result
|
||||
// of processing a message, eg:
|
||||
// - timelining a status
|
||||
// - removing a status from timelines
|
||||
// - sending a notification to a user
|
||||
// - sending an email
|
||||
type surface struct {
|
||||
state *state.State
|
||||
converter *typeutils.Converter
|
||||
stream *stream.Processor
|
||||
filter *visibility.Filter
|
||||
emailSender email.Sender
|
||||
type Surface struct {
|
||||
State *state.State
|
||||
Converter *typeutils.Converter
|
||||
Stream *stream.Processor
|
||||
Filter *visibility.Filter
|
||||
EmailSender email.Sender
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ import (
|
|||
|
||||
// emailUserReportClosed emails the user who created the
|
||||
// given report, to inform them the report has been closed.
|
||||
func (s *surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Report) error {
|
||||
user, err := s.state.DB.GetUserByAccountID(ctx, report.Account.ID)
|
||||
func (s *Surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Report) error {
|
||||
user, err := s.State.DB.GetUserByAccountID(ctx, report.Account.ID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting user: %w", err)
|
||||
}
|
||||
|
@ -51,12 +51,12 @@ func (s *surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Re
|
|||
return nil
|
||||
}
|
||||
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting instance: %w", err)
|
||||
}
|
||||
|
||||
if err := s.state.DB.PopulateReport(ctx, report); err != nil {
|
||||
if err := s.State.DB.PopulateReport(ctx, report); err != nil {
|
||||
return gtserror.Newf("error populating report: %w", err)
|
||||
}
|
||||
|
||||
|
@ -69,12 +69,12 @@ func (s *surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Re
|
|||
ActionTakenComment: report.ActionTaken,
|
||||
}
|
||||
|
||||
return s.emailSender.SendReportClosedEmail(user.Email, reportClosedData)
|
||||
return s.EmailSender.SendReportClosedEmail(user.Email, reportClosedData)
|
||||
}
|
||||
|
||||
// emailUserPleaseConfirm emails the given user
|
||||
// to ask them to confirm their email address.
|
||||
func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.User) error {
|
||||
func (s *Surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.User) error {
|
||||
if user.UnconfirmedEmail == "" ||
|
||||
user.UnconfirmedEmail == user.Email {
|
||||
// User has already confirmed this
|
||||
|
@ -82,7 +82,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
|||
return nil
|
||||
}
|
||||
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting instance: %w", err)
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
|||
)
|
||||
|
||||
// Assemble email contents and send the email.
|
||||
if err := s.emailSender.SendConfirmEmail(
|
||||
if err := s.EmailSender.SendConfirmEmail(
|
||||
user.UnconfirmedEmail,
|
||||
email.ConfirmData{
|
||||
Username: user.Account.Username,
|
||||
|
@ -116,7 +116,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
|||
user.ConfirmationSentAt = now
|
||||
user.LastEmailedAt = now
|
||||
|
||||
if err := s.state.DB.UpdateUser(
|
||||
if err := s.State.DB.UpdateUser(
|
||||
ctx,
|
||||
user,
|
||||
"confirmation_token",
|
||||
|
@ -131,7 +131,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
|||
|
||||
// emailUserSignupApproved emails the given user
|
||||
// to inform them their sign-up has been approved.
|
||||
func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.User) error {
|
||||
func (s *Surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.User) error {
|
||||
// User may have been approved without
|
||||
// their email address being confirmed
|
||||
// yet. Just send to whatever we have.
|
||||
|
@ -140,13 +140,13 @@ func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.Us
|
|||
emailAddr = user.UnconfirmedEmail
|
||||
}
|
||||
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting instance: %w", err)
|
||||
}
|
||||
|
||||
// Assemble email contents and send the email.
|
||||
if err := s.emailSender.SendSignupApprovedEmail(
|
||||
if err := s.EmailSender.SendSignupApprovedEmail(
|
||||
emailAddr,
|
||||
email.SignupApprovedData{
|
||||
Username: user.Account.Username,
|
||||
|
@ -162,7 +162,7 @@ func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.Us
|
|||
now := time.Now()
|
||||
user.LastEmailedAt = now
|
||||
|
||||
if err := s.state.DB.UpdateUser(
|
||||
if err := s.State.DB.UpdateUser(
|
||||
ctx,
|
||||
user,
|
||||
"last_emailed_at",
|
||||
|
@ -175,14 +175,14 @@ func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.Us
|
|||
|
||||
// emailUserSignupApproved emails the given user
|
||||
// to inform them their sign-up has been approved.
|
||||
func (s *surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmodel.DeniedUser) error {
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
func (s *Surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmodel.DeniedUser) error {
|
||||
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("db error getting instance: %w", err)
|
||||
}
|
||||
|
||||
// Assemble email contents and send the email.
|
||||
return s.emailSender.SendSignupRejectedEmail(
|
||||
return s.EmailSender.SendSignupRejectedEmail(
|
||||
deniedUser.Email,
|
||||
email.SignupRejectedData{
|
||||
Message: deniedUser.Message,
|
||||
|
@ -194,13 +194,13 @@ func (s *surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmo
|
|||
|
||||
// emailAdminReportOpened emails all active moderators/admins
|
||||
// of this instance that a new report has been created.
|
||||
func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.Report) error {
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
func (s *Surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.Report) error {
|
||||
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting instance: %w", err)
|
||||
}
|
||||
|
||||
toAddresses, err := s.state.DB.GetInstanceModeratorAddresses(ctx)
|
||||
toAddresses, err := s.State.DB.GetInstanceModeratorAddresses(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNoEntries) {
|
||||
// No registered moderator addresses.
|
||||
|
@ -209,7 +209,7 @@ func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.R
|
|||
return gtserror.Newf("error getting instance moderator addresses: %w", err)
|
||||
}
|
||||
|
||||
if err := s.state.DB.PopulateReport(ctx, report); err != nil {
|
||||
if err := s.State.DB.PopulateReport(ctx, report); err != nil {
|
||||
return gtserror.Newf("error populating report: %w", err)
|
||||
}
|
||||
|
||||
|
@ -221,7 +221,7 @@ func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.R
|
|||
ReportTargetDomain: report.TargetAccount.Domain,
|
||||
}
|
||||
|
||||
if err := s.emailSender.SendNewReportEmail(toAddresses, reportData); err != nil {
|
||||
if err := s.EmailSender.SendNewReportEmail(toAddresses, reportData); err != nil {
|
||||
return gtserror.Newf("error emailing instance moderators: %w", err)
|
||||
}
|
||||
|
||||
|
@ -230,13 +230,13 @@ func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.R
|
|||
|
||||
// emailAdminNewSignup emails all active moderators/admins of this
|
||||
// instance that a new account sign-up has been submitted to the instance.
|
||||
func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.User) error {
|
||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
||||
func (s *Surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.User) error {
|
||||
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting instance: %w", err)
|
||||
}
|
||||
|
||||
toAddresses, err := s.state.DB.GetInstanceModeratorAddresses(ctx)
|
||||
toAddresses, err := s.State.DB.GetInstanceModeratorAddresses(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNoEntries) {
|
||||
// No registered moderator addresses.
|
||||
|
@ -246,7 +246,7 @@ func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.Use
|
|||
}
|
||||
|
||||
// Ensure user populated.
|
||||
if err := s.state.DB.PopulateUser(ctx, newUser); err != nil {
|
||||
if err := s.State.DB.PopulateUser(ctx, newUser); err != nil {
|
||||
return gtserror.Newf("error populating user: %w", err)
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.Use
|
|||
SignupURL: instance.URI + "/settings/admin/accounts/" + newUser.AccountID,
|
||||
}
|
||||
|
||||
if err := s.emailSender.SendNewSignupEmail(toAddresses, newSignupData); err != nil {
|
||||
if err := s.EmailSender.SendNewSignupEmail(toAddresses, newSignupData); err != nil {
|
||||
return gtserror.Newf("error emailing instance moderators: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,18 +20,20 @@ package workers
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// notifyMentions iterates through mentions on the
|
||||
// given status, and notifies each mentioned account
|
||||
// that they have a new mention.
|
||||
func (s *surface) notifyMentions(
|
||||
func (s *Surface) notifyMentions(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
) error {
|
||||
|
@ -43,7 +45,7 @@ func (s *surface) notifyMentions(
|
|||
mention.Status = status
|
||||
|
||||
// Beforehand, ensure the passed mention is fully populated.
|
||||
if err := s.state.DB.PopulateMention(ctx, mention); err != nil {
|
||||
if err := s.State.DB.PopulateMention(ctx, mention); err != nil {
|
||||
errs.Appendf("error populating mention %s: %w", mention.ID, err)
|
||||
continue
|
||||
}
|
||||
|
@ -56,7 +58,7 @@ func (s *surface) notifyMentions(
|
|||
|
||||
// Ensure thread not muted
|
||||
// by mentioned account.
|
||||
muted, err := s.state.DB.IsThreadMutedByAccount(
|
||||
muted, err := s.State.DB.IsThreadMutedByAccount(
|
||||
ctx,
|
||||
status.ThreadID,
|
||||
mention.TargetAccountID,
|
||||
|
@ -75,7 +77,7 @@ func (s *surface) notifyMentions(
|
|||
|
||||
// notify mentioned
|
||||
// by status author.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationMention,
|
||||
mention.TargetAccount,
|
||||
mention.OriginAccount,
|
||||
|
@ -91,12 +93,12 @@ func (s *surface) notifyMentions(
|
|||
|
||||
// notifyFollowRequest notifies the target of the given
|
||||
// follow request that they have a new follow request.
|
||||
func (s *surface) notifyFollowRequest(
|
||||
func (s *Surface) notifyFollowRequest(
|
||||
ctx context.Context,
|
||||
followReq *gtsmodel.FollowRequest,
|
||||
) error {
|
||||
// Beforehand, ensure the passed follow request is fully populated.
|
||||
if err := s.state.DB.PopulateFollowRequest(ctx, followReq); err != nil {
|
||||
if err := s.State.DB.PopulateFollowRequest(ctx, followReq); err != nil {
|
||||
return gtserror.Newf("error populating follow request %s: %w", followReq.ID, err)
|
||||
}
|
||||
|
||||
|
@ -107,7 +109,7 @@ func (s *surface) notifyFollowRequest(
|
|||
}
|
||||
|
||||
// Now notify the follow request itself.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationFollowRequest,
|
||||
followReq.TargetAccount,
|
||||
followReq.Account,
|
||||
|
@ -123,12 +125,12 @@ func (s *surface) notifyFollowRequest(
|
|||
// they have a new follow. It will also remove any previous
|
||||
// notification of a follow request, essentially replacing
|
||||
// that notification.
|
||||
func (s *surface) notifyFollow(
|
||||
func (s *Surface) notifyFollow(
|
||||
ctx context.Context,
|
||||
follow *gtsmodel.Follow,
|
||||
) error {
|
||||
// Beforehand, ensure the passed follow is fully populated.
|
||||
if err := s.state.DB.PopulateFollow(ctx, follow); err != nil {
|
||||
if err := s.State.DB.PopulateFollow(ctx, follow); err != nil {
|
||||
return gtserror.Newf("error populating follow %s: %w", follow.ID, err)
|
||||
}
|
||||
|
||||
|
@ -139,7 +141,7 @@ func (s *surface) notifyFollow(
|
|||
}
|
||||
|
||||
// Check if previous follow req notif exists.
|
||||
prevNotif, err := s.state.DB.GetNotification(
|
||||
prevNotif, err := s.State.DB.GetNotification(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
gtsmodel.NotificationFollowRequest,
|
||||
follow.TargetAccountID,
|
||||
|
@ -152,14 +154,14 @@ func (s *surface) notifyFollow(
|
|||
|
||||
if prevNotif != nil {
|
||||
// Previous follow request notif existed, delete it before creating new.
|
||||
if err := s.state.DB.DeleteNotificationByID(ctx, prevNotif.ID); // nocollapse
|
||||
if err := s.State.DB.DeleteNotificationByID(ctx, prevNotif.ID); // nocollapse
|
||||
err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("error deleting notification %s: %w", prevNotif.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Now notify the follow itself.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationFollow,
|
||||
follow.TargetAccount,
|
||||
follow.Account,
|
||||
|
@ -173,7 +175,7 @@ func (s *surface) notifyFollow(
|
|||
|
||||
// notifyFave notifies the target of the given
|
||||
// fave that their status has been liked/faved.
|
||||
func (s *surface) notifyFave(
|
||||
func (s *Surface) notifyFave(
|
||||
ctx context.Context,
|
||||
fave *gtsmodel.StatusFave,
|
||||
) error {
|
||||
|
@ -183,7 +185,7 @@ func (s *surface) notifyFave(
|
|||
}
|
||||
|
||||
// Beforehand, ensure the passed status fave is fully populated.
|
||||
if err := s.state.DB.PopulateStatusFave(ctx, fave); err != nil {
|
||||
if err := s.State.DB.PopulateStatusFave(ctx, fave); err != nil {
|
||||
return gtserror.Newf("error populating fave %s: %w", fave.ID, err)
|
||||
}
|
||||
|
||||
|
@ -195,7 +197,7 @@ func (s *surface) notifyFave(
|
|||
|
||||
// Ensure favee hasn't
|
||||
// muted the thread.
|
||||
muted, err := s.state.DB.IsThreadMutedByAccount(
|
||||
muted, err := s.State.DB.IsThreadMutedByAccount(
|
||||
ctx,
|
||||
fave.Status.ThreadID,
|
||||
fave.TargetAccountID,
|
||||
|
@ -212,7 +214,7 @@ func (s *surface) notifyFave(
|
|||
|
||||
// notify status author
|
||||
// of fave by account.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationFave,
|
||||
fave.TargetAccount,
|
||||
fave.Account,
|
||||
|
@ -226,7 +228,7 @@ func (s *surface) notifyFave(
|
|||
|
||||
// notifyAnnounce notifies the status boost target
|
||||
// account that their status has been boosted.
|
||||
func (s *surface) notifyAnnounce(
|
||||
func (s *Surface) notifyAnnounce(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
) error {
|
||||
|
@ -241,7 +243,7 @@ func (s *surface) notifyAnnounce(
|
|||
}
|
||||
|
||||
// Beforehand, ensure the passed status is fully populated.
|
||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
||||
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||
return gtserror.Newf("error populating status %s: %w", status.ID, err)
|
||||
}
|
||||
|
||||
|
@ -253,7 +255,7 @@ func (s *surface) notifyAnnounce(
|
|||
|
||||
// Ensure boostee hasn't
|
||||
// muted the thread.
|
||||
muted, err := s.state.DB.IsThreadMutedByAccount(
|
||||
muted, err := s.State.DB.IsThreadMutedByAccount(
|
||||
ctx,
|
||||
status.BoostOf.ThreadID,
|
||||
status.BoostOfAccountID,
|
||||
|
@ -271,7 +273,7 @@ func (s *surface) notifyAnnounce(
|
|||
|
||||
// notify status author
|
||||
// of boost by account.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationReblog,
|
||||
status.BoostOfAccount,
|
||||
status.Account,
|
||||
|
@ -283,14 +285,14 @@ func (s *surface) notifyAnnounce(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status) error {
|
||||
func (s *Surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status) error {
|
||||
// Beforehand, ensure the passed status is fully populated.
|
||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
||||
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||
return gtserror.Newf("error populating status %s: %w", status.ID, err)
|
||||
}
|
||||
|
||||
// Fetch all votes in the attached status poll.
|
||||
votes, err := s.state.DB.GetPollVotes(ctx, status.PollID)
|
||||
votes, err := s.State.DB.GetPollVotes(ctx, status.PollID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting poll %s votes: %w", status.PollID, err)
|
||||
}
|
||||
|
@ -300,7 +302,7 @@ func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status)
|
|||
if status.Account.IsLocal() {
|
||||
// Send a notification to the status
|
||||
// author that their poll has closed!
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationPoll,
|
||||
status.Account,
|
||||
status.Account,
|
||||
|
@ -319,7 +321,7 @@ func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status)
|
|||
|
||||
// notify voter that
|
||||
// poll has been closed.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationPoll,
|
||||
vote.Account,
|
||||
status.Account,
|
||||
|
@ -333,8 +335,8 @@ func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status)
|
|||
return errs.Combine()
|
||||
}
|
||||
|
||||
func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) error {
|
||||
modAccounts, err := s.state.DB.GetInstanceModerators(ctx)
|
||||
func (s *Surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) error {
|
||||
modAccounts, err := s.State.DB.GetInstanceModerators(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNoEntries) {
|
||||
// No registered
|
||||
|
@ -347,18 +349,18 @@ func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
|
|||
}
|
||||
|
||||
// Ensure user + account populated.
|
||||
if err := s.state.DB.PopulateUser(ctx, newUser); err != nil {
|
||||
if err := s.State.DB.PopulateUser(ctx, newUser); err != nil {
|
||||
return gtserror.Newf("db error populating new user: %w", err)
|
||||
}
|
||||
|
||||
if err := s.state.DB.PopulateAccount(ctx, newUser.Account); err != nil {
|
||||
if err := s.State.DB.PopulateAccount(ctx, newUser.Account); err != nil {
|
||||
return gtserror.Newf("db error populating new user's account: %w", err)
|
||||
}
|
||||
|
||||
// Notify each moderator.
|
||||
var errs gtserror.MultiError
|
||||
for _, mod := range modAccounts {
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationSignup,
|
||||
mod,
|
||||
newUser.Account,
|
||||
|
@ -372,7 +374,24 @@ func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
|
|||
return errs.Combine()
|
||||
}
|
||||
|
||||
// notify creates, inserts, and streams a new
|
||||
func getNotifyLockURI(
|
||||
notificationType gtsmodel.NotificationType,
|
||||
targetAccount *gtsmodel.Account,
|
||||
originAccount *gtsmodel.Account,
|
||||
statusID string,
|
||||
) string {
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString("notification:?")
|
||||
builder.WriteString("type=" + string(notificationType))
|
||||
builder.WriteString("&target=" + targetAccount.URI)
|
||||
builder.WriteString("&origin=" + originAccount.URI)
|
||||
if statusID != "" {
|
||||
builder.WriteString("&statusID=" + statusID)
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// Notify creates, inserts, and streams a new
|
||||
// notification to the target account if it
|
||||
// doesn't yet exist with the given parameters.
|
||||
//
|
||||
|
@ -383,7 +402,7 @@ func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
|
|||
//
|
||||
// targetAccount and originAccount must be
|
||||
// set, but statusID can be an empty string.
|
||||
func (s *surface) notify(
|
||||
func (s *Surface) Notify(
|
||||
ctx context.Context,
|
||||
notificationType gtsmodel.NotificationType,
|
||||
targetAccount *gtsmodel.Account,
|
||||
|
@ -395,9 +414,24 @@ func (s *surface) notify(
|
|||
return nil
|
||||
}
|
||||
|
||||
// We're doing state-y stuff so get a
|
||||
// lock on this combo of notif params.
|
||||
lockURI := getNotifyLockURI(
|
||||
notificationType,
|
||||
targetAccount,
|
||||
originAccount,
|
||||
statusID,
|
||||
)
|
||||
unlock := s.State.ProcessingLocks.Lock(lockURI)
|
||||
|
||||
// Wrap the unlock so we
|
||||
// can do granular unlocking.
|
||||
unlock = util.DoOnce(unlock)
|
||||
defer unlock()
|
||||
|
||||
// Make sure a notification doesn't
|
||||
// already exist with these params.
|
||||
if _, err := s.state.DB.GetNotification(
|
||||
if _, err := s.State.DB.GetNotification(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
notificationType,
|
||||
targetAccount.ID,
|
||||
|
@ -424,21 +458,25 @@ func (s *surface) notify(
|
|||
StatusID: statusID,
|
||||
}
|
||||
|
||||
if err := s.state.DB.PutNotification(ctx, notif); err != nil {
|
||||
if err := s.State.DB.PutNotification(ctx, notif); err != nil {
|
||||
return gtserror.Newf("error putting notification in database: %w", err)
|
||||
}
|
||||
|
||||
filters, err := s.state.DB.GetFiltersForAccountID(ctx, targetAccount.ID)
|
||||
// Unlock already, we're done
|
||||
// with the state-y stuff.
|
||||
unlock()
|
||||
|
||||
// Stream notification to the user.
|
||||
filters, err := s.State.DB.GetFiltersForAccountID(ctx, targetAccount.ID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("couldn't retrieve filters for account %s: %w", targetAccount.ID, err)
|
||||
}
|
||||
|
||||
// Stream notification to the user.
|
||||
apiNotif, err := s.converter.NotificationToAPINotification(ctx, notif, filters)
|
||||
apiNotif, err := s.Converter.NotificationToAPINotification(ctx, notif, filters)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error converting notification to api representation: %w", err)
|
||||
}
|
||||
s.stream.Notify(ctx, targetAccount, apiNotif)
|
||||
s.Stream.Notify(ctx, targetAccount, apiNotif)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
115
internal/processing/workers/surfacenotify_test.go
Normal file
115
internal/processing/workers/surfacenotify_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
// 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 workers_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/workers"
|
||||
)
|
||||
|
||||
type SurfaceNotifyTestSuite struct {
|
||||
WorkersTestSuite
|
||||
}
|
||||
|
||||
func (suite *SurfaceNotifyTestSuite) TestSpamNotifs() {
|
||||
testStructs := suite.SetupTestStructs()
|
||||
defer suite.TearDownTestStructs(testStructs)
|
||||
|
||||
surface := &workers.Surface{
|
||||
State: testStructs.State,
|
||||
Converter: testStructs.TypeConverter,
|
||||
Stream: testStructs.Processor.Stream(),
|
||||
Filter: visibility.NewFilter(testStructs.State),
|
||||
EmailSender: testStructs.EmailSender,
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
notificationType = gtsmodel.NotificationFollow
|
||||
targetAccount = suite.testAccounts["local_account_1"]
|
||||
originAccount = suite.testAccounts["local_account_2"]
|
||||
)
|
||||
|
||||
// Set up a bunch of goroutines to surface
|
||||
// a notification at exactly the same time.
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(20)
|
||||
startAt := time.Now().Add(2 * time.Second)
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
// Wait for it....
|
||||
untilTick := time.Until(startAt)
|
||||
<-time.Tick(untilTick)
|
||||
|
||||
// ...Go!
|
||||
if err := surface.Notify(ctx,
|
||||
notificationType,
|
||||
targetAccount,
|
||||
originAccount,
|
||||
"",
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all notif creation
|
||||
// attempts to complete.
|
||||
wg.Wait()
|
||||
|
||||
// Get all notifs for this account.
|
||||
notifs, err := testStructs.State.DB.GetAccountNotifications(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
targetAccount.ID,
|
||||
"", "", "", 0, nil,
|
||||
)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
var gotOne bool
|
||||
for _, notif := range notifs {
|
||||
if notif.NotificationType == notificationType &&
|
||||
notif.TargetAccountID == targetAccount.ID &&
|
||||
notif.OriginAccountID == originAccount.ID {
|
||||
// This is the notif...
|
||||
if gotOne {
|
||||
// We already had
|
||||
// the notif, d'oh!
|
||||
suite.FailNow("already had notif")
|
||||
} else {
|
||||
gotOne = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSurfaceNotifyTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(SurfaceNotifyTestSuite))
|
||||
}
|
|
@ -37,14 +37,14 @@ import (
|
|||
// It will also handle notifications for any mentions attached to
|
||||
// the account, and notifications for any local accounts that want
|
||||
// to know when this account posts.
|
||||
func (s *surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error {
|
||||
func (s *Surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error {
|
||||
// Ensure status fully populated; including account, mentions, etc.
|
||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
||||
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||
return gtserror.Newf("error populating status with id %s: %w", status.ID, err)
|
||||
}
|
||||
|
||||
// Get all local followers of the account that posted the status.
|
||||
follows, err := s.state.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
||||
follows, err := s.State.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err)
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ func (s *surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.
|
|||
// adding the status to list timelines + home timelines of each
|
||||
// follower, as appropriate, and notifying each follower of the
|
||||
// new status, if the status is eligible for notification.
|
||||
func (s *surface) timelineAndNotifyStatusForFollowers(
|
||||
func (s *Surface) timelineAndNotifyStatusForFollowers(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
follows []*gtsmodel.Follow,
|
||||
|
@ -99,7 +99,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
|||
// If it's not timelineable, we can just stop early, since lists
|
||||
// are prettymuch subsets of the home timeline, so if it shouldn't
|
||||
// appear there, it shouldn't appear in lists either.
|
||||
timelineable, err := s.filter.StatusHomeTimelineable(
|
||||
timelineable, err := s.Filter.StatusHomeTimelineable(
|
||||
ctx, follow.Account, status,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -112,7 +112,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
|||
continue
|
||||
}
|
||||
|
||||
filters, err := s.state.DB.GetFiltersForAccountID(ctx, follow.AccountID)
|
||||
filters, err := s.State.DB.GetFiltersForAccountID(ctx, follow.AccountID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("couldn't retrieve filters for account %s: %w", follow.AccountID, err)
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
|||
// of this follow, if applicable.
|
||||
homeTimelined, err := s.timelineStatus(
|
||||
ctx,
|
||||
s.state.Timelines.Home.IngestOne,
|
||||
s.State.Timelines.Home.IngestOne,
|
||||
follow.AccountID, // home timelines are keyed by account ID
|
||||
follow.Account,
|
||||
status,
|
||||
|
@ -168,7 +168,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
|||
// - This is a top-level post (not a reply or boost).
|
||||
//
|
||||
// That means we can officially notify this one.
|
||||
if err := s.notify(ctx,
|
||||
if err := s.Notify(ctx,
|
||||
gtsmodel.NotificationStatus,
|
||||
follow.Account,
|
||||
status.Account,
|
||||
|
@ -183,7 +183,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
|||
|
||||
// listTimelineStatusForFollow puts the given status
|
||||
// in any eligible lists owned by the given follower.
|
||||
func (s *surface) listTimelineStatusForFollow(
|
||||
func (s *Surface) listTimelineStatusForFollow(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
follow *gtsmodel.Follow,
|
||||
|
@ -198,7 +198,7 @@ func (s *surface) listTimelineStatusForFollow(
|
|||
// inclusion in the list.
|
||||
|
||||
// Get every list entry that targets this follow's ID.
|
||||
listEntries, err := s.state.DB.GetListEntriesForFollowID(
|
||||
listEntries, err := s.State.DB.GetListEntriesForFollowID(
|
||||
// We only need the list IDs.
|
||||
gtscontext.SetBarebones(ctx),
|
||||
follow.ID,
|
||||
|
@ -226,7 +226,7 @@ func (s *surface) listTimelineStatusForFollow(
|
|||
// list that this list entry belongs to.
|
||||
if _, err := s.timelineStatus(
|
||||
ctx,
|
||||
s.state.Timelines.List.IngestOne,
|
||||
s.State.Timelines.List.IngestOne,
|
||||
listEntry.ListID, // list timelines are keyed by list ID
|
||||
follow.Account,
|
||||
status,
|
||||
|
@ -242,7 +242,7 @@ func (s *surface) listTimelineStatusForFollow(
|
|||
// listEligible checks if the given status is eligible
|
||||
// for inclusion in the list that that the given listEntry
|
||||
// belongs to, based on the replies policy of the list.
|
||||
func (s *surface) listEligible(
|
||||
func (s *Surface) listEligible(
|
||||
ctx context.Context,
|
||||
listEntry *gtsmodel.ListEntry,
|
||||
status *gtsmodel.Status,
|
||||
|
@ -263,7 +263,7 @@ func (s *surface) listEligible(
|
|||
// We need to fetch the list that this
|
||||
// entry belongs to, in order to check
|
||||
// the list's replies policy.
|
||||
list, err := s.state.DB.GetListByID(
|
||||
list, err := s.State.DB.GetListByID(
|
||||
ctx, listEntry.ListID,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -283,7 +283,7 @@ func (s *surface) listEligible(
|
|||
//
|
||||
// Check if replied-to account is
|
||||
// also included in this list.
|
||||
includes, err := s.state.DB.ListIncludesAccount(
|
||||
includes, err := s.State.DB.ListIncludesAccount(
|
||||
ctx,
|
||||
list.ID,
|
||||
status.InReplyToAccountID,
|
||||
|
@ -305,7 +305,7 @@ func (s *surface) listEligible(
|
|||
//
|
||||
// Check if replied-to account is
|
||||
// followed by list owner account.
|
||||
follows, err := s.state.DB.IsFollowing(
|
||||
follows, err := s.State.DB.IsFollowing(
|
||||
ctx,
|
||||
list.AccountID,
|
||||
status.InReplyToAccountID,
|
||||
|
@ -335,7 +335,7 @@ func (s *surface) listEligible(
|
|||
//
|
||||
// If the status was inserted into the timeline, true will be returned
|
||||
// + it will also be streamed to the user using the given streamType.
|
||||
func (s *surface) timelineStatus(
|
||||
func (s *Surface) timelineStatus(
|
||||
ctx context.Context,
|
||||
ingest func(context.Context, string, timeline.Timelineable) (bool, error),
|
||||
timelineID string,
|
||||
|
@ -354,26 +354,31 @@ func (s *surface) timelineStatus(
|
|||
}
|
||||
|
||||
// The status was inserted so stream it to the user.
|
||||
apiStatus, err := s.converter.StatusToAPIStatus(ctx, status, account, custom.FilterContextHome, filters)
|
||||
apiStatus, err := s.Converter.StatusToAPIStatus(ctx,
|
||||
status,
|
||||
account,
|
||||
custom.FilterContextHome,
|
||||
filters,
|
||||
)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err)
|
||||
return true, err
|
||||
}
|
||||
s.stream.Update(ctx, account, apiStatus, streamType)
|
||||
s.Stream.Update(ctx, account, apiStatus, streamType)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// deleteStatusFromTimelines completely removes the given status from all timelines.
|
||||
// It will also stream deletion of the status to all open streams.
|
||||
func (s *surface) deleteStatusFromTimelines(ctx context.Context, statusID string) error {
|
||||
if err := s.state.Timelines.Home.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
func (s *Surface) deleteStatusFromTimelines(ctx context.Context, statusID string) error {
|
||||
if err := s.State.Timelines.Home.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.state.Timelines.List.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
if err := s.State.Timelines.List.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
return err
|
||||
}
|
||||
s.stream.Delete(ctx, statusID)
|
||||
s.Stream.Delete(ctx, statusID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -381,15 +386,15 @@ func (s *surface) deleteStatusFromTimelines(ctx context.Context, statusID string
|
|||
// unpreparing it from all timelines, forcing it to be prepared again (with updated
|
||||
// stats, boost counts, etc) next time it's fetched by the timeline owner. This goes
|
||||
// both for the status itself, and for any boosts of the status.
|
||||
func (s *surface) invalidateStatusFromTimelines(ctx context.Context, statusID string) {
|
||||
if err := s.state.Timelines.Home.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
func (s *Surface) invalidateStatusFromTimelines(ctx context.Context, statusID string) {
|
||||
if err := s.State.Timelines.Home.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
log.
|
||||
WithContext(ctx).
|
||||
WithField("statusID", statusID).
|
||||
Errorf("error unpreparing status from home timelines: %v", err)
|
||||
}
|
||||
|
||||
if err := s.state.Timelines.List.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
if err := s.State.Timelines.List.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||
log.
|
||||
WithContext(ctx).
|
||||
WithField("statusID", statusID).
|
||||
|
@ -403,14 +408,14 @@ func (s *surface) invalidateStatusFromTimelines(ctx context.Context, statusID st
|
|||
// Note that calling invalidateStatusFromTimelines takes care of the
|
||||
// state in general, we just need to do this for any streams that are
|
||||
// open right now.
|
||||
func (s *surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Status) error {
|
||||
func (s *Surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Status) error {
|
||||
// Ensure status fully populated; including account, mentions, etc.
|
||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
||||
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||
return gtserror.Newf("error populating status with id %s: %w", status.ID, err)
|
||||
}
|
||||
|
||||
// Get all local followers of the account that posted the status.
|
||||
follows, err := s.state.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
||||
follows, err := s.State.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err)
|
||||
}
|
||||
|
@ -438,7 +443,7 @@ func (s *surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Sta
|
|||
// slice of followers of the account that posted the given status,
|
||||
// pushing update messages into open list/home streams of each
|
||||
// follower.
|
||||
func (s *surface) timelineStatusUpdateForFollowers(
|
||||
func (s *Surface) timelineStatusUpdateForFollowers(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
follows []*gtsmodel.Follow,
|
||||
|
@ -455,7 +460,7 @@ func (s *surface) timelineStatusUpdateForFollowers(
|
|||
// If it's not timelineable, we can just stop early, since lists
|
||||
// are prettymuch subsets of the home timeline, so if it shouldn't
|
||||
// appear there, it shouldn't appear in lists either.
|
||||
timelineable, err := s.filter.StatusHomeTimelineable(
|
||||
timelineable, err := s.Filter.StatusHomeTimelineable(
|
||||
ctx, follow.Account, status,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -468,7 +473,7 @@ func (s *surface) timelineStatusUpdateForFollowers(
|
|||
continue
|
||||
}
|
||||
|
||||
filters, err := s.state.DB.GetFiltersForAccountID(ctx, follow.AccountID)
|
||||
filters, err := s.State.DB.GetFiltersForAccountID(ctx, follow.AccountID)
|
||||
if err != nil {
|
||||
return gtserror.Newf("couldn't retrieve filters for account %s: %w", follow.AccountID, err)
|
||||
}
|
||||
|
@ -503,7 +508,7 @@ func (s *surface) timelineStatusUpdateForFollowers(
|
|||
|
||||
// listTimelineStatusUpdateForFollow pushes edits of the given status
|
||||
// into any eligible lists streams opened by the given follower.
|
||||
func (s *surface) listTimelineStatusUpdateForFollow(
|
||||
func (s *Surface) listTimelineStatusUpdateForFollow(
|
||||
ctx context.Context,
|
||||
status *gtsmodel.Status,
|
||||
follow *gtsmodel.Follow,
|
||||
|
@ -518,7 +523,7 @@ func (s *surface) listTimelineStatusUpdateForFollow(
|
|||
// inclusion in the list.
|
||||
|
||||
// Get every list entry that targets this follow's ID.
|
||||
listEntries, err := s.state.DB.GetListEntriesForFollowID(
|
||||
listEntries, err := s.State.DB.GetListEntriesForFollowID(
|
||||
// We only need the list IDs.
|
||||
gtscontext.SetBarebones(ctx),
|
||||
follow.ID,
|
||||
|
@ -559,14 +564,14 @@ func (s *surface) listTimelineStatusUpdateForFollow(
|
|||
|
||||
// timelineStatusUpdate streams the edited status to the user using the
|
||||
// given streamType.
|
||||
func (s *surface) timelineStreamStatusUpdate(
|
||||
func (s *Surface) timelineStreamStatusUpdate(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
status *gtsmodel.Status,
|
||||
streamType string,
|
||||
filters []*gtsmodel.Filter,
|
||||
) error {
|
||||
apiStatus, err := s.converter.StatusToAPIStatus(ctx, status, account, custom.FilterContextHome, filters)
|
||||
apiStatus, err := s.Converter.StatusToAPIStatus(ctx, status, account, custom.FilterContextHome, filters)
|
||||
if errors.Is(err, custom.ErrHideStatus) {
|
||||
// Don't put this status in the stream.
|
||||
return nil
|
||||
|
@ -575,6 +580,6 @@ func (s *surface) timelineStreamStatusUpdate(
|
|||
err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err)
|
||||
return err
|
||||
}
|
||||
s.stream.StatusUpdate(ctx, account, apiStatus, streamType)
|
||||
s.Stream.StatusUpdate(ctx, account, apiStatus, streamType)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -32,20 +32,20 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
)
|
||||
|
||||
// utilF wraps util functions used by both
|
||||
// util provides util functions used by both
|
||||
// the fromClientAPI and fromFediAPI functions.
|
||||
type utilF struct {
|
||||
type utils struct {
|
||||
state *state.State
|
||||
media *media.Processor
|
||||
account *account.Processor
|
||||
surface *surface
|
||||
surface *Surface
|
||||
}
|
||||
|
||||
// wipeStatus encapsulates common logic
|
||||
// used to totally delete a status + all
|
||||
// its attachments, notifications, boosts,
|
||||
// and timeline entries.
|
||||
func (u *utilF) wipeStatus(
|
||||
func (u *utils) wipeStatus(
|
||||
ctx context.Context,
|
||||
statusToDelete *gtsmodel.Status,
|
||||
deleteAttachments bool,
|
||||
|
@ -152,7 +152,7 @@ func (u *utilF) wipeStatus(
|
|||
// already, and the Move must be valid.
|
||||
//
|
||||
// Return bool will be true if all goes OK.
|
||||
func (u *utilF) redirectFollowers(
|
||||
func (u *utils) redirectFollowers(
|
||||
ctx context.Context,
|
||||
originAcct *gtsmodel.Account,
|
||||
targetAcct *gtsmodel.Account,
|
||||
|
@ -239,13 +239,13 @@ func (u *utilF) redirectFollowers(
|
|||
return true
|
||||
}
|
||||
|
||||
func (u *utilF) incrementStatusesCount(
|
||||
func (u *utils) incrementStatusesCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
status *gtsmodel.Status,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -271,12 +271,12 @@ func (u *utilF) incrementStatusesCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) decrementStatusesCount(
|
||||
func (u *utils) decrementStatusesCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -305,12 +305,12 @@ func (u *utilF) decrementStatusesCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) incrementFollowersCount(
|
||||
func (u *utils) incrementFollowersCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -334,12 +334,12 @@ func (u *utilF) incrementFollowersCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) decrementFollowersCount(
|
||||
func (u *utils) decrementFollowersCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -368,12 +368,12 @@ func (u *utilF) decrementFollowersCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) incrementFollowingCount(
|
||||
func (u *utils) incrementFollowingCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -397,12 +397,12 @@ func (u *utilF) incrementFollowingCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) decrementFollowingCount(
|
||||
func (u *utils) decrementFollowingCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -431,12 +431,12 @@ func (u *utilF) decrementFollowingCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) incrementFollowRequestsCount(
|
||||
func (u *utils) incrementFollowRequestsCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
@ -460,12 +460,12 @@ func (u *utilF) incrementFollowRequestsCount(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *utilF) decrementFollowRequestsCount(
|
||||
func (u *utils) decrementFollowRequestsCount(
|
||||
ctx context.Context,
|
||||
account *gtsmodel.Account,
|
||||
) error {
|
||||
// Lock on this account since we're changing stats.
|
||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
||||
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||
defer unlock()
|
||||
|
||||
// Populate stats.
|
||||
|
|
|
@ -30,9 +30,9 @@ import (
|
|||
)
|
||||
|
||||
type Processor struct {
|
||||
clientAPI clientAPI
|
||||
fediAPI fediAPI
|
||||
workers *workers.Workers
|
||||
clientAPI *clientAPI
|
||||
fediAPI *fediAPI
|
||||
}
|
||||
|
||||
func New(
|
||||
|
@ -45,16 +45,6 @@ func New(
|
|||
media *media.Processor,
|
||||
stream *stream.Processor,
|
||||
) Processor {
|
||||
// Init surface logic
|
||||
// wrapper struct.
|
||||
surface := &surface{
|
||||
state: state,
|
||||
converter: converter,
|
||||
stream: stream,
|
||||
filter: filter,
|
||||
emailSender: emailSender,
|
||||
}
|
||||
|
||||
// Init federate logic
|
||||
// wrapper struct.
|
||||
federate := &federate{
|
||||
|
@ -63,8 +53,18 @@ func New(
|
|||
converter: converter,
|
||||
}
|
||||
|
||||
// Init surface logic
|
||||
// wrapper struct.
|
||||
surface := &Surface{
|
||||
State: state,
|
||||
Converter: converter,
|
||||
Stream: stream,
|
||||
Filter: filter,
|
||||
EmailSender: emailSender,
|
||||
}
|
||||
|
||||
// Init shared util funcs.
|
||||
utilF := &utilF{
|
||||
utils := &utils{
|
||||
state: state,
|
||||
media: media,
|
||||
account: account,
|
||||
|
@ -73,20 +73,20 @@ func New(
|
|||
|
||||
return Processor{
|
||||
workers: &state.Workers,
|
||||
clientAPI: &clientAPI{
|
||||
clientAPI: clientAPI{
|
||||
state: state,
|
||||
converter: converter,
|
||||
surface: surface,
|
||||
federate: federate,
|
||||
account: account,
|
||||
utilF: utilF,
|
||||
utils: utils,
|
||||
},
|
||||
fediAPI: &fediAPI{
|
||||
fediAPI: fediAPI{
|
||||
state: state,
|
||||
surface: surface,
|
||||
federate: federate,
|
||||
account: account,
|
||||
utilF: utilF,
|
||||
utils: utils,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,18 +22,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
@ -41,16 +36,6 @@ import (
|
|||
type WorkersTestSuite struct {
|
||||
// standard suite interfaces
|
||||
suite.Suite
|
||||
db db.DB
|
||||
storage *storage.Driver
|
||||
state state.State
|
||||
mediaManager *media.Manager
|
||||
typeconverter *typeutils.Converter
|
||||
httpClient *testrig.MockHTTPClient
|
||||
transportController transport.Controller
|
||||
federator *federation.Federator
|
||||
oauthServer oauth.Server
|
||||
emailSender email.Sender
|
||||
|
||||
// standard suite models
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
|
@ -68,8 +53,23 @@ type WorkersTestSuite struct {
|
|||
testActivities map[string]testrig.ActivityWithSignature
|
||||
testLists map[string]*gtsmodel.List
|
||||
testListEntries map[string]*gtsmodel.ListEntry
|
||||
}
|
||||
|
||||
processor *processing.Processor
|
||||
// TestStructs encapsulates structs needed to
|
||||
// run one test in this package. Each test should
|
||||
// call SetupTestStructs to get a new TestStructs,
|
||||
// and defer TearDownTestStructs to close it when
|
||||
// the test is complete. The reason for doing things
|
||||
// this way here is to prevent the tests in this
|
||||
// package from overwriting one another's processors
|
||||
// and worker queues, which was causing issues
|
||||
// when running all tests at once.
|
||||
type TestStructs struct {
|
||||
State *state.State
|
||||
Processor *processing.Processor
|
||||
HTTPClient *testrig.MockHTTPClient
|
||||
TypeConverter *typeutils.Converter
|
||||
EmailSender email.Sender
|
||||
}
|
||||
|
||||
func (suite *WorkersTestSuite) SetupSuite() {
|
||||
|
@ -96,51 +96,12 @@ func (suite *WorkersTestSuite) SetupSuite() {
|
|||
}
|
||||
|
||||
func (suite *WorkersTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.testActivities = testrig.NewTestActivities(suite.testAccounts)
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
suite.state.Storage = suite.storage
|
||||
suite.typeconverter = typeutils.NewConverter(&suite.state)
|
||||
|
||||
testrig.StartTimelines(
|
||||
&suite.state,
|
||||
visibility.NewFilter(&suite.state),
|
||||
suite.typeconverter,
|
||||
)
|
||||
|
||||
suite.httpClient = testrig.NewMockHTTPClient(nil, "../../../testrig/media")
|
||||
suite.httpClient.TestRemotePeople = testrig.NewTestFediPeople()
|
||||
suite.httpClient.TestRemoteStatuses = testrig.NewTestFediStatuses()
|
||||
|
||||
suite.transportController = testrig.NewTestTransportController(&suite.state, suite.httpClient)
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, suite.transportController, suite.mediaManager)
|
||||
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../web/template/", nil)
|
||||
|
||||
suite.processor = processing.NewProcessor(cleaner.New(&suite.state), suite.typeconverter, suite.federator, suite.oauthServer, suite.mediaManager, &suite.state, suite.emailSender)
|
||||
testrig.StartWorkers(&suite.state, suite.processor.Workers())
|
||||
|
||||
suite.state.Workers.EnqueueClientAPI = suite.processor.Workers().EnqueueClientAPI
|
||||
suite.state.Workers.EnqueueFediAPI = suite.processor.Workers().EnqueueFediAPI
|
||||
|
||||
testrig.StandardDBSetup(suite.db, suite.testAccounts)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
|
||||
}
|
||||
|
||||
func (suite *WorkersTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StandardStorageTeardown(suite.storage)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
func (suite *WorkersTestSuite) openStreams(ctx context.Context, account *gtsmodel.Account, listIDs []string) map[string]*stream.Stream {
|
||||
func (suite *WorkersTestSuite) openStreams(ctx context.Context, processor *processing.Processor, account *gtsmodel.Account, listIDs []string) map[string]*stream.Stream {
|
||||
streams := make(map[string]*stream.Stream)
|
||||
|
||||
for _, streamType := range []string{
|
||||
|
@ -148,7 +109,7 @@ func (suite *WorkersTestSuite) openStreams(ctx context.Context, account *gtsmode
|
|||
stream.TimelinePublic,
|
||||
stream.TimelineNotifications,
|
||||
} {
|
||||
stream, err := suite.processor.Stream().Open(ctx, account, streamType)
|
||||
stream, err := processor.Stream().Open(ctx, account, streamType)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -159,7 +120,7 @@ func (suite *WorkersTestSuite) openStreams(ctx context.Context, account *gtsmode
|
|||
for _, listID := range listIDs {
|
||||
streamType := stream.TimelineList + ":" + listID
|
||||
|
||||
stream, err := suite.processor.Stream().Open(ctx, account, streamType)
|
||||
stream, err := processor.Stream().Open(ctx, account, streamType)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -169,3 +130,52 @@ func (suite *WorkersTestSuite) openStreams(ctx context.Context, account *gtsmode
|
|||
|
||||
return streams
|
||||
}
|
||||
|
||||
func (suite *WorkersTestSuite) SetupTestStructs() *TestStructs {
|
||||
state := state.State{}
|
||||
|
||||
state.Caches.Init()
|
||||
|
||||
db := testrig.NewTestDB(&state)
|
||||
state.DB = db
|
||||
|
||||
storage := testrig.NewInMemoryStorage()
|
||||
state.Storage = storage
|
||||
typeconverter := typeutils.NewConverter(&state)
|
||||
|
||||
testrig.StartTimelines(
|
||||
&state,
|
||||
visibility.NewFilter(&state),
|
||||
typeconverter,
|
||||
)
|
||||
|
||||
httpClient := testrig.NewMockHTTPClient(nil, "../../../testrig/media")
|
||||
httpClient.TestRemotePeople = testrig.NewTestFediPeople()
|
||||
httpClient.TestRemoteStatuses = testrig.NewTestFediStatuses()
|
||||
|
||||
transportController := testrig.NewTestTransportController(&state, httpClient)
|
||||
mediaManager := testrig.NewTestMediaManager(&state)
|
||||
federator := testrig.NewTestFederator(&state, transportController, mediaManager)
|
||||
oauthServer := testrig.NewTestOauthServer(db)
|
||||
emailSender := testrig.NewEmailSender("../../../web/template/", nil)
|
||||
|
||||
processor := processing.NewProcessor(cleaner.New(&state), typeconverter, federator, oauthServer, mediaManager, &state, emailSender)
|
||||
testrig.StartWorkers(&state, processor.Workers())
|
||||
|
||||
testrig.StandardDBSetup(db, suite.testAccounts)
|
||||
testrig.StandardStorageSetup(storage, "../../../testrig/media")
|
||||
|
||||
return &TestStructs{
|
||||
State: &state,
|
||||
Processor: processor,
|
||||
HTTPClient: httpClient,
|
||||
TypeConverter: typeconverter,
|
||||
EmailSender: emailSender,
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *WorkersTestSuite) TearDownTestStructs(testStructs *TestStructs) {
|
||||
testrig.StandardDBTeardown(testStructs.State.DB)
|
||||
testrig.StandardStorageTeardown(testStructs.State.Storage)
|
||||
testrig.StopWorkers(testStructs.State)
|
||||
}
|
||||
|
|
201
internal/queue/simple.go
Normal file
201
internal/queue/simple.go
Normal file
|
@ -0,0 +1,201 @@
|
|||
// 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 queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"codeberg.org/gruf/go-list"
|
||||
)
|
||||
|
||||
// frequency of GC cycles
|
||||
// per no. unlocks. i.e.
|
||||
// every 'gcfreq' unlocks.
|
||||
const gcfreq = 1024
|
||||
|
||||
// SimpleQueue provides a simple concurrency safe
|
||||
// queue using generics and a memory pool of list
|
||||
// elements to reduce overall memory usage.
|
||||
type SimpleQueue[T any] struct {
|
||||
l list.List[T]
|
||||
p elemPool[T]
|
||||
w chan struct{}
|
||||
m sync.Mutex
|
||||
n uint32 // pop counter (safely wraps around)
|
||||
}
|
||||
|
||||
// Push will push given value to the queue.
|
||||
func (q *SimpleQueue[T]) Push(value T) {
|
||||
q.m.Lock()
|
||||
|
||||
// Wrap in element.
|
||||
elem := q.p.alloc()
|
||||
elem.Value = value
|
||||
|
||||
// Push new elem to queue.
|
||||
q.l.PushElemFront(elem)
|
||||
|
||||
if q.w != nil {
|
||||
// Notify any goroutines
|
||||
// blocking on q.Wait(),
|
||||
// or on PopCtx(...).
|
||||
close(q.w)
|
||||
q.w = nil
|
||||
}
|
||||
|
||||
q.m.Unlock()
|
||||
}
|
||||
|
||||
// Pop will attempt to pop value from the queue.
|
||||
func (q *SimpleQueue[T]) Pop() (value T, ok bool) {
|
||||
q.m.Lock()
|
||||
|
||||
// Check for a tail (i.e. not empty).
|
||||
if ok = (q.l.Tail != nil); ok {
|
||||
|
||||
// Extract value.
|
||||
tail := q.l.Tail
|
||||
value = tail.Value
|
||||
|
||||
// Remove tail.
|
||||
q.l.Remove(tail)
|
||||
q.p.free(tail)
|
||||
|
||||
// Every 'gcfreq' pops perform
|
||||
// a garbage collection to keep
|
||||
// us squeaky clean :]
|
||||
if q.n++; q.n%gcfreq == 0 {
|
||||
q.p.GC()
|
||||
}
|
||||
}
|
||||
|
||||
q.m.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// PopCtx will attempt to pop value from queue, else blocking on context.
|
||||
func (q *SimpleQueue[T]) PopCtx(ctx context.Context) (value T, ok bool) {
|
||||
|
||||
// Acquire lock.
|
||||
q.m.Lock()
|
||||
|
||||
var elem *list.Elem[T]
|
||||
|
||||
for {
|
||||
// Get next elem.
|
||||
elem = q.l.Tail
|
||||
if ok = (elem != nil); ok {
|
||||
break
|
||||
}
|
||||
|
||||
if q.w == nil {
|
||||
// Create new wait channel.
|
||||
q.w = make(chan struct{})
|
||||
}
|
||||
|
||||
// Get current
|
||||
// ch pointer.
|
||||
ch := q.w
|
||||
|
||||
// Done with lock.
|
||||
q.m.Unlock()
|
||||
|
||||
select {
|
||||
// Context canceled.
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
// Pushed!
|
||||
case <-ch:
|
||||
}
|
||||
|
||||
// Relock queue.
|
||||
q.m.Lock()
|
||||
}
|
||||
|
||||
// Extract value.
|
||||
value = elem.Value
|
||||
|
||||
// Remove element.
|
||||
q.l.Remove(elem)
|
||||
q.p.free(elem)
|
||||
|
||||
// Every 'gcfreq' pops perform
|
||||
// a garbage collection to keep
|
||||
// us squeaky clean :]
|
||||
if q.n++; q.n%gcfreq == 0 {
|
||||
q.p.GC()
|
||||
}
|
||||
|
||||
// Done with lock.
|
||||
q.m.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Len returns the current length of the queue.
|
||||
func (q *SimpleQueue[T]) Len() int {
|
||||
q.m.Lock()
|
||||
l := q.l.Len()
|
||||
q.m.Unlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// elemPool is a very simple
|
||||
// list.Elem[T] memory pool.
|
||||
type elemPool[T any] struct {
|
||||
current []*list.Elem[T]
|
||||
victim []*list.Elem[T]
|
||||
}
|
||||
|
||||
func (p *elemPool[T]) alloc() *list.Elem[T] {
|
||||
// First try the current queue
|
||||
if l := len(p.current) - 1; l >= 0 {
|
||||
mu := p.current[l]
|
||||
p.current = p.current[:l]
|
||||
return mu
|
||||
}
|
||||
|
||||
// Next try the victim queue.
|
||||
if l := len(p.victim) - 1; l >= 0 {
|
||||
mu := p.victim[l]
|
||||
p.victim = p.victim[:l]
|
||||
return mu
|
||||
}
|
||||
|
||||
// Lastly, alloc new.
|
||||
mu := new(list.Elem[T])
|
||||
return mu
|
||||
}
|
||||
|
||||
// free will release given element to pool.
|
||||
func (p *elemPool[T]) free(elem *list.Elem[T]) {
|
||||
var zero T
|
||||
elem.Next = nil
|
||||
elem.Prev = nil
|
||||
elem.Value = zero
|
||||
p.current = append(p.current, elem)
|
||||
}
|
||||
|
||||
// GC will clear out unused entries from the elemPool.
|
||||
func (p *elemPool[T]) GC() {
|
||||
current := p.current
|
||||
p.current = nil
|
||||
p.victim = current
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"context"
|
||||
|
||||
"codeberg.org/gruf/go-structr"
|
||||
)
|
||||
|
@ -26,15 +26,14 @@ import (
|
|||
// StructQueue wraps a structr.Queue{} to
|
||||
// provide simple index caching by name.
|
||||
type StructQueue[StructType any] struct {
|
||||
queue structr.Queue[StructType]
|
||||
queue structr.QueueCtx[StructType]
|
||||
index map[string]*structr.Index
|
||||
wait atomic.Pointer[chan struct{}]
|
||||
}
|
||||
|
||||
// Init initializes queue with structr.QueueConfig{}.
|
||||
func (q *StructQueue[T]) Init(config structr.QueueConfig[T]) {
|
||||
q.index = make(map[string]*structr.Index, len(config.Indices))
|
||||
q.queue = structr.Queue[T]{}
|
||||
// q.queue = structr.QueueCtx[T]{}
|
||||
q.queue.Init(config)
|
||||
for _, cfg := range config.Indices {
|
||||
q.index[cfg.Fields] = q.queue.Index(cfg.Fields)
|
||||
|
@ -43,13 +42,22 @@ func (q *StructQueue[T]) Init(config structr.QueueConfig[T]) {
|
|||
|
||||
// Pop: see structr.Queue{}.PopFront().
|
||||
func (q *StructQueue[T]) Pop() (value T, ok bool) {
|
||||
return q.queue.PopFront()
|
||||
values := q.queue.PopFrontN(1)
|
||||
if ok = (len(values) > 0); !ok {
|
||||
return
|
||||
}
|
||||
value = values[0]
|
||||
return
|
||||
}
|
||||
|
||||
// Push wraps structr.Queue{}.PushBack() to awaken those blocking on <-.Wait().
|
||||
// PopCtx: see structr.QueueCtx{}.PopFront().
|
||||
func (q *StructQueue[T]) PopCtx(ctx context.Context) (value T, ok bool) {
|
||||
return q.queue.PopFront(ctx)
|
||||
}
|
||||
|
||||
// Push: see structr.Queue.PushBack().
|
||||
func (q *StructQueue[T]) Push(values ...T) {
|
||||
q.queue.PushBack(values...)
|
||||
q.broadcast()
|
||||
}
|
||||
|
||||
// Delete pops (and drops!) all queued entries under index with key.
|
||||
|
@ -66,31 +74,5 @@ func (q *StructQueue[T]) Len() int {
|
|||
// Wait returns current wait channel, which may be
|
||||
// blocked on to awaken when new value pushed to queue.
|
||||
func (q *StructQueue[T]) Wait() <-chan struct{} {
|
||||
var ch chan struct{}
|
||||
|
||||
for {
|
||||
// Get channel ptr.
|
||||
ptr := q.wait.Load()
|
||||
if ptr != nil {
|
||||
return *ptr
|
||||
}
|
||||
|
||||
if ch == nil {
|
||||
// Allocate new channel.
|
||||
ch = make(chan struct{})
|
||||
}
|
||||
|
||||
// Try set the new wait channel ptr.
|
||||
if q.wait.CompareAndSwap(ptr, &ch) {
|
||||
return ch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// broadcast safely closes wait channel if
|
||||
// currently set, releasing waiting goroutines.
|
||||
func (q *StructQueue[T]) broadcast() {
|
||||
if ptr := q.wait.Swap(nil); ptr != nil {
|
||||
close(*ptr)
|
||||
}
|
||||
return q.queue.Wait()
|
||||
}
|
|
@ -42,20 +42,21 @@ type State struct {
|
|||
// DB provides access to the database.
|
||||
DB db.DB
|
||||
|
||||
// FedLocks provides access to this state's
|
||||
// mutex map of per URI federation locks.
|
||||
// FedLocks provides access to this state's mutex
|
||||
// map of per URI federation locks, intended for
|
||||
// use in internal/federation functions.
|
||||
//
|
||||
// Used during account and status dereferencing,
|
||||
// message processing in the FromFediAPI worker
|
||||
// functions, and by the go-fed/activity library.
|
||||
// and by the go-fed/activity library.
|
||||
FedLocks mutexes.MutexMap
|
||||
|
||||
// AccountLocks provides access to this state's
|
||||
// ProcessingLocks provides access to this state's
|
||||
// mutex map of per URI locks, intended for use
|
||||
// in internal/processing functions, for example
|
||||
// when updating accounts, migrating, approving
|
||||
// or rejecting an account, changing stats,
|
||||
// pinned statuses, etc.
|
||||
AccountLocks mutexes.MutexMap
|
||||
// or rejecting an account, changing stats or
|
||||
// pinned statuses, creating notifs, etc.
|
||||
ProcessingLocks mutexes.MutexMap
|
||||
|
||||
// Storage provides access to the storage driver.
|
||||
Storage *storage.Driver
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/httpclient"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/queue"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// Delivery wraps an httpclient.Request{}
|
||||
|
@ -99,29 +100,51 @@ func (p *WorkerPool) Init(client *httpclient.Client) {
|
|||
}
|
||||
|
||||
// Start will attempt to start 'n' Worker{}s.
|
||||
func (p *WorkerPool) Start(n int) (ok bool) {
|
||||
if ok = (len(p.workers) == 0); ok {
|
||||
p.workers = make([]*Worker, n)
|
||||
for i := range p.workers {
|
||||
p.workers[i] = new(Worker)
|
||||
p.workers[i].Client = p.Client
|
||||
p.workers[i].Queue = &p.Queue
|
||||
ok = p.workers[i].Start() && ok
|
||||
}
|
||||
func (p *WorkerPool) Start(n int) {
|
||||
// Check whether workers are
|
||||
// set (is already running).
|
||||
ok := (len(p.workers) > 0)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Allocate new workers slice.
|
||||
p.workers = make([]*Worker, n)
|
||||
for i := range p.workers {
|
||||
|
||||
// Allocate new Worker{}.
|
||||
p.workers[i] = new(Worker)
|
||||
p.workers[i].Client = p.Client
|
||||
p.workers[i].Queue = &p.Queue
|
||||
|
||||
// Attempt to start worker.
|
||||
// Return bool not useful
|
||||
// here, as true = started,
|
||||
// false = already running.
|
||||
_ = p.workers[i].Start()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stop will attempt to stop contained Worker{}s.
|
||||
func (p *WorkerPool) Stop() (ok bool) {
|
||||
if ok = (len(p.workers) > 0); ok {
|
||||
for i := range p.workers {
|
||||
ok = p.workers[i].Stop() && ok
|
||||
p.workers[i] = nil
|
||||
}
|
||||
p.workers = p.workers[:0]
|
||||
func (p *WorkerPool) Stop() {
|
||||
// Check whether workers are
|
||||
// set (is currently running).
|
||||
ok := (len(p.workers) == 0)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
// Stop all running workers.
|
||||
for i := range p.workers {
|
||||
|
||||
// return bool not useful
|
||||
// here, as true = stopped,
|
||||
// false = never running.
|
||||
_ = p.workers[i].Stop()
|
||||
}
|
||||
|
||||
// Unset workers slice.
|
||||
p.workers = p.workers[:0]
|
||||
}
|
||||
|
||||
// Worker wraps an httpclient.Client{} to feed
|
||||
|
@ -158,23 +181,13 @@ func (w *Worker) run(ctx context.Context) {
|
|||
if w.Client == nil || w.Queue == nil {
|
||||
panic("not yet initialized")
|
||||
}
|
||||
log.Infof(ctx, "%p: started delivery worker", w)
|
||||
defer log.Infof(ctx, "%p: stopped delivery worker", w)
|
||||
for returned := false; !returned; {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf(ctx, "recovered panic: %v", r)
|
||||
}
|
||||
}()
|
||||
w.process(ctx)
|
||||
returned = true
|
||||
}()
|
||||
}
|
||||
log.Infof(ctx, "%p: starting worker", w)
|
||||
defer log.Infof(ctx, "%p: stopped worker", w)
|
||||
util.Must(func() { w.process(ctx) })
|
||||
}
|
||||
|
||||
// process is the main delivery worker processing routine.
|
||||
func (w *Worker) process(ctx context.Context) {
|
||||
func (w *Worker) process(ctx context.Context) bool {
|
||||
if w.Client == nil || w.Queue == nil {
|
||||
// we perform this check here just
|
||||
// to ensure the compiler knows these
|
||||
|
@ -188,7 +201,7 @@ loop:
|
|||
// Get next delivery.
|
||||
dlv, ok := w.next(ctx)
|
||||
if !ok {
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether backoff required.
|
||||
|
@ -203,7 +216,7 @@ loop:
|
|||
// Main ctx
|
||||
// cancelled.
|
||||
backoff.Stop()
|
||||
return
|
||||
return true
|
||||
|
||||
case <-w.Queue.Wait():
|
||||
// A new message was
|
||||
|
|
|
@ -32,9 +32,7 @@ func testDeliveryWorkerPool(t *testing.T, sz int, input []*testrequest) {
|
|||
"127.0.0.0/8",
|
||||
}),
|
||||
}))
|
||||
if !wp.Start(sz) {
|
||||
t.Fatal("failed starting pool")
|
||||
}
|
||||
wp.Start(sz)
|
||||
defer wp.Stop()
|
||||
test(t, &wp.Queue, input)
|
||||
}
|
||||
|
|
68
internal/util/fns.go
Normal file
68
internal/util/fns.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"codeberg.org/gruf/go-errors/v2"
|
||||
)
|
||||
|
||||
// Must executes 'fn' repeatedly until
|
||||
// it successfully returns without panic.
|
||||
func Must(fn func()) {
|
||||
if fn == nil {
|
||||
panic("nil func")
|
||||
}
|
||||
for !func() (done bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Gather calling func frames.
|
||||
pcs := make([]uintptr, 10)
|
||||
n := runtime.Callers(3, pcs)
|
||||
i := runtime.CallersFrames(pcs[:n])
|
||||
c := gatherFrames(i, n)
|
||||
|
||||
const msg = "recovered panic: %v\n\n%s\n"
|
||||
fmt.Fprintf(os.Stderr, msg, r, c.String())
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
done = true
|
||||
return
|
||||
}() { //nolint
|
||||
}
|
||||
}
|
||||
|
||||
// gatherFrames collates runtime frames from a frame iterator.
|
||||
func gatherFrames(iter *runtime.Frames, n int) errors.Callers {
|
||||
if iter == nil {
|
||||
return nil
|
||||
}
|
||||
frames := make([]runtime.Frame, 0, n)
|
||||
for {
|
||||
f, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
frames = append(frames, f)
|
||||
}
|
||||
return frames
|
||||
}
|
|
@ -29,45 +29,43 @@ const (
|
|||
robotsTxt = `# GoToSocial robots.txt -- to edit, see internal/web/robots.go
|
||||
# More info @ https://developers.google.com/search/docs/crawling-indexing/robots/intro
|
||||
|
||||
# Before we commence, a giant fuck you to ChatGPT in particular.
|
||||
# https://platform.openai.com/docs/gptbot
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
# As of September 2023, GPTBot and ChatGPT-User are equivalent. But there's no telling
|
||||
# when OpenAI might decide to change that, so block this one too.
|
||||
User-agent: ChatGPT-User
|
||||
Disallow: /
|
||||
|
||||
# And a giant fuck you to Google Bard and their other generative AI ventures too.
|
||||
# https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers
|
||||
User-agent: Google-Extended
|
||||
Disallow: /
|
||||
|
||||
# Block CommonCrawl. Used in training LLMs and specifically GPT-3.
|
||||
# https://commoncrawl.org/faq
|
||||
# AI scrapers and the like.
|
||||
# https://github.com/ai-robots-txt/ai.robots.txt/
|
||||
User-agent: AdsBot-Google
|
||||
User-agent: Amazonbot
|
||||
User-agent: anthropic-ai
|
||||
User-agent: Applebot
|
||||
User-agent: AwarioRssBot
|
||||
User-agent: AwarioSmartBot
|
||||
User-agent: Bytespider
|
||||
User-agent: CCBot
|
||||
Disallow: /
|
||||
|
||||
# Block Omgilike/Webz.io, a "Big Web Data" engine.
|
||||
# https://webz.io/blog/web-data/what-is-the-omgili-bot-and-why-is-it-crawling-your-website/
|
||||
User-agent: Omgilibot
|
||||
Disallow: /
|
||||
|
||||
# Block Faceboobot, because Meta.
|
||||
# https://developers.facebook.com/docs/sharing/bot
|
||||
User-agent: ChatGPT-User
|
||||
User-agent: ClaudeBot
|
||||
User-agent: Claude-Web
|
||||
User-agent: cohere-ai
|
||||
User-agent: DataForSeoBot
|
||||
User-agent: FacebookBot
|
||||
User-agent: FriendlyCrawler
|
||||
User-agent: Google-Extended
|
||||
User-agent: GoogleOther
|
||||
User-agent: GPTBot
|
||||
User-agent: ImagesiftBot
|
||||
User-agent: magpie-crawler
|
||||
User-agent: Meltwater
|
||||
User-agent: omgili
|
||||
User-agent: omgilibot
|
||||
User-agent: peer39_crawler
|
||||
User-agent: peer39_crawler/1.0
|
||||
User-agent: PerplexityBot
|
||||
User-agent: PiplBot
|
||||
User-agent: Seekr
|
||||
User-agent: YouBot
|
||||
Disallow: /
|
||||
|
||||
# Well-known.dev crawler. Indexes stuff under /.well-known.
|
||||
# https://well-known.dev/about/
|
||||
User-agent: WellKnownBot
|
||||
Disallow: /
|
||||
|
||||
# Block Amazonbot, because Amazon.
|
||||
# https://developer.amazon.com/amazonbot
|
||||
User-agent: Amazonbot
|
||||
Disallow: /
|
||||
User-agent: WellKnownBot
|
||||
Disallow: /
|
||||
|
||||
# Rules for everything else.
|
||||
User-agent: *
|
||||
|
|
142
internal/workers/worker_fn.go
Normal file
142
internal/workers/worker_fn.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
// 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 workers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"codeberg.org/gruf/go-runners"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/queue"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// FnWorkerPool wraps multiple FnWorker{}s in
|
||||
// a singular struct for easy multi start / stop.
|
||||
type FnWorkerPool struct {
|
||||
|
||||
// Queue is embedded queue.SimpleQueue{}
|
||||
// passed to each of the pool Worker{}s.
|
||||
Queue queue.SimpleQueue[func(context.Context)]
|
||||
|
||||
// internal fields.
|
||||
workers []*FnWorker
|
||||
}
|
||||
|
||||
// Start will attempt to start 'n' FnWorker{}s.
|
||||
func (p *FnWorkerPool) Start(n int) {
|
||||
// Check whether workers are
|
||||
// set (is already running).
|
||||
ok := (len(p.workers) > 0)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Allocate new workers slice.
|
||||
p.workers = make([]*FnWorker, n)
|
||||
for i := range p.workers {
|
||||
|
||||
// Allocate new FnWorker{}.
|
||||
p.workers[i] = new(FnWorker)
|
||||
p.workers[i].Queue = &p.Queue
|
||||
|
||||
// Attempt to start worker.
|
||||
// Return bool not useful
|
||||
// here, as true = started,
|
||||
// false = already running.
|
||||
_ = p.workers[i].Start()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop will attempt to stop contained FnWorker{}s.
|
||||
func (p *FnWorkerPool) Stop() {
|
||||
// Check whether workers are
|
||||
// set (is currently running).
|
||||
ok := (len(p.workers) == 0)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Stop all running workers.
|
||||
for i := range p.workers {
|
||||
|
||||
// return bool not useful
|
||||
// here, as true = stopped,
|
||||
// false = never running.
|
||||
_ = p.workers[i].Stop()
|
||||
}
|
||||
|
||||
// Unset workers slice.
|
||||
p.workers = p.workers[:0]
|
||||
}
|
||||
|
||||
// FnWorker wraps a queue.SimpleQueue{} which
|
||||
// it feeds from to provide it with function
|
||||
// tasks to execute. It does so in a single
|
||||
// goroutine with state management utilities.
|
||||
type FnWorker struct {
|
||||
|
||||
// Queue is the fn queue that FnWorker
|
||||
// will feed from for upcoming tasks.
|
||||
Queue *queue.SimpleQueue[func(context.Context)]
|
||||
|
||||
// internal fields.
|
||||
service runners.Service
|
||||
}
|
||||
|
||||
// Start will attempt to start the Worker{}.
|
||||
func (w *FnWorker) Start() bool {
|
||||
return w.service.GoRun(w.run)
|
||||
}
|
||||
|
||||
// Stop will attempt to stop the Worker{}.
|
||||
func (w *FnWorker) Stop() bool {
|
||||
return w.service.Stop()
|
||||
}
|
||||
|
||||
// run wraps process to restart on any panic.
|
||||
func (w *FnWorker) run(ctx context.Context) {
|
||||
if w.Queue == nil {
|
||||
panic("not yet initialized")
|
||||
}
|
||||
log.Infof(ctx, "%p: starting worker", w)
|
||||
defer log.Infof(ctx, "%p: stopped worker", w)
|
||||
util.Must(func() { w.process(ctx) })
|
||||
}
|
||||
|
||||
// process is the main delivery worker processing routine.
|
||||
func (w *FnWorker) process(ctx context.Context) {
|
||||
if w.Queue == nil {
|
||||
// we perform this check here just
|
||||
// to ensure the compiler knows these
|
||||
// variables aren't nil in the loop,
|
||||
// even if already checked by caller.
|
||||
panic("not yet initialized")
|
||||
}
|
||||
|
||||
for {
|
||||
// Block until pop next func.
|
||||
fn, ok := w.Queue.PopCtx(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// run!
|
||||
fn(ctx)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue