Compare commits
91 Commits
Author | SHA1 | Date |
---|---|---|
Vyr Cossont | 04bcde08a1 | |
Vyr Cossont | 61a8d36255 | |
tobi | 4db596b8b9 | |
Daenney | 2fd69ec58b | |
Daenney | b67937c213 | |
浮心物语 | 59fedfc4f9 | |
kim | 32e570abfd | |
Vyr Cossont | 975e92b7f1 | |
tobi | f9a4a6120d | |
tobi | fa9a3075a5 | |
tobi | 4dc30f8687 | |
tobi | a276b1ca06 | |
kim | 1e7b32490d | |
tobi | cce21c11cb | |
tobi | 5bee30d60c | |
dependabot[bot] | d96cca60a1 | |
dependabot[bot] | 0a18c0d802 | |
kim | 3d3e99ae52 | |
dependabot[bot] | 06b1e0173b | |
kim | b092da6d28 | |
kim | 6c0d93c6cb | |
dependabot[bot] | 16c1832793 | |
dependabot[bot] | f817f96596 | |
dependabot[bot] | 1ba9601472 | |
dependabot[bot] | 96eea416f3 | |
kim | c06e6fb656 | |
tobi | 578a4e0cf5 | |
kim | f24ce34c3a | |
kim | f456bd3401 | |
kim | 3554991444 | |
Vyr Cossont | 45f4afe60e | |
dependabot[bot] | a0d066844f | |
dependabot[bot] | 8237e8d09e | |
dependabot[bot] | a5f28fe0c9 | |
dependabot[bot] | c98ec6f89d | |
kim | d3f6960ba0 | |
tobi | 6171dcbe51 | |
tobi | 35b1c54bde | |
kim | a840f4d49d | |
tobi | ebec95a522 | |
tobi | 725a21b027 | |
tobi | 1edcb06afe | |
kim | 2300d5e73b | |
kim | eb61c783ed | |
kim | a8254a40e7 | |
kim | ec7c983e46 | |
kim | ec334ece20 | |
Daenney | 39b3a27c82 | |
kim | 4f87ef246c | |
dependabot[bot] | bfc21e4850 | |
dependabot[bot] | d3bac8bbec | |
tobi | 40ece19055 | |
dependabot[bot] | 1375a86919 | |
dependabot[bot] | b2eecd9dc7 | |
kim | 48b91ca239 | |
kim | c9c0773f2c | |
tobi | ba4f51ce2f | |
Daenney | 83d24658a8 | |
Daenney | 6c9bc26a6d | |
tobi | fd8a724e77 | |
Daenney | 3a369d834a | |
tobi | aecf74951c | |
tobi | 7a1e639483 | |
dependabot[bot] | 62788aa116 | |
dependabot[bot] | a57dd15a8e | |
kim | 12a7eba01f | |
Daenney | dcab555a6b | |
dependabot[bot] | 0db9e34b69 | |
tobi | b7c629a18a | |
tobi | 431505b3e4 | |
tobi | 34d0159ad5 | |
kim | b3f2d44143 | |
kim | c67bbe5ba0 | |
tobi | cef9924d9a | |
tobi | ef16919d4a | |
tobi | 8cf685fbe9 | |
tobi | 6de5717d7f | |
tobi | 3cceed11b2 | |
kim | f79d50b9b2 | |
tobi | 8b30709791 | |
kim | 1018cde107 | |
dependabot[bot] | 6bb43f3f9b | |
dependabot[bot] | 66e4510bf1 | |
Bishochiparaa | fdd23cb9ed | |
tobi | 89e0cfd874 | |
kim | 1439042104 | |
tobi | a5c6dc9716 | |
dependabot[bot] | c097745c38 | |
kim | db2dcc3455 | |
tobi | 9fb8a78f91 | |
kim | a483bd9e38 |
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
|
||||
|
||||
...
|
||||
|
|
|
@ -29,6 +29,8 @@ builds:
|
|||
- timetzdata
|
||||
- >-
|
||||
{{ if and (index .Env "DEBUG") (.Env.DEBUG) }}debugenv{{ end }}
|
||||
- >-
|
||||
{{ if and (index .Env "WASMSQLITE3") (.Env.WASMSQLITE3) }}wasmsqlite3{{ end }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
|
|
|
@ -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,12 +277,13 @@ 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-storage](https://codeberg.org/gruf/go-storage); 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).
|
||||
- [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html).
|
||||
- jackc:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -186,9 +184,13 @@ var Confirm action.GTSAction = func(ctx context.Context) error {
|
|||
user.Approved = func() *bool { a := true; return &a }()
|
||||
user.Email = user.UnconfirmedEmail
|
||||
user.ConfirmedAt = time.Now()
|
||||
user.SignUpIP = nil
|
||||
return state.DB.UpdateUser(
|
||||
ctx, user,
|
||||
"approved", "email", "confirmed_at",
|
||||
"approved",
|
||||
"email",
|
||||
"confirmed_at",
|
||||
"sign_up_ip",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -77,15 +80,11 @@ func setupPrune(ctx context.Context) (*prune, error) {
|
|||
func (p *prune) shutdown() error {
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
if err := p.storage.Close(); err != nil {
|
||||
errs.Appendf("error closing storage backend: %w", err)
|
||||
}
|
||||
|
||||
if err := p.dbService.Close(); err != nil {
|
||||
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"
|
||||
|
@ -47,7 +48,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/httpclient"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
|
@ -68,55 +68,107 @@ import (
|
|||
// Start creates and starts a gotosocial server
|
||||
var Start action.GTSAction = func(ctx context.Context) error {
|
||||
if _, err := maxprocs.Set(maxprocs.Logger(nil)); err != nil {
|
||||
log.Infof(ctx, "could not set CPU limits from cgroup: %s", err)
|
||||
log.Warnf(ctx, "could not set CPU limits from cgroup: %s", err)
|
||||
}
|
||||
|
||||
var state state.State
|
||||
var (
|
||||
// Define necessary core variables
|
||||
// before anything so we can prepare
|
||||
// defer function for safe shutdown
|
||||
// depending on what services were
|
||||
// managed to be started.
|
||||
|
||||
// Initialize caches
|
||||
state.Caches.Init()
|
||||
state.Caches.Start()
|
||||
defer state.Caches.Stop()
|
||||
state = new(state.State)
|
||||
route *router.Router
|
||||
)
|
||||
|
||||
// Initialize Tracing
|
||||
defer func() {
|
||||
// Stop caches with
|
||||
// background tasks.
|
||||
state.Caches.Stop()
|
||||
|
||||
if route != nil {
|
||||
// We reached a point where the API router
|
||||
// was created + setup. Ensure it gets stopped
|
||||
// first to stop processing new information.
|
||||
if err := route.Stop(); err != nil {
|
||||
log.Errorf(ctx, "error stopping router: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop any currently running
|
||||
// worker processes / scheduled
|
||||
// tasks from being executed.
|
||||
state.Workers.Stop()
|
||||
|
||||
if state.Timelines.Home != nil {
|
||||
// Home timeline mgr was setup, ensure it gets stopped.
|
||||
if err := state.Timelines.Home.Stop(); err != nil {
|
||||
log.Errorf(ctx, "error stopping home timeline: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if state.Timelines.List != nil {
|
||||
// List timeline mgr was setup, ensure it gets stopped.
|
||||
if err := state.Timelines.List.Stop(); err != nil {
|
||||
log.Errorf(ctx, "error stopping list timeline: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if state.DB != nil {
|
||||
// Lastly, if database service was started,
|
||||
// ensure it gets closed now all else stopped.
|
||||
if err := state.DB.Close(); err != nil {
|
||||
log.Errorf(ctx, "error stopping database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally reached end of shutdown.
|
||||
log.Info(ctx, "done! exiting...")
|
||||
}()
|
||||
|
||||
// Initialize tracing (noop if not enabled).
|
||||
if err := tracing.Initialize(); err != nil {
|
||||
return fmt.Errorf("error initializing tracing: %w", err)
|
||||
}
|
||||
|
||||
// Open connection to the database
|
||||
dbService, err := bundb.NewBunDBService(ctx, &state)
|
||||
// Initialize caches
|
||||
state.Caches.Init()
|
||||
state.Caches.Start()
|
||||
|
||||
// Open connection to the database now caches started.
|
||||
dbService, err := bundb.NewBunDBService(ctx, state)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dbservice: %s", err)
|
||||
}
|
||||
|
||||
// Set the state DB connection
|
||||
// Set DB on state.
|
||||
state.DB = dbService
|
||||
|
||||
// Ensure necessary database instance prerequisites exist.
|
||||
if err := dbService.CreateInstanceAccount(ctx); err != nil {
|
||||
return fmt.Errorf("error creating instance account: %s", err)
|
||||
}
|
||||
|
||||
if err := dbService.CreateInstanceInstance(ctx); err != nil {
|
||||
return fmt.Errorf("error creating instance instance: %s", err)
|
||||
}
|
||||
if err := dbService.CreateInstanceApplication(ctx); err != nil {
|
||||
return fmt.Errorf("error creating instance application: %s", err)
|
||||
}
|
||||
|
||||
// Get the instance account
|
||||
// (we'll need this later).
|
||||
// Get the instance account (we'll need this later).
|
||||
instanceAccount, err := dbService.GetInstanceAccount(ctx, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving instance account: %w", err)
|
||||
}
|
||||
|
||||
// Open the storage backend
|
||||
storage, err := gtsstorage.AutoConfig()
|
||||
// Open the storage backend according to config.
|
||||
state.Storage, err = gtsstorage.AutoConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating storage backend: %w", err)
|
||||
return fmt.Errorf("error opening storage backend: %w", err)
|
||||
}
|
||||
|
||||
// Set the state storage driver
|
||||
state.Storage = storage
|
||||
|
||||
// Build HTTP client
|
||||
// Prepare wrapped httpclient with config.
|
||||
client := httpclient.New(httpclient.Config{
|
||||
AllowRanges: config.MustParseIPPrefixes(config.GetHTTPClientAllowIPs()),
|
||||
BlockRanges: config.MustParseIPPrefixes(config.GetHTTPClientBlockIPs()),
|
||||
|
@ -124,31 +176,15 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
TLSInsecureSkipVerify: config.GetHTTPClientTLSInsecureSkipVerify(),
|
||||
})
|
||||
|
||||
// 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)
|
||||
mediaManager := media.NewManager(state)
|
||||
oauthServer := oauth.New(ctx, dbService)
|
||||
typeConverter := typeutils.NewConverter(&state)
|
||||
visFilter := visibility.NewFilter(&state)
|
||||
spamFilter := spam.NewFilter(&state)
|
||||
federatingDB := federatingdb.New(&state, typeConverter, visFilter, spamFilter)
|
||||
transportController := transport.NewController(&state, federatingDB, &federation.Clock{}, client)
|
||||
federator := federation.NewFederator(&state, federatingDB, transportController, typeConverter, visFilter, mediaManager)
|
||||
typeConverter := typeutils.NewConverter(state)
|
||||
visFilter := visibility.NewFilter(state)
|
||||
spamFilter := spam.NewFilter(state)
|
||||
federatingDB := federatingdb.New(state, typeConverter, visFilter, spamFilter)
|
||||
transportController := transport.NewController(state, federatingDB, &federation.Clock{}, client)
|
||||
federator := federation.NewFederator(state, federatingDB, transportController, typeConverter, visFilter, mediaManager)
|
||||
|
||||
// Decide whether to create a noop email
|
||||
// sender (won't send emails) or a real one.
|
||||
|
@ -167,50 +203,75 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize timelines.
|
||||
// Initialize both home / list timelines.
|
||||
state.Timelines.Home = timeline.NewManager(
|
||||
tlprocessor.HomeTimelineGrab(&state),
|
||||
tlprocessor.HomeTimelineFilter(&state, visFilter),
|
||||
tlprocessor.HomeTimelineStatusPrepare(&state, typeConverter),
|
||||
tlprocessor.HomeTimelineGrab(state),
|
||||
tlprocessor.HomeTimelineFilter(state, visFilter),
|
||||
tlprocessor.HomeTimelineStatusPrepare(state, typeConverter),
|
||||
tlprocessor.SkipInsert(),
|
||||
)
|
||||
if err := state.Timelines.Home.Start(); err != nil {
|
||||
return fmt.Errorf("error starting home timeline: %s", err)
|
||||
}
|
||||
|
||||
state.Timelines.List = timeline.NewManager(
|
||||
tlprocessor.ListTimelineGrab(&state),
|
||||
tlprocessor.ListTimelineFilter(&state, visFilter),
|
||||
tlprocessor.ListTimelineStatusPrepare(&state, typeConverter),
|
||||
tlprocessor.ListTimelineGrab(state),
|
||||
tlprocessor.ListTimelineFilter(state, visFilter),
|
||||
tlprocessor.ListTimelineStatusPrepare(state, typeConverter),
|
||||
tlprocessor.SkipInsert(),
|
||||
)
|
||||
if err := state.Timelines.List.Start(); err != nil {
|
||||
return fmt.Errorf("error starting list timeline: %s", err)
|
||||
}
|
||||
|
||||
// Create a media cleaner using the given state.
|
||||
cleaner := cleaner.New(&state)
|
||||
// Start the job scheduler
|
||||
// (this is required for cleaner).
|
||||
state.Workers.StartScheduler()
|
||||
|
||||
// Create the processor using all the other services we've created so far.
|
||||
// Add a task to the scheduler to sweep caches.
|
||||
// Frequency = 1 * minute
|
||||
// Threshold = 60% capacity
|
||||
if !state.Workers.Scheduler.AddRecurring(
|
||||
"@cachesweep", // id
|
||||
time.Time{}, // start
|
||||
time.Minute, // freq
|
||||
func(context.Context, time.Time) {
|
||||
state.Caches.Sweep(60)
|
||||
},
|
||||
) {
|
||||
return fmt.Errorf("error scheduling cache sweep: %w", err)
|
||||
}
|
||||
|
||||
// Create background cleaner.
|
||||
cleaner := cleaner.New(state)
|
||||
|
||||
// Now schedule background cleaning tasks.
|
||||
if err := cleaner.ScheduleJobs(); err != nil {
|
||||
return fmt.Errorf("error scheduling cleaner jobs: %w", err)
|
||||
}
|
||||
|
||||
// Create the processor using all the
|
||||
// other services we've created so far.
|
||||
processor := processing.NewProcessor(
|
||||
cleaner,
|
||||
typeConverter,
|
||||
federator,
|
||||
oauthServer,
|
||||
mediaManager,
|
||||
&state,
|
||||
state,
|
||||
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 pools.
|
||||
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
|
||||
// Now start workers!
|
||||
state.Workers.Start()
|
||||
|
||||
// Schedule tasks for all existing poll expiries.
|
||||
// Schedule notif tasks for all existing poll expiries.
|
||||
if err := processor.Polls().ScheduleAll(ctx); err != nil {
|
||||
return fmt.Errorf("error scheduling poll expiries: %w", err)
|
||||
}
|
||||
|
@ -224,7 +285,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
HTTP router initialization
|
||||
*/
|
||||
|
||||
router, err := router.New(ctx)
|
||||
route, err = router.New(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating router: %s", err)
|
||||
}
|
||||
|
@ -249,7 +310,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
// note: hooks adding ctx fields must be ABOVE
|
||||
// the logger, otherwise won't be accessible.
|
||||
middleware.Logger(config.GetLogClientIP()),
|
||||
middleware.HeaderFilter(&state),
|
||||
middleware.HeaderFilter(state),
|
||||
middleware.UserAgent(),
|
||||
middleware.CORS(),
|
||||
middleware.ExtraHeaders(),
|
||||
|
@ -279,10 +340,10 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
middlewares = append(middlewares, middleware.ContentSecurityPolicy(cspExtraURIs...))
|
||||
|
||||
// attach global middlewares which are used for every request
|
||||
router.AttachGlobalMiddleware(middlewares...)
|
||||
route.AttachGlobalMiddleware(middlewares...)
|
||||
|
||||
// attach global no route / 404 handler to the router
|
||||
router.AttachNoRouteHandler(func(c *gin.Context) {
|
||||
route.AttachNoRouteHandler(func(c *gin.Context) {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), processor.InstanceGetV1)
|
||||
})
|
||||
|
||||
|
@ -307,7 +368,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
|
||||
var (
|
||||
authModule = api.NewAuth(dbService, processor, idp, routerSession, sessionName) // auth/oauth paths
|
||||
clientModule = api.NewClient(dbService, processor) // api client endpoints
|
||||
clientModule = api.NewClient(state, processor) // api client endpoints
|
||||
metricsModule = api.NewMetrics() // Metrics endpoints
|
||||
healthModule = api.NewHealth(dbService.Ready) // Health check endpoints
|
||||
fileserverModule = api.NewFileserver(processor) // fileserver endpoints
|
||||
|
@ -338,22 +399,21 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
|
||||
// these should be routed in order;
|
||||
// apply throttling *after* rate limiting
|
||||
authModule.Route(router, clLimit, clThrottle, gzip)
|
||||
clientModule.Route(router, clLimit, clThrottle, gzip)
|
||||
metricsModule.Route(router, clLimit, clThrottle, gzip)
|
||||
healthModule.Route(router, clLimit, clThrottle)
|
||||
fileserverModule.Route(router, fsMainLimit, fsThrottle)
|
||||
fileserverModule.RouteEmojis(router, instanceAccount.ID, fsEmojiLimit, fsThrottle)
|
||||
wellKnownModule.Route(router, gzip, s2sLimit, s2sThrottle)
|
||||
nodeInfoModule.Route(router, s2sLimit, s2sThrottle, gzip)
|
||||
activityPubModule.Route(router, s2sLimit, s2sThrottle, gzip)
|
||||
activityPubModule.RoutePublicKey(router, s2sLimit, pkThrottle, gzip)
|
||||
webModule.Route(router, fsMainLimit, fsThrottle, gzip)
|
||||
authModule.Route(route, clLimit, clThrottle, gzip)
|
||||
clientModule.Route(route, clLimit, clThrottle, gzip)
|
||||
metricsModule.Route(route, clLimit, clThrottle, gzip)
|
||||
healthModule.Route(route, clLimit, clThrottle)
|
||||
fileserverModule.Route(route, fsMainLimit, fsThrottle)
|
||||
fileserverModule.RouteEmojis(route, instanceAccount.ID, fsEmojiLimit, fsThrottle)
|
||||
wellKnownModule.Route(route, gzip, s2sLimit, s2sThrottle)
|
||||
nodeInfoModule.Route(route, s2sLimit, s2sThrottle, gzip)
|
||||
activityPubModule.Route(route, s2sLimit, s2sThrottle, gzip)
|
||||
activityPubModule.RoutePublicKey(route, s2sLimit, pkThrottle, gzip)
|
||||
webModule.Route(route, fsMainLimit, fsThrottle, gzip)
|
||||
|
||||
// Start the GoToSocial server.
|
||||
server := gotosocial.NewServer(dbService, router, cleaner)
|
||||
if err := server.Start(ctx); err != nil {
|
||||
return fmt.Errorf("error starting gotosocial service: %s", err)
|
||||
// Finally start the main http server!
|
||||
if err := route.Start(); err != nil {
|
||||
return fmt.Errorf("error starting router: %w", err)
|
||||
}
|
||||
|
||||
// catch shutdown signals from the operating system
|
||||
|
@ -362,11 +422,5 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
sig := <-sigs // block until signal received
|
||||
log.Infof(ctx, "received signal %s, shutting down", sig)
|
||||
|
||||
// close down all running services in order
|
||||
if err := server.Stop(ctx); err != nil {
|
||||
return fmt.Errorf("error closing gotosocial service: %s", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "done! exiting...")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/language"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -43,6 +42,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oidc"
|
||||
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
|
@ -54,11 +54,71 @@ import (
|
|||
|
||||
// Start creates and starts a gotosocial testrig server
|
||||
var Start action.GTSAction = func(ctx context.Context) error {
|
||||
var state state.State
|
||||
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
||||
var (
|
||||
// Define necessary core variables
|
||||
// before anything so we can prepare
|
||||
// defer function for safe shutdown
|
||||
// depending on what services were
|
||||
// managed to be started.
|
||||
|
||||
state = new(state.State)
|
||||
route *router.Router
|
||||
)
|
||||
|
||||
defer func() {
|
||||
// Stop caches with
|
||||
// background tasks.
|
||||
state.Caches.Stop()
|
||||
|
||||
if route != nil {
|
||||
// We reached a point where the API router
|
||||
// was created + setup. Ensure it gets stopped
|
||||
// first to stop processing new information.
|
||||
if err := route.Stop(); err != nil {
|
||||
log.Errorf(ctx, "error stopping router: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop any currently running
|
||||
// worker processes / scheduled
|
||||
// tasks from being executed.
|
||||
testrig.StopWorkers(state)
|
||||
|
||||
if state.Timelines.Home != nil {
|
||||
// Home timeline mgr was setup, ensure it gets stopped.
|
||||
if err := state.Timelines.Home.Stop(); err != nil {
|
||||
log.Errorf(ctx, "error stopping home timeline: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if state.Timelines.List != nil {
|
||||
// List timeline mgr was setup, ensure it gets stopped.
|
||||
if err := state.Timelines.List.Stop(); err != nil {
|
||||
log.Errorf(ctx, "error stopping list timeline: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if state.Storage != nil {
|
||||
// If storage was created, ensure torn down.
|
||||
testrig.StandardStorageTeardown(state.Storage)
|
||||
}
|
||||
|
||||
if state.DB != nil {
|
||||
// Lastly, if database service was started,
|
||||
// ensure it gets closed now all else stopped.
|
||||
testrig.StandardDBTeardown(state.DB)
|
||||
if err := state.DB.Close(); err != nil {
|
||||
log.Errorf(ctx, "error stopping database: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally reached end of shutdown.
|
||||
log.Info(ctx, "done! exiting...")
|
||||
}()
|
||||
|
||||
parsedLangs, err := language.InitLangs(config.GetInstanceLanguages().TagStrs())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing languages: %w", err)
|
||||
|
@ -70,17 +130,15 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Initialize caches and database
|
||||
state.DB = testrig.NewTestDB(&state)
|
||||
state.DB = testrig.NewTestDB(state)
|
||||
|
||||
// New test db inits caches so we don't need to do
|
||||
// that twice, we can just start the initialized caches.
|
||||
state.Caches.Start()
|
||||
defer state.Caches.Stop()
|
||||
|
||||
testrig.StandardDBSetup(state.DB, nil)
|
||||
|
||||
// Get the instance account
|
||||
// (we'll need this later).
|
||||
// Get the instance account (we'll need this later).
|
||||
instanceAccount, err := state.DB.GetInstanceAccount(ctx, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving instance account: %w", err)
|
||||
|
@ -98,11 +156,11 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
testrig.StandardStorageSetup(state.Storage, "./testrig/media")
|
||||
|
||||
// Initialize workers.
|
||||
state.Workers.Start()
|
||||
defer state.Workers.Stop()
|
||||
testrig.StartNoopWorkers(state)
|
||||
defer testrig.StopWorkers(state)
|
||||
|
||||
// build backend handlers
|
||||
transportController := testrig.NewTestTransportController(&state, testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
transportController := testrig.NewTestTransportController(state, testrig.NewMockHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
r := io.NopCloser(bytes.NewReader([]byte{}))
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
|
@ -112,35 +170,34 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
},
|
||||
}, nil
|
||||
}, ""))
|
||||
mediaManager := testrig.NewTestMediaManager(&state)
|
||||
federator := testrig.NewTestFederator(&state, transportController, mediaManager)
|
||||
mediaManager := testrig.NewTestMediaManager(state)
|
||||
federator := testrig.NewTestFederator(state, transportController, mediaManager)
|
||||
|
||||
emailSender := testrig.NewEmailSender("./web/template/", nil)
|
||||
typeConverter := typeutils.NewConverter(&state)
|
||||
filter := visibility.NewFilter(&state)
|
||||
typeConverter := typeutils.NewConverter(state)
|
||||
filter := visibility.NewFilter(state)
|
||||
|
||||
// Initialize timelines.
|
||||
// Initialize both home / list timelines.
|
||||
state.Timelines.Home = timeline.NewManager(
|
||||
tlprocessor.HomeTimelineGrab(&state),
|
||||
tlprocessor.HomeTimelineFilter(&state, filter),
|
||||
tlprocessor.HomeTimelineStatusPrepare(&state, typeConverter),
|
||||
tlprocessor.HomeTimelineGrab(state),
|
||||
tlprocessor.HomeTimelineFilter(state, filter),
|
||||
tlprocessor.HomeTimelineStatusPrepare(state, typeConverter),
|
||||
tlprocessor.SkipInsert(),
|
||||
)
|
||||
if err := state.Timelines.Home.Start(); err != nil {
|
||||
return fmt.Errorf("error starting home timeline: %s", err)
|
||||
}
|
||||
|
||||
state.Timelines.List = timeline.NewManager(
|
||||
tlprocessor.ListTimelineGrab(&state),
|
||||
tlprocessor.ListTimelineFilter(&state, filter),
|
||||
tlprocessor.ListTimelineStatusPrepare(&state, typeConverter),
|
||||
tlprocessor.ListTimelineGrab(state),
|
||||
tlprocessor.ListTimelineFilter(state, filter),
|
||||
tlprocessor.ListTimelineStatusPrepare(state, typeConverter),
|
||||
tlprocessor.SkipInsert(),
|
||||
)
|
||||
if err := state.Timelines.List.Start(); err != nil {
|
||||
return fmt.Errorf("error starting list timeline: %s", err)
|
||||
}
|
||||
|
||||
processor := testrig.NewTestProcessor(&state, federator, emailSender, mediaManager)
|
||||
processor := testrig.NewTestProcessor(state, federator, emailSender, mediaManager)
|
||||
|
||||
// Initialize metrics.
|
||||
if err := metrics.Initialize(state.DB); err != nil {
|
||||
|
@ -151,7 +208,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
HTTP router initialization
|
||||
*/
|
||||
|
||||
router := testrig.NewTestRouter(state.DB)
|
||||
route = testrig.NewTestRouter(state.DB)
|
||||
middlewares := []gin.HandlerFunc{
|
||||
middleware.AddRequestID(config.GetRequestIDHeader()), // requestID middleware must run before tracing
|
||||
}
|
||||
|
@ -165,6 +222,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
|
||||
middlewares = append(middlewares, []gin.HandlerFunc{
|
||||
middleware.Logger(config.GetLogClientIP()),
|
||||
middleware.HeaderFilter(state),
|
||||
middleware.UserAgent(),
|
||||
middleware.CORS(),
|
||||
middleware.ExtraHeaders(),
|
||||
|
@ -194,10 +252,10 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
middlewares = append(middlewares, middleware.ContentSecurityPolicy(cspExtraURIs...))
|
||||
|
||||
// attach global middlewares which are used for every request
|
||||
router.AttachGlobalMiddleware(middlewares...)
|
||||
route.AttachGlobalMiddleware(middlewares...)
|
||||
|
||||
// attach global no route / 404 handler to the router
|
||||
router.AttachNoRouteHandler(func(c *gin.Context) {
|
||||
route.AttachNoRouteHandler(func(c *gin.Context) {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(errors.New(http.StatusText(http.StatusNotFound))), processor.InstanceGetV1)
|
||||
})
|
||||
|
||||
|
@ -222,7 +280,7 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
|
||||
var (
|
||||
authModule = api.NewAuth(state.DB, processor, idp, routerSession, sessionName) // auth/oauth paths
|
||||
clientModule = api.NewClient(state.DB, processor) // api client endpoints
|
||||
clientModule = api.NewClient(state, processor) // api client endpoints
|
||||
metricsModule = api.NewMetrics() // Metrics endpoints
|
||||
healthModule = api.NewHealth(state.DB.Ready) // Health check endpoints
|
||||
fileserverModule = api.NewFileserver(processor) // fileserver endpoints
|
||||
|
@ -233,23 +291,29 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
)
|
||||
|
||||
// these should be routed in order
|
||||
authModule.Route(router)
|
||||
clientModule.Route(router)
|
||||
metricsModule.Route(router)
|
||||
healthModule.Route(router)
|
||||
fileserverModule.Route(router)
|
||||
fileserverModule.RouteEmojis(router, instanceAccount.ID)
|
||||
wellKnownModule.Route(router)
|
||||
nodeInfoModule.Route(router)
|
||||
activityPubModule.Route(router)
|
||||
activityPubModule.RoutePublicKey(router)
|
||||
webModule.Route(router)
|
||||
authModule.Route(route)
|
||||
clientModule.Route(route)
|
||||
metricsModule.Route(route)
|
||||
healthModule.Route(route)
|
||||
fileserverModule.Route(route)
|
||||
fileserverModule.RouteEmojis(route, instanceAccount.ID)
|
||||
wellKnownModule.Route(route)
|
||||
nodeInfoModule.Route(route)
|
||||
activityPubModule.Route(route)
|
||||
activityPubModule.RoutePublicKey(route)
|
||||
webModule.Route(route)
|
||||
|
||||
cleaner := cleaner.New(&state)
|
||||
// Create background cleaner.
|
||||
cleaner := cleaner.New(state)
|
||||
|
||||
gts := gotosocial.NewServer(state.DB, router, cleaner)
|
||||
if err := gts.Start(ctx); err != nil {
|
||||
return fmt.Errorf("error starting gotosocial service: %s", err)
|
||||
// Now schedule background cleaning tasks.
|
||||
if err := cleaner.ScheduleJobs(); err != nil {
|
||||
return fmt.Errorf("error scheduling cleaner jobs: %w", err)
|
||||
}
|
||||
|
||||
// Finally start the main http server!
|
||||
if err := route.Start(); err != nil {
|
||||
return fmt.Errorf("error starting router: %w", err)
|
||||
}
|
||||
|
||||
// catch shutdown signals from the operating system
|
||||
|
@ -258,14 +322,5 @@ var Start action.GTSAction = func(ctx context.Context) error {
|
|||
sig := <-sigs
|
||||
log.Infof(ctx, "received signal %s, shutting down", sig)
|
||||
|
||||
testrig.StandardDBTeardown(state.DB)
|
||||
testrig.StandardStorageTeardown(state.Storage)
|
||||
|
||||
// close down all running services in order
|
||||
if err := gts.Stop(ctx); err != nil {
|
||||
return fmt.Errorf("error closing gotosocial service: %s", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "done! exiting...")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ GoToSocial currently offers 'block', 'allow' and disabled HTTP request header fi
|
|||
|
||||
HTTP request header filtering is also still considered "experimental". It should do what it says on the box, but it may cause bugs or edge cases to appear elsewhere, we're not sure yet!
|
||||
|
||||
Management via settings panel is TBA. Until then you will need to manage these directly via API endpoints.
|
||||
|
||||
## Disabled header filtering mode (default)
|
||||
|
||||
When `advanced-header-filter-mode` is set to `""`, i.e. an empty string, all request header filtering will be disabled.
|
||||
|
@ -30,4 +28,4 @@ In allow mode, a block header filter can be used to override an existing allow f
|
|||
A request in allow mode will only be accepted if it is EXPLICITLY ALLOWED AND NOT EXPLICITLY BLOCKED.
|
||||
|
||||
!!! danger
|
||||
Allow filtering mode is an extremely restrictive mode that will almost certainly prevent many (legitimate) clients from being able to access your instance, including yourself. You should only enable this mode if you know exactly what you're trying to achieve.
|
||||
Allow filtering mode is an extremely restrictive mode that will almost certainly prevent many (legitimate) clients from being able to access your instance, including yourself. You should only enable this mode if you know exactly what you're trying to achieve.
|
||||
|
|
|
@ -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/
|
|
@ -66,6 +66,10 @@ Instance administration settings.
|
|||
|
||||
Run one-off administrative actions.
|
||||
|
||||
#### Email
|
||||
|
||||
You can use this section to send a test email to the given email address, with an optional test message.
|
||||
|
||||
#### Media
|
||||
|
||||
You can use this section run a media action to clean up the remote media cache using the specified number of days. Media older than the given number of days will be removed from storage (s3 or local). Media removed in this way will be refetched again later if the media is required again. This action is functionally identical to the media cleanup that runs automatically.
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# New Account Sign-Ups
|
||||
|
||||
If you want to allow more people than just you to have an account on your instance, you can open your instance to new account sign-ups / registrations.
|
||||
|
||||
Be wary that as instance admin, like it or not, you are responsible for what people post on your instance. If users on your instance harass or annoy other people on the fediverse, you may find your instance gets a bad reputation and becomes blocked by others. Moderating a space properly takes work. As such, you should carefully consider whether or not you are willing and able to do moderation, and consider accepting sign-ups on your instance only from friends and people that you really trust.
|
||||
|
||||
!!! warning
|
||||
For the sign-up flow to work as intended, your instance [should be configured to send emails](../configuration/smtp.md).
|
||||
|
||||
As mentioned below, several emails are sent during the sign-up flow, both to you (as admin/moderator) and to the applicant, including an email asking them to confirm their email address.
|
||||
|
||||
If they cannot receive this email (because your instance is not configured to send emails), you will have to manually confirm the account by [using the CLI tool](../admin/cli.md#gotosocial-admin-account-confirm).
|
||||
|
||||
## Opening Sign-Ups
|
||||
|
||||
You can open new account sign-ups for your instance by changing the variable `accounts-registration-open` to `true` in your [configuration](../configuration/accounts.md), and restarting your GoToSocial instance.
|
||||
|
||||
A sign-up form for your instance will be available at the `/signup` endpoint. For example, `https://your-instance.example.org/signup`.
|
||||
|
||||
![Sign-up form, showing email, password, username, and reason fields.](../assets/signup-form.png)
|
||||
|
||||
Also, your instance homepage and "about" pages will be updated to reflect that registrations are open.
|
||||
|
||||
When someone submits a new sign-up, they'll receive an email at the provided email address, giving them a link to confirm that the address really belongs to them.
|
||||
|
||||
In the meantime, admins and moderators on your instance will receive an email and a notification that a new sign-up has been submitted.
|
||||
|
||||
## Handling Sign-Ups
|
||||
|
||||
Instance admins and moderators can handle a new sign-up by either approving or rejecting it via the "accounts" -> "pending" section in the admin panel.
|
||||
|
||||
![Admin settings panel open to "accounts" -> "pending", showing one account in a list.](../assets/signup-pending.png)
|
||||
|
||||
If you have no sign-ups, the list pictured above will be empty. If you have a pending account sign-up, however, you can click on it to open that account in the account details screen:
|
||||
|
||||
![Details of a new pending account, giving options to approve or reject the sign-up.](../assets/signup-account.png)
|
||||
|
||||
At the bottom, you will find actions that let you approve or reject the sign-up.
|
||||
|
||||
If you **approve** the sign-up, the account will be marked as "approved", and an email will be sent to the applicant informing them their sign-up has been approved, and reminding them to confirm their email address if they haven't already done so. If they have already confirmed their email address, they will be able to log in and start using their account.
|
||||
|
||||
If you **reject** the sign-up, you may wish to inform the applicant that their sign-up has been rejected, which you can do by ticking the "send email" checkbox. This will send a short email to the applicant informing them of the rejection. If you wish, you can add a custom message, which will be added at the bottom of the email. You can also add a private note that will be visible to other admins only.
|
||||
|
||||
!!! warning
|
||||
You may want to hold off on approving a sign-up until they have confirmed their email address, in case the applicant made a typo when submitting, or the email address they provided does not actually belong to them. If they cannot confirm their email address, they will not be able to log in and use their account.
|
||||
|
||||
## Sign-Up Limits
|
||||
|
||||
To avoid sign-up backlogs overwhelming admins and moderators, GoToSocial limits the sign-up pending backlog to 20 accounts. Once there are 20 accounts pending in the backlog waiting to be handled by an admin or moderator, new sign-ups will not be accepted via the form.
|
||||
|
||||
New sign-ups will also not be accepted via the form if 10 or more new account sign-ups have been approved in the last 24 hours, to avoid instances rapidly expanding beyond the capabilities of moderators.
|
||||
|
||||
In both cases, applicants will be shown an error message explaining why they could not submit the form, and inviting them to try again later.
|
||||
|
||||
To combat spam accounts, GoToSocial account sign-ups **always** require manual approval by an administrator, and applicants must **always** confirm their email address before they are able to log in and post.
|
||||
|
||||
## Sign-Up Via Invite
|
||||
|
||||
NOT IMPLEMENTED YET: in a future update, admins and moderators will be able to create and send invites that allow accounts to be created even when public sign-up is closed, and to pre-approve accounts created via invitation, and/or allow them to override the sign-up limits described above.
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
|
@ -9,15 +9,11 @@
|
|||
|
||||
# Config pertaining to creation and maintenance of accounts on the server, as well as defaults for new accounts.
|
||||
|
||||
# Bool. Do we want people to be able to just submit sign up requests, or do we want invite only?
|
||||
# Bool. Allow people to submit new sign-up / registration requests via the form at /signup.
|
||||
#
|
||||
# Options: [true, false]
|
||||
# Default: true
|
||||
accounts-registration-open: true
|
||||
|
||||
# Bool. Do sign up requests require approval from an admin/moderator before an account can sign in/use the server?
|
||||
# Options: [true, false]
|
||||
# Default: true
|
||||
accounts-approval-required: true
|
||||
# Default: false
|
||||
accounts-registration-open: false
|
||||
|
||||
# Bool. Are sign up requests required to submit a reason for the request (eg., an explanation of why they want to join the instance)?
|
||||
# Options: [true, false]
|
||||
|
|
|
@ -119,15 +119,10 @@ advanced-throttling-multiplier: 8
|
|||
# Default: "30s"
|
||||
advanced-throttling-retry-after: "30s"
|
||||
|
||||
# Int. CPU multiplier for the amount of goroutines to spawn in order to send messages via ActivityPub.
|
||||
# Messages will be batched so that at most multiplier * CPU count messages will be sent out at once.
|
||||
# This can be tuned to limit concurrent POSTing to remote inboxes, preventing your instance CPU
|
||||
# usage from skyrocketing when an account with many followers posts a new status.
|
||||
#
|
||||
# Messages are split among available senders, and each sender processes its assigned messages in serial.
|
||||
# For example, say a user with 1000 followers is on an instance with 2 CPUs. With the default multiplier
|
||||
# of 2, this means 4 senders would be in process at once on this instance. When the user creates a new post,
|
||||
# each sender would end up iterating through about 250 Create messages + delivering them to remote instances.
|
||||
# Int. CPU multiplier for the fixed number of goroutines to spawn in order to send messages via ActivityPub.
|
||||
# Messages will be batched and pushed to a singular queue, from which multiplier * CPU count goroutines will
|
||||
# pull and attempt deliveries. This can be tuned to limit concurrent posting to remote inboxes, preventing
|
||||
# your instance CPU usage skyrocketing when accounts with many followers post statuses.
|
||||
#
|
||||
# If you set this to 0 or less, only 1 sender will be used regardless of CPU count. This may be
|
||||
# useful in cases where you are working with very tight network or CPU constraints.
|
||||
|
@ -168,4 +163,23 @@ advanced-sender-multiplier: 2
|
|||
# Example: ["s3.example.org", "some-bucket-name.s3.example.org"]
|
||||
# Default: []
|
||||
advanced-csp-extra-uris: []
|
||||
|
||||
# String. HTTP request header filtering mode to use for this instance.
|
||||
#
|
||||
# "block" -- only requests that are explicitly blocked by header filters
|
||||
# will be denied (unless they are also explicitly allowed).
|
||||
#
|
||||
# "allow" -- only requests that are explicitly allowed by header filters
|
||||
# will be accepted (unless they are also explicitly blocked).
|
||||
# This mode is considered experimental and will almost certainly
|
||||
# break access to your instance unless you are very careful.
|
||||
#
|
||||
# "" -- request header filtering disabled.
|
||||
#
|
||||
# For more details on block and allow modes, check the documentation at:
|
||||
# https://docs.gotosocial.org/en/latest/admin/request_filtering_modes
|
||||
#
|
||||
# Options: ["block", "allow", ""]
|
||||
# Default: ""
|
||||
advanced-header-filter-mode: ""
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ host: "localhost"
|
|||
# DO NOT change this after your server has already run once, or you will break things!
|
||||
#
|
||||
# Please read the appropriate section of the installation guide before you go messing around with this setting:
|
||||
# https://docs.gotosocial.org/installation_guide/advanced/#can-i-host-my-instance-at-fediexampleorg-but-have-just-exampleorg-in-my-username
|
||||
# https://docs.gotosocial.org/en/latest/advanced/host-account-domain/
|
||||
#
|
||||
# Examples: ["example.org","server.com"]
|
||||
# Default: ""
|
||||
|
|
|
@ -6,6 +6,8 @@ Configuring GoToSocial to send emails is **not required** in order to have a pro
|
|||
|
||||
In order to make GoToSocial email sending work, you need an smtp-compatible mail service running somewhere, either as a server on the same machine that GoToSocial is running on, or via an external service like [Mailgun](https://mailgun.com). It may also be possible to use a free personal email address for sending emails, if your email provider supports smtp (check with them--most do), but you might run into trouble sending lots of emails.
|
||||
|
||||
To validate your configuration, you can use the "Administration -> Actions -> Email" section of the settings panel to send a test email.
|
||||
|
||||
## Settings
|
||||
|
||||
The configuration options for smtp are as follows:
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Search
|
||||
|
||||
## Query formats
|
||||
|
||||
GotoSocial accepts several kinds of search query:
|
||||
|
||||
- `@username`: search for an account with the given username on any domain. Can return multiple results.
|
||||
- `@username@domain`: search for a remote account with exact username and domain. Will only ever return 1 result at most.
|
||||
- `https://example.org/some/arbitrary/url`: search for an account or post with the given URL. If the account or post hasn't already federated to GotoSocial, it will try to retrieve it. Will only ever return 1 result at most.
|
||||
- `#hashtag_name`: search for a hashtag with the given hashtag name, or starting with the given hashtag name. Case insensitive. Can return multiple results.
|
||||
- `any arbitrary text`: search for posts containing the text, hashtags containing the text, and accounts with usernames, display names, or bios containing the text, exactly as written. Both posts you've written as well as posts replying to you will be searched. Account bios will only be searched for accounts that you follow. Can return multiple results.
|
||||
|
||||
## Search operators
|
||||
|
||||
Arbitrary text queries may include the following search operators:
|
||||
|
||||
- `from:username`: restrict results to statuses created by the specified *local* account.
|
||||
- `from:username@domain`: restrict results to statuses created by the specified remote account.
|
||||
|
||||
For example, you can search for `sloth from:yourusername` to find your own posts about sloths.
|
|
@ -406,15 +406,11 @@ instance-inject-mastodon-version: false
|
|||
|
||||
# Config pertaining to creation and maintenance of accounts on the server, as well as defaults for new accounts.
|
||||
|
||||
# Bool. Do we want people to be able to just submit sign up requests, or do we want invite only?
|
||||
# Bool. Allow people to submit new sign-up / registration requests via the form at /signup.
|
||||
#
|
||||
# Options: [true, false]
|
||||
# Default: true
|
||||
accounts-registration-open: true
|
||||
|
||||
# Bool. Do sign up requests require approval from an admin/moderator before an account can sign in/use the server?
|
||||
# Options: [true, false]
|
||||
# Default: true
|
||||
accounts-approval-required: true
|
||||
# Default: false
|
||||
accounts-registration-open: false
|
||||
|
||||
# Bool. Are sign up requests required to submit a reason for the request (eg., an explanation of why they want to join the instance)?
|
||||
# Options: [true, false]
|
||||
|
@ -1042,15 +1038,10 @@ advanced-throttling-multiplier: 8
|
|||
# Default: "30s"
|
||||
advanced-throttling-retry-after: "30s"
|
||||
|
||||
# Int. CPU multiplier for the amount of goroutines to spawn in order to send messages via ActivityPub.
|
||||
# Messages will be batched so that at most multiplier * CPU count messages will be sent out at once.
|
||||
# This can be tuned to limit concurrent POSTing to remote inboxes, preventing your instance CPU
|
||||
# usage from skyrocketing when an account with many followers posts a new status.
|
||||
#
|
||||
# Messages are split among available senders, and each sender processes its assigned messages in serial.
|
||||
# For example, say a user with 1000 followers is on an instance with 2 CPUs. With the default multiplier
|
||||
# of 2, this means 4 senders would be in process at once on this instance. When the user creates a new post,
|
||||
# each sender would end up iterating through about 250 Create messages + delivering them to remote instances.
|
||||
# Int. CPU multiplier for the fixed number of goroutines to spawn in order to send messages via ActivityPub.
|
||||
# Messages will be batched and pushed to a singular queue, from which multiplier * CPU count goroutines will
|
||||
# pull and attempt deliveries. This can be tuned to limit concurrent posting to remote inboxes, preventing
|
||||
# your instance CPU usage skyrocketing when accounts with many followers post statuses.
|
||||
#
|
||||
# If you set this to 0 or less, only 1 sender will be used regardless of CPU count. This may be
|
||||
# useful in cases where you are working with very tight network or CPU constraints.
|
||||
|
@ -1099,6 +1090,8 @@ advanced-csp-extra-uris: []
|
|||
#
|
||||
# "allow" -- only requests that are explicitly allowed by header filters
|
||||
# will be accepted (unless they are also explicitly blocked).
|
||||
# This mode is considered experimental and will almost certainly
|
||||
# break access to your instance unless you are very careful.
|
||||
#
|
||||
# "" -- request header filtering disabled.
|
||||
#
|
||||
|
|
164
go.mod
164
go.mod
|
@ -1,10 +1,8 @@
|
|||
module github.com/superseriousbusiness/gotosocial
|
||||
|
||||
go 1.21
|
||||
go 1.22.2
|
||||
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround
|
||||
|
||||
toolchain go1.21.3
|
||||
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround
|
||||
|
||||
require (
|
||||
codeberg.org/gruf/go-bytes v1.0.2
|
||||
|
@ -16,69 +14,72 @@ 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-mempool v0.0.0-20240507125005-cef10d64a760
|
||||
codeberg.org/gruf/go-mutexes v1.5.0
|
||||
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.0
|
||||
codeberg.org/gruf/go-storage v0.1.1
|
||||
codeberg.org/gruf/go-structr v0.8.4
|
||||
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.1
|
||||
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
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gin-contrib/cors v1.7.1
|
||||
github.com/gin-contrib/gzip v1.0.0
|
||||
github.com/gin-contrib/sessions v1.0.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-contrib/sessions v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
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.31.0
|
||||
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/jackc/pgx/v5 v5.6.0
|
||||
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/ncruces/go-sqlite3 v0.16.0
|
||||
github.com/oklog/ulid v1.3.1
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240221151241-5d56c04088d4
|
||||
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.32
|
||||
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
|
||||
github.com/uptrace/bun v1.1.17
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.17
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.17
|
||||
github.com/uptrace/bun/extra/bunotel v1.1.17
|
||||
github.com/uptrace/bun v1.2.1
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.1
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1
|
||||
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.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.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.24.0
|
||||
go.opentelemetry.io/otel/sdk v1.24.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.24.0
|
||||
go.opentelemetry.io/otel/trace v1.26.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.22.0
|
||||
golang.org/x/oauth2 v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/oauth2 v0.20.0
|
||||
golang.org/x/text v0.15.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 v0.0.0-00010101000000-000000000000
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
|
@ -89,29 +90,31 @@ 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
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.3 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/cilium/ebpf v0.9.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/cornelk/hashmap v1.0.8 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dolthub/swiss v0.2.1 // indirect
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b // indirect
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
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,36 +123,35 @@ 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
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // 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
|
||||
|
@ -158,7 +160,7 @@ require (
|
|||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
|
@ -166,29 +168,28 @@ require (
|
|||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
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
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.0.2 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
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
|
||||
|
@ -197,34 +198,35 @@ 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.14 // indirect
|
||||
github.com/tetratelabs/wazero v1.7.2 // 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
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 // indirect
|
||||
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.24.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/mod v0.14.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/grpc v1.61.1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/tools v0.21.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.2 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.41.0 // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
|
|
458
go.sum
458
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,18 @@ 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-mempool v0.0.0-20240507125005-cef10d64a760 h1:m2/UCRXhjDwAg4vyji6iKCpomKw6P4PmBOUi5DvAMH4=
|
||||
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760/go.mod h1:E3RcaCFNq4zXpvaJb8lfpPqdUAmSkP5F1VmMiEUYTEk=
|
||||
codeberg.org/gruf/go-mutexes v1.5.0 h1:kDegqA/FYQhcn294zUJ3U3VBegfvhtcI7ObcAku1zkw=
|
||||
codeberg.org/gruf/go-mutexes v1.5.0/go.mod h1:rPEqQ/y6CmGITaZ3GPTMQVsoZAOzbsAHyIaLsJcOqVE=
|
||||
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.0 h1:cFiTE0SN1RzgX833y5DnPgWcdVNfqcvLVvPGLTNcvjg=
|
||||
codeberg.org/gruf/go-structr v0.6.0/go.mod h1:K1FXkUyO6N/JKt8aWqyQ8rtW7Z9ZmXKWP8mFAQ2OJjE=
|
||||
codeberg.org/gruf/go-storage v0.1.1 h1:CSX1PMMg/7vqqK8aCFtq94xCrOB3xhj7eWIvzILdLpY=
|
||||
codeberg.org/gruf/go-storage v0.1.1/go.mod h1:145IWMUOc6YpIiZIiCIEwkkNZZPiSbwMnZxRjSc5q6c=
|
||||
codeberg.org/gruf/go-structr v0.8.4 h1:2eT1VOTWG6T9gIGZwF/1Jop6k6plvfdUY5yBcvbizVg=
|
||||
codeberg.org/gruf/go-structr v0.8.4/go.mod h1:c5UvVDSA3lZ1kv05V+7pXkO8u8Jea+VRWFDRFBCOxSA=
|
||||
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 +85,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.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8=
|
||||
github.com/KimMachineGun/automemlimit v0.6.1/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 +101,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=
|
||||
|
@ -107,28 +109,25 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
|||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do=
|
||||
github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
|
||||
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=
|
||||
github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
|
||||
|
@ -138,8 +137,6 @@ github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoE
|
|||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cornelk/hashmap v1.0.8 h1:nv0AWgw02n+iDcawr5It4CjQIAcdMMKRrs10HOJYlrc=
|
||||
github.com/cornelk/hashmap v1.0.8/go.mod h1:RfZb7JO3RviW/rT6emczVuC/oxpdz4UsSB2LJSclR1k=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -151,6 +148,10 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
|
|||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
|
||||
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
|
||||
github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw=
|
||||
github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
|
@ -176,9 +177,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=
|
||||
|
@ -191,16 +191,16 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
|
|||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/gin-contrib/cors v1.7.1 h1:s9SIppU/rk8enVvkzwiC2VK3UZ/0NNGsWfUKvV55rqs=
|
||||
github.com/gin-contrib/cors v1.7.1/go.mod h1:n/Zj7B4xyrgk/cX1WCX2dkzFfaNm/xJb6oIUk7WTtps=
|
||||
github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk=
|
||||
github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE=
|
||||
github.com/gin-contrib/sessions v1.0.0 h1:r5GLta4Oy5xo9rAwMHx8B4wLpeRGHMdz9NafzJAdP8Y=
|
||||
github.com/gin-contrib/sessions v1.0.0/go.mod h1:DN0f4bvpqMQElDdi+gNGScrP2QEI04IErRyMFyorUOI=
|
||||
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
|
||||
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
|
||||
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
|
@ -218,46 +218,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=
|
||||
|
@ -267,42 +249,15 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.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-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.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.31.0 h1:H8eOYQnY2u7vNKWDNykv2xJP3pBhRG/R+SOCAmKrLlc=
|
||||
github.com/go-swagger/go-swagger v0.31.0/go.mod h1:WSigRRWEig8zV6t6Sm8Y+EmUjlzA/HoaZJ5edupq7po=
|
||||
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,10 +297,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/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
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=
|
||||
|
@ -355,8 +306,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.5.5/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=
|
||||
|
@ -373,8 +322,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -390,8 +339,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=
|
||||
|
@ -399,8 +348,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=
|
||||
|
@ -410,23 +359,23 @@ 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=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
|
@ -434,7 +383,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=
|
||||
|
@ -444,23 +392,17 @@ 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/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
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=
|
||||
|
@ -472,38 +414,25 @@ 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=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
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=
|
||||
|
@ -514,12 +443,14 @@ 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-sqlite3 v0.16.0 h1:O7eULuEjvSBnS1QCN+dDL/ixLQZoUGWr466A02Gx1xc=
|
||||
github.com/ncruces/go-sqlite3 v0.16.0/go.mod h1:2TmAeD93ImsKXJRsUIKohfMvt17dZSbS6pzJ3k6YYFg=
|
||||
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/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
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=
|
||||
|
@ -534,12 +465,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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/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=
|
||||
|
@ -547,25 +475,23 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/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=
|
||||
|
@ -576,11 +502,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=
|
||||
|
@ -594,16 +518,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=
|
||||
|
@ -622,8 +543,8 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
|
|||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/sunfish-shogi/bufseekio v0.0.0-20210207115823-a4185644b365/go.mod h1:dEzdXgvImkQ3WLI+0KQpmEx8T/C/ma9KeS3AfmU899I=
|
||||
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240221151241-5d56c04088d4 h1:kPjQR/hVZtROTzkxptp/EIR7Wm58O8jppwpCFrZ7sVU=
|
||||
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240221151241-5d56c04088d4/go.mod h1:AZw0Xb4Oju8rmaJCZ21gc5CPg47MmNgyac+Hx5jo8VM=
|
||||
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240408131430-247f7f7110f0 h1:zPdbgwbjPxrJqme2sFTMQoML5ukNWRhChOnilR47rss=
|
||||
github.com/superseriousbusiness/activity v1.6.0-gts.0.20240408131430-247f7f7110f0/go.mod h1:AZw0Xb4Oju8rmaJCZ21gc5CPg47MmNgyac+Hx5jo8VM=
|
||||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe h1:ksl2oCx/Qo8sNDc3Grb8WGKBM9nkvhCm25uvlT86azE=
|
||||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4=
|
||||
github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB h1:8psprYSK1KdOSH7yQ4PbJq0YYaGQY+gzdW/B0ExDb/8=
|
||||
|
@ -632,15 +553,17 @@ 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.32 h1:rk4THvBPLEU+gGDKaJxyvFhF5+quSwCk3HKv1GpSVyE=
|
||||
github.com/tdewolff/minify/v2 v2.20.32/go.mod h1:1TJni7+mATKu24cBQQpgwakrYRD27uC1/rdJOgdv8ns=
|
||||
github.com/tdewolff/parse/v2 v2.7.14 h1:100KJ+QAO3PpMb3uUjzEU/NpmCdbBYz6KPmCIAfWpR8=
|
||||
github.com/tdewolff/parse/v2 v2.7.14/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=
|
||||
github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw=
|
||||
github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So=
|
||||
github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
|
||||
github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
||||
|
@ -671,16 +594,16 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
|||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA=
|
||||
github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI=
|
||||
github.com/uptrace/bun v1.1.17 h1:qxBaEIo0hC/8O3O6GrMDKxqyT+mw5/s0Pn/n6xjyGIk=
|
||||
github.com/uptrace/bun v1.1.17/go.mod h1:hATAzivtTIRsSJR4B8AXR+uABqnQxr3myKDKEf5iQ9U=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.17 h1:NsvFVHAx1Az6ytlAD/B6ty3cVE6j9Yp82bjqd9R9hOs=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.17/go.mod h1:fLBDclNc7nKsZLzNjFL6BqSdgJzbj2HdnyOnLoDvAME=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.17 h1:i8NFU9r8YuavNFaYlNqi4ppn+MgoHtqLgpWQDrVTjm0=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.17/go.mod h1:YF0FO4VVnY9GHNH6rM4r3STlVEBxkOc6L88Bm5X5mzA=
|
||||
github.com/uptrace/bun/extra/bunotel v1.1.17 h1:RLEJdHH06RI9BLg06Vu1JHJ3KNHQCfwa2Fa3x+56qkk=
|
||||
github.com/uptrace/bun/extra/bunotel v1.1.17/go.mod h1:xV7AYrCFji4Sio6N9X+Cz+XJ+JuHq6TQQjuxaVbsypk=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3 h1:LNi0Qa7869/loPjz2kmMvp/jwZZnMZ9scMJKhDJ1DIo=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.3/go.mod h1:jyigonKik3C5V895QNiAGpKYKEvFuqjw9qAEZks1mUg=
|
||||
github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
|
||||
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.1 h1:ceP99r03u+s8ylaDE/RzgcajwGiC76Jz3nS2ZgyPQ4M=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.1/go.mod h1:mv6B12cisvSc6bwKm9q9wcrr26awkZK8QXM+nso9n2U=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 h1:IprvkIKUjEjvt4VKpcmLpbMIucjrsmUPJOSlg19+a0Q=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1/go.mod h1:mMQf4NUpgY8bnOanxGmxNiHCdALOggS4cZ3v63a9D/o=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.1 h1:5oTy3Jh7Q1bhCd5vnPszBmJgYouw+PuuZ8iSCm+uNCQ=
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.1/go.mod h1:SWW3HyjiXPYM36q0QSpdtTP8v21nWHnTCxu4lYkpO90=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4 h1:x3omFAG2XkvWFg1hvXRinY2ExAL1Aacl7W9ZlYjo6gc=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.4/go.mod h1:qMKJr5fTnY0p7hqCQMNrAk62bCARWR5rAbTrGUFRuh4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
|
||||
|
@ -695,11 +618,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=
|
||||
|
@ -708,7 +626,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=
|
||||
|
@ -720,11 +637,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.5-concurrency-workaround h1:cyYnGCVJ0zLW2Q0pCepy++ERHegWcKpl5JD1MiTKUuw=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.5-concurrency-workaround/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||
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.9-concurrency-workaround h1:gFAlklid3jyXIuZBy5Vy0dhG+F6YBgosRy4syT5CDsg=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.9-concurrency-workaround/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
|
||||
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=
|
||||
|
@ -732,26 +646,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.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
|
||||
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.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
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.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
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=
|
||||
|
@ -759,21 +673,17 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.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/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -784,13 +694,13 @@ 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=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -810,8 +720,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -842,45 +752,36 @@ 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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.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=
|
||||
|
@ -907,8 +808,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=
|
||||
|
@ -916,24 +815,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -944,13 +841,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=
|
||||
|
@ -983,8 +876,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -1040,12 +933,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
|||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
|
||||
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -1058,8 +951,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.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
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=
|
||||
|
@ -1070,14 +963,11 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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=
|
||||
|
@ -1097,9 +987,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=
|
||||
|
@ -1109,16 +997,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
|
|
@ -105,6 +105,15 @@ func (iter *regularCollectionIterator) PrevItem() TypeOrIRI {
|
|||
return cur
|
||||
}
|
||||
|
||||
func (iter *regularCollectionIterator) TotalItems() int {
|
||||
totalItems := iter.GetActivityStreamsTotalItems()
|
||||
if totalItems == nil || !totalItems.IsXMLSchemaNonNegativeInteger() {
|
||||
return -1
|
||||
}
|
||||
|
||||
return totalItems.Get()
|
||||
}
|
||||
|
||||
func (iter *regularCollectionIterator) initItems() bool {
|
||||
if iter.once {
|
||||
return (iter.items != nil)
|
||||
|
@ -147,6 +156,15 @@ func (iter *orderedCollectionIterator) PrevItem() TypeOrIRI {
|
|||
return cur
|
||||
}
|
||||
|
||||
func (iter *orderedCollectionIterator) TotalItems() int {
|
||||
totalItems := iter.GetActivityStreamsTotalItems()
|
||||
if totalItems == nil || !totalItems.IsXMLSchemaNonNegativeInteger() {
|
||||
return -1
|
||||
}
|
||||
|
||||
return totalItems.Get()
|
||||
}
|
||||
|
||||
func (iter *orderedCollectionIterator) initItems() bool {
|
||||
if iter.once {
|
||||
return (iter.items != nil)
|
||||
|
@ -203,6 +221,15 @@ func (iter *regularCollectionPageIterator) PrevItem() TypeOrIRI {
|
|||
return cur
|
||||
}
|
||||
|
||||
func (iter *regularCollectionPageIterator) TotalItems() int {
|
||||
totalItems := iter.GetActivityStreamsTotalItems()
|
||||
if totalItems == nil || !totalItems.IsXMLSchemaNonNegativeInteger() {
|
||||
return -1
|
||||
}
|
||||
|
||||
return totalItems.Get()
|
||||
}
|
||||
|
||||
func (iter *regularCollectionPageIterator) initItems() bool {
|
||||
if iter.once {
|
||||
return (iter.items != nil)
|
||||
|
@ -259,6 +286,15 @@ func (iter *orderedCollectionPageIterator) PrevItem() TypeOrIRI {
|
|||
return cur
|
||||
}
|
||||
|
||||
func (iter *orderedCollectionPageIterator) TotalItems() int {
|
||||
totalItems := iter.GetActivityStreamsTotalItems()
|
||||
if totalItems == nil || !totalItems.IsXMLSchemaNonNegativeInteger() {
|
||||
return -1
|
||||
}
|
||||
|
||||
return totalItems.Get()
|
||||
}
|
||||
|
||||
func (iter *orderedCollectionPageIterator) initItems() bool {
|
||||
if iter.once {
|
||||
return (iter.items != nil)
|
||||
|
|
|
@ -307,6 +307,12 @@ type CollectionIterator interface {
|
|||
|
||||
NextItem() TypeOrIRI
|
||||
PrevItem() TypeOrIRI
|
||||
|
||||
// TotalItems returns the total items
|
||||
// present in the collection, derived
|
||||
// from the totalItems property, or -1
|
||||
// if totalItems not present / readable.
|
||||
TotalItems() int
|
||||
}
|
||||
|
||||
// CollectionPageIterator represents the minimum interface for interacting with a wrapped
|
||||
|
@ -319,6 +325,12 @@ type CollectionPageIterator interface {
|
|||
|
||||
NextItem() TypeOrIRI
|
||||
PrevItem() TypeOrIRI
|
||||
|
||||
// TotalItems returns the total items
|
||||
// present in the collection, derived
|
||||
// from the totalItems property, or -1
|
||||
// if totalItems not present / readable.
|
||||
TotalItems() int
|
||||
}
|
||||
|
||||
// Flaggable represents the minimum interface for an activitystreams 'Flag' activity.
|
||||
|
|
|
@ -18,13 +18,14 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
||||
errorsv2 "codeberg.org/gruf/go-errors/v2"
|
||||
)
|
||||
|
||||
// InboxPOSTHandler deals with incoming POST requests to an actor's inbox.
|
||||
|
@ -32,18 +33,18 @@ import (
|
|||
func (m *Module) InboxPOSTHandler(c *gin.Context) {
|
||||
_, err := m.processor.Fedi().InboxPost(c.Request.Context(), c.Writer, c.Request)
|
||||
if err != nil {
|
||||
errWithCode := new(gtserror.WithCode)
|
||||
errWithCode := errorsv2.AsV2[gtserror.WithCode](err)
|
||||
|
||||
if !errors.As(err, errWithCode) {
|
||||
if errWithCode == nil {
|
||||
// Something else went wrong, and someone forgot to return
|
||||
// an errWithCode! It's chill though. Log the error but don't
|
||||
// return it as-is to the caller, to avoid leaking internals.
|
||||
log.Errorf(c.Request.Context(), "returning Bad Request to caller, err was: %q", err)
|
||||
*errWithCode = gtserror.NewErrorBadRequest(err)
|
||||
errWithCode = gtserror.NewErrorBadRequest(err)
|
||||
}
|
||||
|
||||
// Pass along confirmed error with code to the main error handler
|
||||
apiutil.ErrorHandler(c, *errWithCode, m.processor.InstanceGetV1)
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -49,7 +49,7 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) {
|
|||
|
||||
form := &tokenRequestForm{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), err.Error()))
|
||||
apiutil.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.ErrInvalidRequest, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if len(help) != 0 {
|
||||
apiutil.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), help...))
|
||||
apiutil.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.ErrInvalidRequest, help...))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -26,15 +26,18 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/client/apps"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/blocks"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/bookmarks"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/conversations"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/customemojis"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/favourites"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/featuredtags"
|
||||
filtersV1 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v1"
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/lists"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/markers"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/mutes"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/notifications"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/polls"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/preferences"
|
||||
|
@ -48,6 +51,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/middleware"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/router"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
@ -59,15 +63,18 @@ type Client struct {
|
|||
apps *apps.Module // api/v1/apps
|
||||
blocks *blocks.Module // api/v1/blocks
|
||||
bookmarks *bookmarks.Module // api/v1/bookmarks
|
||||
conversations *conversations.Module // api/v1/conversations
|
||||
customEmojis *customemojis.Module // api/v1/custom_emojis
|
||||
favourites *favourites.Module // api/v1/favourites
|
||||
featuredTags *featuredtags.Module // api/v1/featured_tags
|
||||
filtersV1 *filtersV1.Module // api/v1/filters
|
||||
filtersV2 *filtersV2.Module // api/v2/filters
|
||||
followRequests *followrequests.Module // api/v1/follow_requests
|
||||
instance *instance.Module // api/v1/instance
|
||||
lists *lists.Module // api/v1/lists
|
||||
markers *markers.Module // api/v1/markers
|
||||
media *media.Module // api/v1/media, api/v2/media
|
||||
mutes *mutes.Module // api/v1/mutes
|
||||
notifications *notifications.Module // api/v1/notifications
|
||||
polls *polls.Module // api/v1/polls
|
||||
preferences *preferences.Module // api/v1/preferences
|
||||
|
@ -101,15 +108,18 @@ func (c *Client) Route(r *router.Router, m ...gin.HandlerFunc) {
|
|||
c.apps.Route(h)
|
||||
c.blocks.Route(h)
|
||||
c.bookmarks.Route(h)
|
||||
c.conversations.Route(h)
|
||||
c.customEmojis.Route(h)
|
||||
c.favourites.Route(h)
|
||||
c.featuredTags.Route(h)
|
||||
c.filtersV1.Route(h)
|
||||
c.filtersV2.Route(h)
|
||||
c.followRequests.Route(h)
|
||||
c.instance.Route(h)
|
||||
c.lists.Route(h)
|
||||
c.markers.Route(h)
|
||||
c.media.Route(h)
|
||||
c.mutes.Route(h)
|
||||
c.notifications.Route(h)
|
||||
c.polls.Route(h)
|
||||
c.preferences.Route(h)
|
||||
|
@ -121,25 +131,28 @@ func (c *Client) Route(r *router.Router, m ...gin.HandlerFunc) {
|
|||
c.user.Route(h)
|
||||
}
|
||||
|
||||
func NewClient(db db.DB, p *processing.Processor) *Client {
|
||||
func NewClient(state *state.State, p *processing.Processor) *Client {
|
||||
return &Client{
|
||||
processor: p,
|
||||
db: db,
|
||||
db: state.DB,
|
||||
|
||||
accounts: accounts.New(p),
|
||||
admin: admin.New(p),
|
||||
admin: admin.New(state, p),
|
||||
apps: apps.New(p),
|
||||
blocks: blocks.New(p),
|
||||
bookmarks: bookmarks.New(p),
|
||||
conversations: conversations.New(p),
|
||||
customEmojis: customemojis.New(p),
|
||||
favourites: favourites.New(p),
|
||||
featuredTags: featuredtags.New(p),
|
||||
filtersV1: filtersV1.New(p),
|
||||
filtersV2: filtersV2.New(p),
|
||||
followRequests: followrequests.New(p),
|
||||
instance: instance.New(p),
|
||||
lists: lists.New(p),
|
||||
markers: markers.New(p),
|
||||
media: media.New(p),
|
||||
mutes: mutes.New(p),
|
||||
notifications: notifications.New(p),
|
||||
polls: polls.New(p),
|
||||
preferences: preferences.New(p),
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
|
@ -67,6 +66,11 @@ import (
|
|||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '422':
|
||||
// description: >-
|
||||
// Unprocessable. Your account creation request cannot be processed
|
||||
// because either too many accounts have been created on this instance
|
||||
// in the last 24h, or the pending account backlog is full.
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
||||
|
@ -87,7 +91,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeCreateAccount(form); err != nil {
|
||||
if err := validate.CreateAccount(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
@ -101,7 +105,25 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
}
|
||||
form.IP = signUpIP
|
||||
|
||||
ti, errWithCode := m.processor.Account().Create(c.Request.Context(), authed.Token, authed.Application, form)
|
||||
// Create the new account + user.
|
||||
ctx := c.Request.Context()
|
||||
user, errWithCode := m.processor.Account().Create(
|
||||
ctx,
|
||||
authed.Application,
|
||||
form,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
// Get a token for the new user.
|
||||
ti, errWithCode := m.processor.Account().TokenForNewUser(
|
||||
ctx,
|
||||
authed.Token,
|
||||
authed.Application,
|
||||
user,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@ -109,40 +131,3 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
|
||||
apiutil.JSON(c, http.StatusOK, ti)
|
||||
}
|
||||
|
||||
// validateNormalizeCreateAccount checks through all the necessary prerequisites for creating a new account,
|
||||
// according to the provided account create request. If the account isn't eligible, an error will be returned.
|
||||
// Side effect: normalizes the provided language tag for the user's locale.
|
||||
func validateNormalizeCreateAccount(form *apimodel.AccountCreateRequest) error {
|
||||
if form == nil {
|
||||
return errors.New("form was nil")
|
||||
}
|
||||
|
||||
if !config.GetAccountsRegistrationOpen() {
|
||||
return errors.New("registration is not open for this server")
|
||||
}
|
||||
|
||||
if err := validate.Username(form.Username); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.Email(form.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.Password(form.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !form.Agreement {
|
||||
return errors.New("agreement to terms and conditions not given")
|
||||
}
|
||||
|
||||
locale, err := validate.Language(form.Locale)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form.Locale = locale
|
||||
|
||||
return validate.SignUpReason(form.Reason, config.GetAccountsReasonRequired())
|
||||
}
|
||||
|
|
|
@ -56,6 +56,11 @@ const (
|
|||
MovePath = BasePath + "/move"
|
||||
AliasPath = BasePath + "/alias"
|
||||
ThemesPath = BasePath + "/themes"
|
||||
|
||||
// ProfileBasePath for the profile API, an extension of the account update API with a different path.
|
||||
ProfileBasePath = "/v1/profile"
|
||||
AvatarPath = ProfileBasePath + "/avatar"
|
||||
HeaderPath = ProfileBasePath + "/header"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
|
@ -84,6 +89,10 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
// modify account
|
||||
attachHandler(http.MethodPatch, UpdatePath, m.AccountUpdateCredentialsPATCHHandler)
|
||||
|
||||
// modify account profile media
|
||||
attachHandler(http.MethodDelete, AvatarPath, m.AccountAvatarDELETEHandler)
|
||||
attachHandler(http.MethodDelete, HeaderPath, m.AccountHeaderDELETEHandler)
|
||||
|
||||
// get account's statuses
|
||||
attachHandler(http.MethodGet, StatusesPath, m.AccountStatusesGETHandler)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
// 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 accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// AccountAvatarDELETEHandler swagger:operation DELETE /api/v1/profile/avatar accountAvatarDelete
|
||||
//
|
||||
// Delete the authenticated account's avatar.
|
||||
// If the account doesn't have an avatar, the call succeeds anyway.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - accounts
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The updated account, including profile source information.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/account"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountAvatarDELETEHandler(c *gin.Context) {
|
||||
m.accountDeleteProfileAttachment(c, m.processor.Media().DeleteAvatar)
|
||||
}
|
||||
|
||||
// AccountHeaderDELETEHandler swagger:operation DELETE /api/v1/profile/header accountHeaderDelete
|
||||
//
|
||||
// Delete the authenticated account's header.
|
||||
// If the account doesn't have a header, the call succeeds anyway.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - accounts
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The updated account, including profile source information.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/account"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountHeaderDELETEHandler(c *gin.Context) {
|
||||
m.accountDeleteProfileAttachment(c, m.processor.Media().DeleteHeader)
|
||||
}
|
||||
|
||||
// accountDeleteProfileAttachment checks that an authenticated account is present and allowed to alter itself,
|
||||
// runs an attachment deletion processor method, and returns the updated account.
|
||||
func (m *Module) accountDeleteProfileAttachment(c *gin.Context, processDelete func(context.Context, *gtsmodel.Account) (*apimodel.Account, gtserror.WithCode)) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
acctSensitive, errWithCode := processDelete(c, authed.Account)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, acctSensitive)
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
// 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 accounts_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type AccountProfileTestSuite struct {
|
||||
AccountStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *AccountProfileTestSuite) deleteProfileAttachment(
|
||||
testAccountFixtureName string,
|
||||
profileSubpath string,
|
||||
handler func(*gin.Context),
|
||||
expectedHTTPStatus int,
|
||||
) (*apimodel.Account, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts[testAccountFixtureName])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens[testAccountFixtureName]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers[testAccountFixtureName])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodDelete, config.GetProtocol()+"://"+config.GetHost()+"/api"+accounts.ProfileBasePath+"/"+profileSubpath, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
// trigger the handler
|
||||
handler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check code
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
return nil, fmt.Errorf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
resp := &apimodel.Account{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Delete the avatar of a user that has an avatar. Should succeed.
|
||||
func (suite *AccountProfileTestSuite) TestDeleteAvatar() {
|
||||
account, err := suite.deleteProfileAttachment(
|
||||
"local_account_1",
|
||||
"avatar",
|
||||
suite.accountsModule.AccountAvatarDELETEHandler,
|
||||
http.StatusOK,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
// An empty URL is legal *only* in the test environment, which may have no default avatars.
|
||||
suite.True(account.Avatar == "" || strings.HasPrefix(account.Avatar, "http://localhost:8080/assets/default_avatars/"))
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the avatar of a user that doesn't have an avatar. Should succeed.
|
||||
func (suite *AccountProfileTestSuite) TestDeleteNonexistentAvatar() {
|
||||
account, err := suite.deleteProfileAttachment(
|
||||
"admin_account",
|
||||
"avatar",
|
||||
suite.accountsModule.AccountAvatarDELETEHandler,
|
||||
http.StatusOK,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
// An empty URL is legal *only* in the test environment, which may have no default avatars.
|
||||
suite.True(account.Avatar == "" || strings.HasPrefix(account.Avatar, "http://localhost:8080/assets/default_avatars/"))
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the header of a user that has a header. Should succeed.
|
||||
func (suite *AccountProfileTestSuite) TestDeleteHeader() {
|
||||
account, err := suite.deleteProfileAttachment(
|
||||
"local_account_2",
|
||||
"header",
|
||||
suite.accountsModule.AccountHeaderDELETEHandler,
|
||||
http.StatusOK,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal("http://localhost:8080/assets/default_header.png", account.Header)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the header of a user that doesn't have a header. Should succeed.
|
||||
func (suite *AccountProfileTestSuite) TestDeleteNonexistentHeader() {
|
||||
account, err := suite.deleteProfileAttachment(
|
||||
"admin_account",
|
||||
"header",
|
||||
suite.accountsModule.AccountHeaderDELETEHandler,
|
||||
http.StatusOK,
|
||||
)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal("http://localhost:8080/assets/default_header.png", account.Header)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountProfileTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(AccountProfileTestSuite))
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// AccountApprovePOSTHandler swagger:operation POST /api/v1/admin/accounts/{id}/approve adminAccountApprove
|
||||
//
|
||||
// Approve pending account.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// required: true
|
||||
// in: path
|
||||
// description: ID of the account.
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The now-approved account.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/adminAccountInfo"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountApprovePOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
targetAcctID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
account, errWithCode := m.processor.Admin().AccountApprove(
|
||||
c.Request.Context(),
|
||||
authed.Account,
|
||||
targetAcctID,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, account)
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// AccountGETHandler swagger:operation GET /api/v1/admin/accounts/{id} adminAccountGet
|
||||
//
|
||||
// View one account.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// required: true
|
||||
// in: path
|
||||
// description: ID of the account.
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: OK
|
||||
// schema:
|
||||
// "$ref": "#/definitions/adminAccountInfo"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
targetAcctID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
account, errWithCode := m.processor.Admin().AccountGet(c.Request.Context(), targetAcctID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, account)
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// AccountRejectPOSTHandler swagger:operation POST /api/v1/admin/accounts/{id}/reject adminAccountReject
|
||||
//
|
||||
// Reject pending account.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// required: true
|
||||
// in: path
|
||||
// description: ID of the account.
|
||||
// type: string
|
||||
// -
|
||||
// name: private_comment
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Comment to leave on why the account was denied.
|
||||
// The comment will be visible to admins only.
|
||||
// type: string
|
||||
// -
|
||||
// name: message
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Message to include in email to applicant.
|
||||
// Will be included only if send_email is true.
|
||||
// type: string
|
||||
// -
|
||||
// name: send_email
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Send an email to the applicant informing
|
||||
// them that their sign-up has been rejected.
|
||||
// type: boolean
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The now-rejected account.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/adminAccountInfo"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountRejectPOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
targetAcctID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := new(apimodel.AdminAccountRejectRequest)
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
account, errWithCode := m.processor.Admin().AccountReject(
|
||||
c.Request.Context(),
|
||||
authed.Account,
|
||||
targetAcctID,
|
||||
form.PrivateComment,
|
||||
form.SendEmail,
|
||||
form.Message,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, account)
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
// 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/>.
|
||||
|
||||
// AccountsGETHandlerV1 swagger:operation GET /api/v1/admin/accounts adminAccountsGetV1
|
||||
//
|
||||
// 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=example.org%2F%40someone>; rel="next", <https://example.org/api/v1/admin/accounts?limit=80&min_id=example.org%2F%40someone_else>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: local
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for local accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: remote
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for remote accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: active
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for currently active accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: pending
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for currently pending accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: disabled
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for currently disabled accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: silenced
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for currently silenced accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: suspended
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for currently suspended accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: sensitized
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for accounts force-marked as sensitive.
|
||||
// default: false
|
||||
// -
|
||||
// name: username
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Search for the given username.
|
||||
// -
|
||||
// name: display_name
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Search for the given display name.
|
||||
// -
|
||||
// name: by_domain
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Filter by the given domain.
|
||||
// -
|
||||
// name: email
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Lookup a user with this email.
|
||||
// -
|
||||
// name: ip
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Lookup users with this IP address.
|
||||
// -
|
||||
// name: staff
|
||||
// in: query
|
||||
// type: boolean
|
||||
// description: Filter for staff accounts.
|
||||
// default: false
|
||||
// -
|
||||
// name: max_id
|
||||
// in: query
|
||||
// type: string
|
||||
// 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: >-
|
||||
// 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: 50
|
||||
// maximum: 200
|
||||
// minimum: 1
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// headers:
|
||||
// Link:
|
||||
// type: string
|
||||
// description: Links to the next and previous queries.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/adminAccountInfo"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
func (m *Module) AccountsGETV1Handler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
page, errWithCode := paging.ParseIDPage(c, 1, 200, 50)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
/* Translate to v2 `origin` query param */
|
||||
|
||||
local, errWithCode := apiutil.ParseLocal(c.Query(apiutil.LocalKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
remote, errWithCode := apiutil.ParseAdminRemote(c.Query(apiutil.AdminRemoteKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if local && remote {
|
||||
keys := []string{apiutil.LocalKey, apiutil.AdminRemoteKey}
|
||||
err := fmt.Errorf("only one of %+v can be true", keys)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
var origin string
|
||||
if local {
|
||||
origin = "local"
|
||||
} else if remote {
|
||||
origin = "remote"
|
||||
}
|
||||
|
||||
/* Translate to v2 `status` query param */
|
||||
|
||||
active, errWithCode := apiutil.ParseAdminActive(c.Query(apiutil.AdminActiveKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
pending, errWithCode := apiutil.ParseAdminPending(c.Query(apiutil.AdminPendingKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
disabled, errWithCode := apiutil.ParseAdminDisabled(c.Query(apiutil.AdminDisabledKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
silenced, errWithCode := apiutil.ParseAdminSilenced(c.Query(apiutil.AdminSilencedKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
suspended, errWithCode := apiutil.ParseAdminSuspended(c.Query(apiutil.AdminSuspendedKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure only one `status` query param set.
|
||||
var status string
|
||||
states := map[string]bool{
|
||||
apiutil.AdminActiveKey: active,
|
||||
apiutil.AdminPendingKey: pending,
|
||||
apiutil.AdminDisabledKey: disabled,
|
||||
apiutil.AdminSilencedKey: silenced,
|
||||
apiutil.AdminSuspendedKey: suspended,
|
||||
}
|
||||
for k, v := range states {
|
||||
if !v {
|
||||
// False status,
|
||||
// so irrelevant.
|
||||
continue
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
// Status was already set by another
|
||||
// query param, this is an error.
|
||||
keys := []string{
|
||||
apiutil.AdminActiveKey,
|
||||
apiutil.AdminPendingKey,
|
||||
apiutil.AdminDisabledKey,
|
||||
apiutil.AdminSilencedKey,
|
||||
apiutil.AdminSuspendedKey,
|
||||
}
|
||||
err := fmt.Errorf("only one of %+v can be true", keys)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
// Use this
|
||||
// account status.
|
||||
status = k
|
||||
}
|
||||
|
||||
/* Translate to v2 `permissions` query param */
|
||||
|
||||
staff, errWithCode := apiutil.ParseAdminStaff(c.Query(apiutil.AdminStaffKey), false)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
var permissions string
|
||||
if staff {
|
||||
permissions = "staff"
|
||||
}
|
||||
|
||||
// Parse out all optional params from the query.
|
||||
params := &apimodel.AdminGetAccountsRequest{
|
||||
Origin: origin,
|
||||
Status: status,
|
||||
Permissions: permissions,
|
||||
RoleIDs: nil, // Can't do in V1.
|
||||
InvitedBy: "", // Can't do in V1.
|
||||
Username: c.Query(apiutil.UsernameKey),
|
||||
DisplayName: c.Query(apiutil.AdminDisplayNameKey),
|
||||
ByDomain: c.Query(apiutil.AdminByDomainKey),
|
||||
Email: c.Query(apiutil.AdminEmailKey),
|
||||
IP: c.Query(apiutil.AdminIPKey),
|
||||
APIVersion: 1,
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Admin().AccountsGet(
|
||||
c.Request.Context(),
|
||||
params,
|
||||
page,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.LinkHeader != "" {
|
||||
c.Header("Link", resp.LinkHeader)
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, resp.Items)
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
// 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/>.
|
||||
|
||||
// AccountsGETHandlerV2 swagger:operation GET /api/v2/admin/accounts adminAccountsGetV2
|
||||
//
|
||||
// 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=example.org%2F%40someone>; rel="next", <https://example.org/api/v2/admin/accounts?limit=80&min_id=example.org%2F%40someone_else>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: origin
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Filter for `local` or `remote` accounts.
|
||||
// -
|
||||
// name: status
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Filter for `active`, `pending`, `disabled`, `silenced`, or `suspended` accounts.
|
||||
// -
|
||||
// name: permissions
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Filter for accounts with staff permissions (users that can manage reports).
|
||||
// -
|
||||
// name: role_ids[]
|
||||
// in: query
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// description: Filter for users with these roles.
|
||||
// -
|
||||
// name: invited_by
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Lookup users invited by the account with this ID.
|
||||
// -
|
||||
// name: username
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Search for the given username.
|
||||
// -
|
||||
// name: display_name
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Search for the given display name.
|
||||
// -
|
||||
// name: by_domain
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Filter by the given domain.
|
||||
// -
|
||||
// name: email
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Lookup a user with this email.
|
||||
// -
|
||||
// name: ip
|
||||
// in: query
|
||||
// type: string
|
||||
// description: Lookup users with this IP address.
|
||||
// -
|
||||
// name: max_id
|
||||
// in: query
|
||||
// type: string
|
||||
// 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: >-
|
||||
// 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: 50
|
||||
// maximum: 200
|
||||
// minimum: 1
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// headers:
|
||||
// Link:
|
||||
// type: string
|
||||
// description: Links to the next and previous queries.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/adminAccountInfo"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
func (m *Module) AccountsGETV2Handler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
page, errWithCode := paging.ParseIDPage(c, 1, 200, 50)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse out all optional params from the query.
|
||||
params := &apimodel.AdminGetAccountsRequest{
|
||||
Origin: c.Query(apiutil.AdminOriginKey),
|
||||
Status: c.Query(apiutil.AdminStatusKey),
|
||||
Permissions: c.Query(apiutil.AdminPermissionsKey),
|
||||
RoleIDs: c.QueryArray(apiutil.AdminRoleIDsKey),
|
||||
InvitedBy: c.Query(apiutil.AdminInvitedByKey),
|
||||
Username: c.Query(apiutil.UsernameKey),
|
||||
DisplayName: c.Query(apiutil.AdminDisplayNameKey),
|
||||
ByDomain: c.Query(apiutil.AdminByDomainKey),
|
||||
Email: c.Query(apiutil.AdminEmailKey),
|
||||
IP: c.Query(apiutil.AdminIPKey),
|
||||
APIVersion: 2,
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Admin().AccountsGet(
|
||||
c.Request.Context(),
|
||||
params,
|
||||
page,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.LinkHeader != "" {
|
||||
c.Header("Link", resp.LinkHeader)
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, resp.Items)
|
||||
}
|
|
@ -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{})
|
||||
}
|
|
@ -23,6 +23,7 @@ import (
|
|||
"codeberg.org/gruf/go-debug"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -39,9 +40,12 @@ const (
|
|||
HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + IDKey
|
||||
HeaderBlocksPath = BasePath + "/header_blocks"
|
||||
HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + IDKey
|
||||
AccountsPath = BasePath + "/accounts"
|
||||
AccountsPathWithID = AccountsPath + "/:" + IDKey
|
||||
AccountsV1Path = BasePath + "/accounts"
|
||||
AccountsV2Path = "/v2/admin/accounts"
|
||||
AccountsPathWithID = AccountsV1Path + "/:" + IDKey
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
AccountsApprovePath = AccountsPathWithID + "/approve"
|
||||
AccountsRejectPath = AccountsPathWithID + "/reject"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
ReportsPath = BasePath + "/reports"
|
||||
|
@ -53,6 +57,7 @@ const (
|
|||
InstanceRulesPathWithID = InstanceRulesPath + "/:" + IDKey
|
||||
DebugPath = BasePath + "/debug"
|
||||
DebugAPUrlPath = DebugPath + "/apurl"
|
||||
DebugClearCachesPath = DebugPath + "/caches/clear"
|
||||
|
||||
IDKey = "id"
|
||||
FilterQueryKey = "filter"
|
||||
|
@ -70,11 +75,13 @@ const (
|
|||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
state *state.State
|
||||
}
|
||||
|
||||
func New(processor *processing.Processor) *Module {
|
||||
func New(state *state.State, processor *processing.Processor) *Module {
|
||||
return &Module{
|
||||
processor: processor,
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +120,12 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
attachHandler(http.MethodPost, DomainKeysExpirePath, m.DomainKeysExpirePOSTHandler)
|
||||
|
||||
// accounts stuff
|
||||
attachHandler(http.MethodGet, AccountsV1Path, m.AccountsGETV1Handler)
|
||||
attachHandler(http.MethodGet, AccountsV2Path, m.AccountsGETV2Handler)
|
||||
attachHandler(http.MethodGet, AccountsPathWithID, m.AccountGETHandler)
|
||||
attachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler)
|
||||
attachHandler(http.MethodPost, AccountsApprovePath, m.AccountApprovePOSTHandler)
|
||||
attachHandler(http.MethodPost, AccountsRejectPath, m.AccountRejectPOSTHandler)
|
||||
|
||||
// media stuff
|
||||
attachHandler(http.MethodPost, MediaCleanupPath, m.MediaCleanupPOSTHandler)
|
||||
|
@ -137,5 +149,6 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
// debug stuff
|
||||
if debug.DEBUG {
|
||||
attachHandler(http.MethodGet, DebugAPUrlPath, m.DebugAPUrlHandler)
|
||||
attachHandler(http.MethodPost, DebugClearCachesPath, m.DebugClearCachesHandler)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ func (suite *AdminStandardTestSuite) SetupTest() {
|
|||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.adminModule = admin.New(suite.processor)
|
||||
suite.adminModule = admin.New(&suite.state, suite.processor)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
}
|
||||
|
|
|
@ -73,3 +73,35 @@ import (
|
|||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) DebugAPUrlHandler(c *gin.Context) {}
|
||||
|
||||
// DebugClearCachesHandler swagger:operation POST /api/v1/admin/debug/caches/clear debugClearCaches
|
||||
//
|
||||
// Sweep/clear all in-memory caches.
|
||||
//
|
||||
// Only enabled / exposed if GoToSocial was built and is running with flag DEBUG=1.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - debug
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: All good baby!
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) DebugClearCachesHandler(c *gin.Context) {}
|
||||
|
|
|
@ -56,3 +56,27 @@ func (m *Module) DebugAPUrlHandler(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (m *Module) DebugClearCachesHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
// Sweep all caches down to 0 (empty).
|
||||
m.state.Caches.Sweep(0)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "OK"})
|
||||
}
|
||||
|
|
|
@ -54,6 +54,12 @@ import (
|
|||
// in: formData
|
||||
// description: The email address that the test email should be sent to.
|
||||
// type: string
|
||||
// required: true
|
||||
// -
|
||||
// name: message
|
||||
// in: formData
|
||||
// description: Optional message to include in the email.
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
|
@ -115,7 +121,12 @@ func (m *Module) EmailTestPOSTHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
errWithCode := m.processor.Admin().EmailTest(c.Request.Context(), authed.Account, email.Address)
|
||||
errWithCode := m.processor.Admin().EmailTest(
|
||||
c.Request.Context(),
|
||||
authed.Account,
|
||||
email.Address,
|
||||
form.Message,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
@ -52,7 +52,7 @@ func (m *Module) getHeaderFilter(c *gin.Context, get func(context.Context, strin
|
|||
return
|
||||
}
|
||||
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param("ID"))
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@ -167,7 +167,7 @@ func (m *Module) deleteHeaderFilter(c *gin.Context, delete func(context.Context,
|
|||
return
|
||||
}
|
||||
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param("ID"))
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
@ -92,5 +92,5 @@ func (m *Module) HeaderFilterAllowDELETE(c *gin.Context) {
|
|||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) HeaderFilterBlockDELETE(c *gin.Context) {
|
||||
m.deleteHeaderFilter(c, m.processor.Admin().DeleteAllowHeaderFilter)
|
||||
m.deleteHeaderFilter(c, m.processor.Admin().DeleteBlockHeaderFilter)
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"domain": null,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"email": "tortle.dude@example.org",
|
||||
"ip": "118.44.18.196",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
|
@ -249,7 +249,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"domain": null,
|
||||
"created_at": "2022-05-17T13:10:59.000Z",
|
||||
"email": "admin@example.org",
|
||||
"ip": "89.122.255.1",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
|
@ -295,7 +295,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"domain": null,
|
||||
"created_at": "2022-05-17T13:10:59.000Z",
|
||||
"email": "admin@example.org",
|
||||
"ip": "89.122.255.1",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
|
@ -354,7 +354,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() {
|
|||
"domain": null,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"email": "tortle.dude@example.org",
|
||||
"ip": "118.44.18.196",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
|
@ -576,7 +576,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() {
|
|||
"domain": null,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"email": "tortle.dude@example.org",
|
||||
"ip": "118.44.18.196",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
|
@ -798,7 +798,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() {
|
|||
"domain": null,
|
||||
"created_at": "2022-06-04T13:12:00.000Z",
|
||||
"email": "tortle.dude@example.org",
|
||||
"ip": "118.44.18.196",
|
||||
"ip": null,
|
||||
"ips": [],
|
||||
"locale": "en",
|
||||
"invite_request": null,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// 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 conversations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
)
|
||||
|
||||
const (
|
||||
// BasePath is the base URI path for serving
|
||||
// conversations, minus the api prefix.
|
||||
BasePath = "/v1/conversations"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
}
|
||||
|
||||
func New(processor *processing.Processor) *Module {
|
||||
return &Module{
|
||||
processor: processor,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
|
||||
attachHandler(http.MethodGet, BasePath, m.ConversationsGETHandler)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// 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 conversations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// ConversationsGETHandler swagger:operation GET /api/v1/conversations conversationsGet
|
||||
//
|
||||
// Get an array of (direct message) conversations that requesting account is involved in.
|
||||
//
|
||||
// NOT IMPLEMENTED YET: Will currently always return an array of length 0.
|
||||
//
|
||||
// The next and previous queries can be parsed from the returned Link header.
|
||||
// Example:
|
||||
//
|
||||
// ```
|
||||
// <https://example.org/api/v1/conversations?limit=80&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/conversations?limit=80&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - conversations
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: max_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Return only conversations *OLDER* than the given max ID.
|
||||
// The conversation with the specified ID will not be included in the response.
|
||||
// NOTE: the ID is of the internal conversation, use the Link header for pagination.
|
||||
// in: query
|
||||
// required: false
|
||||
// -
|
||||
// name: since_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Return only conversations *NEWER* than the given since ID.
|
||||
// The conversation with the specified ID will not be included in the response.
|
||||
// NOTE: the ID is of the internal conversation, use the Link header for pagination.
|
||||
// in: query
|
||||
// -
|
||||
// name: min_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Return only conversations *IMMEDIATELY NEWER* than the given min ID.
|
||||
// The conversation with the specified ID will not be included in the response.
|
||||
// NOTE: the ID is of the internal conversation, use the Link header for pagination.
|
||||
// in: query
|
||||
// required: false
|
||||
// -
|
||||
// name: limit
|
||||
// type: integer
|
||||
// description: Number of conversations to return.
|
||||
// default: 40
|
||||
// minimum: 1
|
||||
// maximum: 80
|
||||
// in: query
|
||||
// required: false
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:statuses
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// headers:
|
||||
// Link:
|
||||
// type: string
|
||||
// description: Links to the next and previous queries.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/conversation"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) ConversationsGETHandler(c *gin.Context) {
|
||||
if _, err := oauth.Authed(c, true, true, true, true); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONArray)
|
||||
}
|
|
@ -41,7 +41,7 @@ import (
|
|||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the list
|
||||
// description: ID of the filter
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
|
|
|
@ -66,6 +66,9 @@ func (suite *FiltersTestSuite) deleteFilter(
|
|||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
|
|
|
@ -68,6 +68,9 @@ func (suite *FiltersTestSuite) getFilter(
|
|||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
|
|
|
@ -52,6 +52,7 @@ import (
|
|||
// The text to be filtered.
|
||||
//
|
||||
// Sample: fnord
|
||||
// minLength: 1
|
||||
// maxLength: 40
|
||||
// type: string
|
||||
// -
|
||||
|
@ -120,6 +121,8 @@ import (
|
|||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate keyword)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
|
|
|
@ -94,6 +94,9 @@ func (suite *FiltersTestSuite) postFilter(
|
|||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
|
@ -226,14 +229,3 @@ func (suite *FiltersTestSuite) TestPostFilterTitleConflict() {
|
|||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// FUTURE: this should be removed once we support server-side filters.
|
||||
func (suite *FiltersTestSuite) TestPostFilterIrreversibleNotSupported() {
|
||||
phrase := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
irreversible := true
|
||||
_, err := suite.postFilter(&phrase, &context, &irreversible, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ import (
|
|||
// The text to be filtered.
|
||||
//
|
||||
// Sample: fnord
|
||||
// minLength: 1
|
||||
// maxLength: 40
|
||||
// type: string
|
||||
// -
|
||||
|
@ -126,6 +127,8 @@ import (
|
|||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate keyword)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
|
|
|
@ -97,6 +97,9 @@ func (suite *FiltersTestSuite) putFilter(
|
|||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
|
@ -238,16 +241,6 @@ func (suite *FiltersTestSuite) TestPutFilterTitleConflict() {
|
|||
}
|
||||
}
|
||||
|
||||
// FUTURE: this should be removed once we support server-side filters.
|
||||
func (suite *FiltersTestSuite) TestPutFilterIrreversibleNotSupported() {
|
||||
id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
irreversible := true
|
||||
_, err := suite.putFilter(id, nil, nil, &irreversible, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() {
|
||||
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
|
||||
phrase := "GNU/Linux"
|
||||
|
|
|
@ -64,6 +64,9 @@ func (suite *FiltersTestSuite) getFilters(
|
|||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
|
|
|
@ -31,6 +31,10 @@ func validateNormalizeCreateUpdateFilter(form *model.FilterCreateUpdateRequestV1
|
|||
if err := validate.FilterKeyword(form.Phrase); err != nil {
|
||||
return err
|
||||
}
|
||||
// For filter v1 forwards compatibility, the phrase is used as the title of a v2 filter, so it must pass that as well.
|
||||
if err := validate.FilterTitle(form.Phrase); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.FilterContexts(form.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
)
|
||||
|
||||
const (
|
||||
// BasePath is the base path for serving the filters API, minus the 'api' prefix
|
||||
BasePath = "/v2/filters"
|
||||
// BasePathWithID is the base path with the ID key in it, for operations on an existing filter.
|
||||
BasePathWithID = BasePath + "/:" + apiutil.IDKey
|
||||
// FilterKeywordsPathWithID is the path for operations on an existing filter's keywords.
|
||||
FilterKeywordsPathWithID = BasePathWithID + "/keywords"
|
||||
// FilterStatusesPathWithID is the path for operations on an existing filter's statuses.
|
||||
FilterStatusesPathWithID = BasePathWithID + "/statuses"
|
||||
|
||||
// KeywordPath is the base path for operations on filter keywords that don't require a filter ID.
|
||||
KeywordPath = BasePath + "/keywords"
|
||||
// KeywordPathWithKeywordID is the path for operations on an existing filter keyword.
|
||||
KeywordPathWithKeywordID = KeywordPath + "/:" + apiutil.IDKey
|
||||
|
||||
// StatusPath is the base path for operations on filter statuses that don't require a filter ID.
|
||||
StatusPath = BasePath + "/statuses"
|
||||
// StatusPathWithStatusID is the path for operations on an existing filter status.
|
||||
StatusPathWithStatusID = StatusPath + "/:" + apiutil.IDKey
|
||||
)
|
||||
|
||||
// Module implements APIs for client-side aka "v1" filtering.
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
}
|
||||
|
||||
func New(processor *processing.Processor) *Module {
|
||||
return &Module{
|
||||
processor: processor,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
|
||||
attachHandler(http.MethodGet, BasePath, m.FiltersGETHandler)
|
||||
|
||||
attachHandler(http.MethodPost, BasePath, m.FilterPOSTHandler)
|
||||
attachHandler(http.MethodGet, BasePathWithID, m.FilterGETHandler)
|
||||
attachHandler(http.MethodPut, BasePathWithID, m.FilterPUTHandler)
|
||||
attachHandler(http.MethodDelete, BasePathWithID, m.FilterDELETEHandler)
|
||||
|
||||
attachHandler(http.MethodGet, FilterKeywordsPathWithID, m.FilterKeywordsGETHandler)
|
||||
attachHandler(http.MethodPost, FilterKeywordsPathWithID, m.FilterKeywordPOSTHandler)
|
||||
|
||||
attachHandler(http.MethodGet, KeywordPathWithKeywordID, m.FilterKeywordGETHandler)
|
||||
attachHandler(http.MethodPut, KeywordPathWithKeywordID, m.FilterKeywordPUTHandler)
|
||||
attachHandler(http.MethodDelete, KeywordPathWithKeywordID, m.FilterKeywordDELETEHandler)
|
||||
|
||||
attachHandler(http.MethodGet, FilterStatusesPathWithID, m.FilterStatusesGETHandler)
|
||||
attachHandler(http.MethodPost, FilterStatusesPathWithID, m.FilterStatusPOSTHandler)
|
||||
|
||||
attachHandler(http.MethodGet, StatusPathWithStatusID, m.FilterStatusGETHandler)
|
||||
attachHandler(http.MethodDelete, StatusPathWithStatusID, m.FilterStatusDELETEHandler)
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type FiltersTestSuite struct {
|
||||
suite.Suite
|
||||
db db.DB
|
||||
storage *storage.Driver
|
||||
mediaManager *media.Manager
|
||||
federator *federation.Federator
|
||||
processor *processing.Processor
|
||||
emailSender email.Sender
|
||||
sentEmails map[string]string
|
||||
state state.State
|
||||
|
||||
// standard suite models
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
testClients map[string]*gtsmodel.Client
|
||||
testApplications map[string]*gtsmodel.Application
|
||||
testUsers map[string]*gtsmodel.User
|
||||
testAccounts map[string]*gtsmodel.Account
|
||||
testStatuses map[string]*gtsmodel.Status
|
||||
testFilters map[string]*gtsmodel.Filter
|
||||
testFilterKeywords map[string]*gtsmodel.FilterKeyword
|
||||
testFilterStatuses map[string]*gtsmodel.FilterStatus
|
||||
|
||||
// module being tested
|
||||
filtersModule *filtersV2.Module
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) SetupSuite() {
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
suite.testApplications = testrig.NewTestApplications()
|
||||
suite.testUsers = testrig.NewTestUsers()
|
||||
suite.testAccounts = testrig.NewTestAccounts()
|
||||
suite.testStatuses = testrig.NewTestStatuses()
|
||||
suite.testFilters = testrig.NewTestFilters()
|
||||
suite.testFilterKeywords = testrig.NewTestFilterKeywords()
|
||||
suite.testFilterStatuses = testrig.NewTestFilterStatuses()
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
testrig.InitTestConfig()
|
||||
config.Config(func(cfg *config.Configuration) {
|
||||
cfg.WebAssetBaseDir = "../../../../../web/assets/"
|
||||
cfg.WebTemplateBaseDir = "../../../../../web/templates/"
|
||||
})
|
||||
testrig.InitTestLog()
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
suite.state.Storage = suite.storage
|
||||
|
||||
testrig.StartTimelines(
|
||||
&suite.state,
|
||||
visibility.NewFilter(&suite.state),
|
||||
typeutils.NewConverter(&suite.state),
|
||||
)
|
||||
|
||||
suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
|
||||
suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../../testrig/media")), suite.mediaManager)
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../../../web/template/", suite.sentEmails)
|
||||
suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
|
||||
suite.filtersModule = filtersV2.New(suite.processor)
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../../testrig/media")
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StandardStorageTeardown(suite.storage)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
func TestFiltersTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(FiltersTestSuite))
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterDELETEHandler swagger:operation DELETE /api/v2/filters/{id} filterV2Delete
|
||||
//
|
||||
// Delete a single filter with the given ID.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the filter
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - write:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: filter deleted
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterDELETEHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
errWithCode = m.processor.FiltersV2().Delete(c.Request.Context(), authed.Account, id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiutil.EmptyJSONObject)
|
||||
}
|
|
@ -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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) deleteFilter(
|
||||
filterID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) error {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodDelete, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterDELETEHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
resp := &struct{}{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteFilter() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
|
||||
err := suite.deleteFilter(id, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilter() {
|
||||
id := suite.testFilters["local_account_2_filter_1"].ID
|
||||
|
||||
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteNonexistentFilter() {
|
||||
id := "not_even_a_real_ULID"
|
||||
|
||||
err := suite.deleteFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterGETHandler swagger:operation GET /api/v2/filters/{id} filterV2Get
|
||||
//
|
||||
// Get a single filter with the given ID.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the filter
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filter
|
||||
// description: Requested filter.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterV2"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().Get(c.Request.Context(), authed.Account, id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiFilter)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) getFilter(
|
||||
filterID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.FilterV2, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterV2{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetFilter() {
|
||||
expectedFilter := suite.testFilters["local_account_1_filter_1"]
|
||||
|
||||
filter, err := suite.getFilter(expectedFilter.ID, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.NotEmpty(filter)
|
||||
suite.Equal(expectedFilter.Action, typeutils.APIFilterActionToFilterAction(filter.FilterAction))
|
||||
suite.Equal(expectedFilter.ID, filter.ID)
|
||||
suite.Equal(expectedFilter.Title, filter.Title)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilter() {
|
||||
id := suite.testFilters["local_account_2_filter_1"].ID
|
||||
|
||||
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetNonexistentFilter() {
|
||||
id := "not_even_a_real_ULID"
|
||||
|
||||
_, err := suite.getFilter(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an empty filter with no keywords or statuses serializes the keywords and statuses arrays as empty arrays,
|
||||
// not as null values or entirely omitted fields.
|
||||
func (suite *FiltersTestSuite) TestGetEmptyFilter() {
|
||||
id := suite.testFilters["local_account_1_filter_4"].ID
|
||||
|
||||
_, err := suite.getFilter(id, http.StatusOK, `{"id":"01HZ55WWWP82WYP2A1BKWK8Y9Q","title":"empty filter with no keywords or statuses","context":["home","public"],"expires_at":null,"filter_action":"warn","keywords":[],"statuses":[]}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
func (m *Module) FilterKeywordDELETEHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
errWithCode = m.processor.FiltersV2().KeywordDelete(c.Request.Context(), authed.Account, id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiutil.EmptyJSONObject)
|
||||
}
|
|
@ -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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) deleteFilterKeyword(
|
||||
filterKeywordID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) error {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodDelete, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.KeywordPath+"/"+filterKeywordID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterKeywordID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterKeywordDELETEHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
resp := &struct{}{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteFilterKeyword() {
|
||||
id := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
|
||||
err := suite.deleteFilterKeyword(id, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilterKeyword() {
|
||||
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
|
||||
|
||||
err := suite.deleteFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteNonexistentFilterKeyword() {
|
||||
id := "not_even_a_real_ULID"
|
||||
|
||||
err := suite.deleteFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterKeywordGETHandler swagger:operation GET /api/v2/filters/keywords/{id} filterKeywordGet
|
||||
//
|
||||
// Get a single filter keyword with the given ID.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the filter keyword
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterKeyword
|
||||
// description: Requested filter keyword.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterKeyword"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterKeywordGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().KeywordGet(c.Request.Context(), authed.Account, id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiFilter)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) getFilterKeyword(
|
||||
filterKeywordID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.FilterKeyword, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.KeywordPath+"/"+filterKeywordID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterKeywordID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterKeywordGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterKeyword{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetFilterKeyword() {
|
||||
expectedFilterKeyword := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"]
|
||||
|
||||
filterKeyword, err := suite.getFilterKeyword(expectedFilterKeyword.ID, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.NotEmpty(filterKeyword)
|
||||
suite.Equal(expectedFilterKeyword.ID, filterKeyword.ID)
|
||||
suite.Equal(expectedFilterKeyword.Keyword, filterKeyword.Keyword)
|
||||
suite.Equal(util.PtrValueOr(expectedFilterKeyword.WholeWord, false), filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterKeyword() {
|
||||
id := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
|
||||
|
||||
_, err := suite.getFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetNonexistentFilterKeyword() {
|
||||
id := "not_even_a_real_ULID"
|
||||
|
||||
_, err := suite.getFilterKeyword(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
// FilterKeywordPOSTHandler swagger:operation POST /api/v2/filters/{id}/keywords filterKeywordPost
|
||||
//
|
||||
// Add a filter keyword to an existing filter.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/xml
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// in: path
|
||||
// type: string
|
||||
// required: true
|
||||
// description: ID of the filter to add the filtered status to.
|
||||
// -
|
||||
// name: keyword
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The text to be filtered
|
||||
//
|
||||
// Sample: fnord
|
||||
// type: string
|
||||
// minLength: 1
|
||||
// maxLength: 40
|
||||
// -
|
||||
// name: whole_word
|
||||
// in: formData
|
||||
// description: |-
|
||||
// Should the filter consider word boundaries?
|
||||
//
|
||||
// Sample: true
|
||||
// type: boolean
|
||||
// default: false
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - write:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterKeyword
|
||||
// description: New filter keyword.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterKeyword"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate keyword)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterKeywordPOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.FilterKeywordCreateUpdateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeCreateUpdateFilterKeyword(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().KeywordCreate(c.Request.Context(), authed.Account, filterID, form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiFilter)
|
||||
}
|
||||
|
||||
func validateNormalizeCreateUpdateFilterKeyword(form *apimodel.FilterKeywordCreateUpdateRequest) error {
|
||||
if err := validate.FilterKeyword(form.Keyword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
form.WholeWord = util.Ptr(util.PtrValueOr(form.WholeWord, false))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) postFilterKeyword(
|
||||
filterID string,
|
||||
keyword *string,
|
||||
wholeWord *bool,
|
||||
requestJson *string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.FilterKeyword, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID+"/keywords", nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if keyword != nil {
|
||||
ctx.Request.Form["keyword"] = []string{*keyword}
|
||||
}
|
||||
if wholeWord != nil {
|
||||
ctx.Request.Form["whole_word"] = []string{strconv.FormatBool(*wholeWord)}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterKeywordPOSTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterKeyword{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordFull() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
keyword := "fnords"
|
||||
wholeWord := true
|
||||
filterKeyword, err := suite.postFilterKeyword(filterID, &keyword, &wholeWord, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(keyword, filterKeyword.Keyword)
|
||||
suite.Equal(wholeWord, filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordFullJSON() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
requestJson := `{
|
||||
"keyword": "fnords",
|
||||
"whole_word": true
|
||||
}`
|
||||
filterKeyword, err := suite.postFilterKeyword(filterID, nil, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal("fnords", filterKeyword.Keyword)
|
||||
suite.True(filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordMinimal() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
keyword := "fnords"
|
||||
filterKeyword, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(keyword, filterKeyword.Keyword)
|
||||
suite.False(filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordEmptyKeyword() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
keyword := ""
|
||||
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter keyword must be provided, and must be no more than 40 chars"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordMissingKeyword() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
_, err := suite.postFilterKeyword(filterID, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter keyword must be provided, and must be no more than 40 chars"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Creating another filter keyword in the same filter with the same keyword should fail.
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordKeywordConflict() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
keyword := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].Keyword
|
||||
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusConflict, `{"error":"Conflict: duplicate keyword"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordAnotherAccountsFilter() {
|
||||
filterID := suite.testFilters["local_account_2_filter_1"].ID
|
||||
keyword := "fnords"
|
||||
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterKeywordNonexistentFilter() {
|
||||
filterID := "not_even_a_real_ULID"
|
||||
keyword := "fnords"
|
||||
_, err := suite.postFilterKeyword(filterID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterKeywordPUTHandler swagger:operation PUT /api/v2/filters/keywords{id} filterKeywordPut
|
||||
//
|
||||
// Update a single filter keyword with the given ID.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/xml
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// in: path
|
||||
// type: string
|
||||
// required: true
|
||||
// description: ID of the filter keyword to update.
|
||||
// -
|
||||
// name: keyword
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The text to be filtered
|
||||
//
|
||||
// Sample: fnord
|
||||
// type: string
|
||||
// minLength: 1
|
||||
// maxLength: 40
|
||||
// -
|
||||
// name: whole_word
|
||||
// in: formData
|
||||
// description: |-
|
||||
// Should the filter consider word boundaries?
|
||||
//
|
||||
// Sample: true
|
||||
// type: boolean
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - write:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterKeyword
|
||||
// description: Updated filter keyword.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterKeyword"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate keyword)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterKeywordPUTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.FilterKeywordCreateUpdateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeCreateUpdateFilterKeyword(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().KeywordUpdate(c.Request.Context(), authed.Account, id, form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiFilter)
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) putFilterKeyword(
|
||||
filterKeywordID string,
|
||||
keyword *string,
|
||||
wholeWord *bool,
|
||||
requestJson *string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.FilterKeyword, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPut, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.KeywordPath+"/"+filterKeywordID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if keyword != nil {
|
||||
ctx.Request.Form["keyword"] = []string{*keyword}
|
||||
}
|
||||
if wholeWord != nil {
|
||||
ctx.Request.Form["whole_word"] = []string{strconv.FormatBool(*wholeWord)}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AddParam("id", filterKeywordID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterKeywordPUTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterKeyword{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordFull() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
keyword := "fnords"
|
||||
wholeWord := true
|
||||
filterKeyword, err := suite.putFilterKeyword(filterKeywordID, &keyword, &wholeWord, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(keyword, filterKeyword.Keyword)
|
||||
suite.Equal(wholeWord, filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordFullJSON() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
requestJson := `{
|
||||
"keyword": "fnords",
|
||||
"whole_word": true
|
||||
}`
|
||||
filterKeyword, err := suite.putFilterKeyword(filterKeywordID, nil, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal("fnords", filterKeyword.Keyword)
|
||||
suite.True(filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordMinimal() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
keyword := "fnords"
|
||||
filterKeyword, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(keyword, filterKeyword.Keyword)
|
||||
suite.False(filterKeyword.WholeWord)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordEmptyKeyword() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
keyword := ""
|
||||
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter keyword must be provided, and must be no more than 40 chars"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordMissingKeyword() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_1_filter_1_keyword_1"].ID
|
||||
_, err := suite.putFilterKeyword(filterKeywordID, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter keyword must be provided, and must be no more than 40 chars"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Changing our filter keyword to the same keyword as another filter keyword in the same filter should fail.
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordKeywordConflict() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_1_filter_2_keyword_1"].ID
|
||||
conflictingKeyword := suite.testFilterKeywords["local_account_1_filter_2_keyword_2"].Keyword
|
||||
_, err := suite.putFilterKeyword(filterKeywordID, &conflictingKeyword, nil, nil, http.StatusConflict, `{"error":"Conflict: duplicate keyword"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordAnotherAccountsFilterKeyword() {
|
||||
filterKeywordID := suite.testFilterKeywords["local_account_2_filter_1_keyword_1"].ID
|
||||
keyword := "fnord"
|
||||
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterKeywordNonexistentFilterKeyword() {
|
||||
filterKeywordID := "not_even_a_real_ULID"
|
||||
keyword := "fnord"
|
||||
_, err := suite.putFilterKeyword(filterKeywordID, &keyword, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterKeywordsGETHandler swagger:operation GET /api/v2/filters/{id}/keywords filterKeywordsGet
|
||||
//
|
||||
// Get all filter keywords for a given filter.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the filter
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterKeywords
|
||||
// description: Requested filter keywords.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/filterKeyword"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterKeywordsGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().KeywordsGetForFilterID(c.Request.Context(), authed.Account, filterID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiFilter)
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) getFilterKeywords(
|
||||
filterID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) ([]*apimodel.FilterKeyword, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID+"/keywords", nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterKeywordsGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := make([]*apimodel.FilterKeyword, 0)
|
||||
if err := json.Unmarshal(b, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetFilterKeywords() {
|
||||
// Collect the sets of filter keyword IDs we expect to see.
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
expectedFilterKeywordIDs := []string{}
|
||||
for _, filterKeyword := range suite.testFilterKeywords {
|
||||
if filterKeyword.FilterID == filterID {
|
||||
expectedFilterKeywordIDs = append(expectedFilterKeywordIDs, filterKeyword.ID)
|
||||
}
|
||||
}
|
||||
suite.NotEmpty(expectedFilterKeywordIDs)
|
||||
|
||||
// Fetch all filter keywords for the test filter.
|
||||
filterKeywords, err := suite.getFilterKeywords(filterID, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotEmpty(filterKeywords)
|
||||
|
||||
// Check that we got the right ones.
|
||||
suite.Len(filterKeywords, len(expectedFilterKeywordIDs))
|
||||
actualFilterKeywordIDs := []string{}
|
||||
for _, filterKeyword := range filterKeywords {
|
||||
actualFilterKeywordIDs = append(actualFilterKeywordIDs, filterKeyword.ID)
|
||||
}
|
||||
suite.ElementsMatch(expectedFilterKeywordIDs, actualFilterKeywordIDs)
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
// FilterPOSTHandler swagger:operation POST /api/v2/filters filterV2Post
|
||||
//
|
||||
// Create a single filter.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/xml
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: title
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The name of the filter.
|
||||
//
|
||||
// Sample: illuminati nonsense
|
||||
// type: string
|
||||
// minLength: 1
|
||||
// maxLength: 200
|
||||
// -
|
||||
// name: context[]
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The contexts in which the filter should be applied.
|
||||
//
|
||||
// Sample: home, public
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// type: array
|
||||
// items:
|
||||
// type:
|
||||
// string
|
||||
// collectionFormat: multi
|
||||
// minItems: 1
|
||||
// uniqueItems: true
|
||||
// -
|
||||
// name: expires_in
|
||||
// in: formData
|
||||
// description: |-
|
||||
// Number of seconds from now that the filter should expire. If omitted, filter never expires.
|
||||
//
|
||||
// Sample: 86400
|
||||
// type: number
|
||||
// -
|
||||
// name: filter_action
|
||||
// in: formData
|
||||
// description: |-
|
||||
// The action to be taken when a status matches this filter.
|
||||
//
|
||||
// Sample: warn
|
||||
// type: string
|
||||
// enum:
|
||||
// - warn
|
||||
// - hide
|
||||
// default: warn
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - write:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filter
|
||||
// description: New filter.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterV2"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate title, keyword, or status)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterPOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.FilterCreateRequestV2{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeCreateFilter(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().Create(c.Request.Context(), authed.Account, form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiFilter)
|
||||
}
|
||||
|
||||
func validateNormalizeCreateFilter(form *apimodel.FilterCreateRequestV2) error {
|
||||
if err := validate.FilterTitle(form.Title); err != nil {
|
||||
return err
|
||||
}
|
||||
action := util.PtrValueOr(form.FilterAction, apimodel.FilterActionWarn)
|
||||
if err := validate.FilterAction(action); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validate.FilterContexts(form.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply defaults for missing fields.
|
||||
form.FilterAction = util.Ptr(action)
|
||||
|
||||
// Normalize filter expiry if necessary.
|
||||
// If we parsed this as JSON, expires_in
|
||||
// may be either a float64 or a string.
|
||||
if ei := form.ExpiresInI; ei != nil {
|
||||
switch e := ei.(type) {
|
||||
case float64:
|
||||
form.ExpiresIn = util.Ptr(int(e))
|
||||
|
||||
case string:
|
||||
expiresIn, err := strconv.Atoi(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse expires_in value %s as integer: %w", e, err)
|
||||
}
|
||||
|
||||
form.ExpiresIn = &expiresIn
|
||||
|
||||
default:
|
||||
return fmt.Errorf("could not parse expires_in type %T as integer", ei)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) postFilter(title *string, context *[]string, action *string, expiresIn *int, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if title != nil {
|
||||
ctx.Request.Form["title"] = []string{*title}
|
||||
}
|
||||
if context != nil {
|
||||
ctx.Request.Form["context[]"] = *context
|
||||
}
|
||||
if action != nil {
|
||||
ctx.Request.Form["filter_action"] = []string{*action}
|
||||
}
|
||||
if expiresIn != nil {
|
||||
ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)}
|
||||
}
|
||||
}
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterPOSTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterV2{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterFull() {
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home", "public"}
|
||||
action := "warn"
|
||||
expiresIn := 86400
|
||||
filter, err := suite.postFilter(&title, &context, &action, &expiresIn, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(title, filter.Title)
|
||||
filterContext := make([]string, 0, len(filter.Context))
|
||||
for _, c := range filter.Context {
|
||||
filterContext = append(filterContext, string(c))
|
||||
}
|
||||
suite.ElementsMatch(context, filterContext)
|
||||
suite.Equal(apimodel.FilterActionWarn, filter.FilterAction)
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Empty(filter.Keywords)
|
||||
suite.Empty(filter.Statuses)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterFullJSON() {
|
||||
// Use a numeric literal with a fractional part to test the JSON-specific handling for non-integer "expires_in".
|
||||
requestJson := `{
|
||||
"title": "GNU/Linux",
|
||||
"context": ["home", "public"],
|
||||
"filter_action": "warn",
|
||||
"whole_word": true,
|
||||
"expires_in": 86400.1
|
||||
}`
|
||||
filter, err := suite.postFilter(nil, nil, nil, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal("GNU/Linux", filter.Title)
|
||||
suite.ElementsMatch(
|
||||
[]apimodel.FilterContext{
|
||||
apimodel.FilterContextHome,
|
||||
apimodel.FilterContextPublic,
|
||||
},
|
||||
filter.Context,
|
||||
)
|
||||
suite.Equal(apimodel.FilterActionWarn, filter.FilterAction)
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Empty(filter.Keywords)
|
||||
suite.Empty(filter.Statuses)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterMinimal() {
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
filter, err := suite.postFilter(&title, &context, nil, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(title, filter.Title)
|
||||
filterContext := make([]string, 0, len(filter.Context))
|
||||
for _, c := range filter.Context {
|
||||
filterContext = append(filterContext, string(c))
|
||||
}
|
||||
suite.ElementsMatch(context, filterContext)
|
||||
suite.Equal(apimodel.FilterActionWarn, filter.FilterAction)
|
||||
suite.Nil(filter.ExpiresAt)
|
||||
suite.Empty(filter.Keywords)
|
||||
suite.Empty(filter.Statuses)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterEmptyTitle() {
|
||||
title := ""
|
||||
context := []string{"home"}
|
||||
_, err := suite.postFilter(&title, &context, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterMissingTitle() {
|
||||
context := []string{"home"}
|
||||
_, err := suite.postFilter(nil, &context, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterEmptyContext() {
|
||||
title := "GNU/Linux"
|
||||
context := []string{}
|
||||
_, err := suite.postFilter(&title, &context, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterMissingContext() {
|
||||
title := "GNU/Linux"
|
||||
_, err := suite.postFilter(&title, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Creating another filter with the same title should fail.
|
||||
func (suite *FiltersTestSuite) TestPostFilterTitleConflict() {
|
||||
title := suite.testFilters["local_account_1_filter_1"].Title
|
||||
_, err := suite.postFilter(&title, nil, nil, nil, nil, http.StatusUnprocessableEntity, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
// FilterPUTHandler swagger:operation PUT /api/v2/filters/{id} filterV2Put
|
||||
//
|
||||
// Update a single filter with the given ID.
|
||||
// Note that this is actually closer to a PATCH operation:
|
||||
// only provided fields will be updated, and omitted fields will remain set to previous values.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/xml
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// in: path
|
||||
// type: string
|
||||
// required: true
|
||||
// description: ID of the filter.
|
||||
// -
|
||||
// name: title
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The name of the filter.
|
||||
//
|
||||
// Sample: illuminati nonsense
|
||||
// type: string
|
||||
// minLength: 1
|
||||
// maxLength: 200
|
||||
// -
|
||||
// name: context[]
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The contexts in which the filter should be applied.
|
||||
//
|
||||
// Sample: home, public
|
||||
// enum:
|
||||
// - home
|
||||
// - notifications
|
||||
// - public
|
||||
// - thread
|
||||
// - account
|
||||
// type: array
|
||||
// items:
|
||||
// type:
|
||||
// string
|
||||
// collectionFormat: multi
|
||||
// minItems: 1
|
||||
// uniqueItems: true
|
||||
// -
|
||||
// name: expires_in
|
||||
// in: formData
|
||||
// description: |-
|
||||
// Number of seconds from now that the filter should expire.
|
||||
//
|
||||
// Sample: 86400
|
||||
// type: number
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - write:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filter
|
||||
// description: Updated filter.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterV2"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate title, keyword, or status)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterPUTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.FilterUpdateRequestV2{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateNormalizeUpdateFilter(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().Update(c.Request.Context(), authed.Account, id, form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiFilter)
|
||||
}
|
||||
|
||||
func validateNormalizeUpdateFilter(form *apimodel.FilterUpdateRequestV2) error {
|
||||
if form.Title != nil {
|
||||
if err := validate.FilterTitle(*form.Title); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if form.FilterAction != nil {
|
||||
if err := validate.FilterAction(*form.FilterAction); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if form.Context != nil {
|
||||
if err := validate.FilterContexts(*form.Context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize filter expiry if necessary.
|
||||
// If we parsed this as JSON, expires_in
|
||||
// may be either a float64 or a string.
|
||||
if ei := form.ExpiresInI; ei != nil {
|
||||
switch e := ei.(type) {
|
||||
case float64:
|
||||
form.ExpiresIn = util.Ptr(int(e))
|
||||
|
||||
case string:
|
||||
expiresIn, err := strconv.Atoi(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse expires_in value %s as integer: %w", e, err)
|
||||
}
|
||||
|
||||
form.ExpiresIn = &expiresIn
|
||||
|
||||
default:
|
||||
return fmt.Errorf("could not parse expires_in type %T as integer", ei)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) putFilter(filterID string, title *string, context *[]string, action *string, expiresIn *int, requestJson *string, expectedHTTPStatus int, expectedBody string) (*apimodel.FilterV2, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPut, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if title != nil {
|
||||
ctx.Request.Form["title"] = []string{*title}
|
||||
}
|
||||
if context != nil {
|
||||
ctx.Request.Form["context[]"] = *context
|
||||
}
|
||||
if action != nil {
|
||||
ctx.Request.Form["filter_action"] = []string{*action}
|
||||
}
|
||||
if expiresIn != nil {
|
||||
ctx.Request.Form["expires_in"] = []string{strconv.Itoa(*expiresIn)}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterPUTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterV2{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterFull() {
|
||||
id := suite.testFilters["local_account_1_filter_2"].ID
|
||||
title := "messy synoptic varblabbles"
|
||||
context := []string{"home", "public"}
|
||||
action := "hide"
|
||||
expiresIn := 86400
|
||||
filter, err := suite.putFilter(id, &title, &context, &action, &expiresIn, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(title, filter.Title)
|
||||
filterContext := make([]string, 0, len(filter.Context))
|
||||
for _, c := range filter.Context {
|
||||
filterContext = append(filterContext, string(c))
|
||||
}
|
||||
suite.ElementsMatch(context, filterContext)
|
||||
suite.Equal(apimodel.FilterActionHide, filter.FilterAction)
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Len(filter.Keywords, 3)
|
||||
suite.Len(filter.Statuses, 0)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterFullJSON() {
|
||||
id := suite.testFilters["local_account_1_filter_2"].ID
|
||||
// Use a numeric literal with a fractional part to test the JSON-specific handling for non-integer "expires_in".
|
||||
requestJson := `{
|
||||
"title": "messy synoptic varblabbles",
|
||||
"context": ["home", "public"],
|
||||
"filter_action": "hide",
|
||||
"expires_in": 86400.1
|
||||
}`
|
||||
filter, err := suite.putFilter(id, nil, nil, nil, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal("messy synoptic varblabbles", filter.Title)
|
||||
suite.ElementsMatch(
|
||||
[]apimodel.FilterContext{
|
||||
apimodel.FilterContextHome,
|
||||
apimodel.FilterContextPublic,
|
||||
},
|
||||
filter.Context,
|
||||
)
|
||||
suite.Equal(apimodel.FilterActionHide, filter.FilterAction)
|
||||
if suite.NotNil(filter.ExpiresAt) {
|
||||
suite.NotEmpty(*filter.ExpiresAt)
|
||||
}
|
||||
suite.Len(filter.Keywords, 3)
|
||||
suite.Len(filter.Statuses, 0)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterMinimal() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
filter, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(title, filter.Title)
|
||||
filterContext := make([]string, 0, len(filter.Context))
|
||||
for _, c := range filter.Context {
|
||||
filterContext = append(filterContext, string(c))
|
||||
}
|
||||
suite.ElementsMatch(context, filterContext)
|
||||
suite.Equal(apimodel.FilterActionWarn, filter.FilterAction)
|
||||
suite.Nil(filter.ExpiresAt)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterEmptyTitle() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := ""
|
||||
context := []string{"home"}
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: filter title must be provided, and must be no more than 200 chars"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutFilterEmptyContext() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := "GNU/Linux"
|
||||
context := []string{}
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: at least one filter context is required"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Changing our title to a title used by an existing filter should fail.
|
||||
func (suite *FiltersTestSuite) TestPutFilterTitleConflict() {
|
||||
id := suite.testFilters["local_account_1_filter_1"].ID
|
||||
title := suite.testFilters["local_account_1_filter_2"].Title
|
||||
_, err := suite.putFilter(id, &title, nil, nil, nil, nil, http.StatusConflict, `{"error":"Conflict: you already have a filter with this title"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutAnotherAccountsFilter() {
|
||||
id := suite.testFilters["local_account_2_filter_1"].ID
|
||||
title := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
_, err := suite.putFilter(id, &title, &context, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPutNonexistentFilter() {
|
||||
id := "not_even_a_real_ULID"
|
||||
phrase := "GNU/Linux"
|
||||
context := []string{"home"}
|
||||
_, err := suite.putFilter(id, &phrase, &context, nil, nil, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FiltersGETHandler swagger:operation GET /api/v2/filters filtersV2Get
|
||||
//
|
||||
// Get all filters for the authenticated account.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filters
|
||||
// description: Requested filters.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/filterV2"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FiltersGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilters, errWithCode := m.processor.FiltersV2().GetAll(c.Request.Context(), authed.Account)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiFilters)
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) getFilters(
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) ([]*apimodel.FilterV2, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FiltersGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := make([]*apimodel.FilterV2, 0)
|
||||
if err := json.Unmarshal(b, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetFilters() {
|
||||
// Set of filter IDs for the test user.
|
||||
expectedFilterIDs := []string{}
|
||||
// Map of filter IDs to filter keyword and status IDs.
|
||||
expectedFilters := map[string]struct {
|
||||
keywordIDs []string
|
||||
statusIDs []string
|
||||
}{}
|
||||
|
||||
// Collect the sets of IDs we expect to see.
|
||||
accountID := suite.testAccounts["local_account_1"].ID
|
||||
for _, filter := range suite.testFilters {
|
||||
if filter.AccountID == accountID {
|
||||
expectedFilterIDs = append(expectedFilterIDs, filter.ID)
|
||||
expectedFilters[filter.ID] = struct {
|
||||
keywordIDs []string
|
||||
statusIDs []string
|
||||
}{}
|
||||
}
|
||||
}
|
||||
for _, filterKeyword := range suite.testFilterKeywords {
|
||||
if filterKeyword.AccountID == accountID {
|
||||
expectedIDsForFilter := expectedFilters[filterKeyword.FilterID]
|
||||
expectedIDsForFilter.keywordIDs = append(expectedIDsForFilter.keywordIDs, filterKeyword.ID)
|
||||
expectedFilters[filterKeyword.FilterID] = expectedIDsForFilter
|
||||
}
|
||||
}
|
||||
for _, filterStatus := range suite.testFilterStatuses {
|
||||
if filterStatus.AccountID == accountID {
|
||||
expectedIDsForFilter := expectedFilters[filterStatus.FilterID]
|
||||
expectedIDsForFilter.statusIDs = append(expectedIDsForFilter.statusIDs, filterStatus.ID)
|
||||
expectedFilters[filterStatus.FilterID] = expectedIDsForFilter
|
||||
}
|
||||
}
|
||||
suite.NotEmpty(expectedFilterIDs)
|
||||
suite.NotEmpty(expectedFilters)
|
||||
|
||||
// Fetch all filters for the logged-in account.
|
||||
filters, err := suite.getFilters(http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotEmpty(filters)
|
||||
|
||||
// Check that we got the right ones.
|
||||
suite.Len(filters, len(expectedFilters))
|
||||
|
||||
actualFilterIDs := []string{}
|
||||
for _, filter := range filters {
|
||||
actualFilterIDs = append(actualFilterIDs, filter.ID)
|
||||
|
||||
expectedIDsForFilter := expectedFilters[filter.ID]
|
||||
|
||||
actualFilterKeywordIDs := []string{}
|
||||
for _, filterKeyword := range filter.Keywords {
|
||||
actualFilterKeywordIDs = append(actualFilterKeywordIDs, filterKeyword.ID)
|
||||
}
|
||||
suite.ElementsMatch(actualFilterKeywordIDs, expectedIDsForFilter.keywordIDs)
|
||||
|
||||
actualFilterStatusIDs := []string{}
|
||||
for _, filterStatus := range filter.Statuses {
|
||||
actualFilterStatusIDs = append(actualFilterStatusIDs, filterStatus.ID)
|
||||
}
|
||||
suite.ElementsMatch(actualFilterStatusIDs, expectedIDsForFilter.statusIDs)
|
||||
}
|
||||
suite.ElementsMatch(expectedFilterIDs, actualFilterIDs)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
func (m *Module) FilterStatusDELETEHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
errWithCode = m.processor.FiltersV2().StatusDelete(c.Request.Context(), authed.Account, id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiutil.EmptyJSONObject)
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) deleteFilterStatus(
|
||||
filterStatusID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) error {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodDelete, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.StatusPath+"/"+filterStatusID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterStatusID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterDELETEHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
resp := &struct{}{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteFilterStatus() {
|
||||
id := suite.testFilterStatuses["local_account_1_filter_3_status_1"].ID
|
||||
|
||||
err := suite.deleteFilterStatus(id, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteAnotherAccountsFilterStatus() {
|
||||
id := suite.testFilterStatuses["local_account_2_filter_1_status_1"].ID
|
||||
|
||||
err := suite.deleteFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestDeleteNonexistentFilterStatus() {
|
||||
id := "not_even_a_real_ULID"
|
||||
|
||||
err := suite.deleteFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterStatusesGETHandler swagger:operation GET /api/v2/filters/{id}/statuses filterStatusesGet
|
||||
//
|
||||
// Get all filter statuses for a given filter.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the filter
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterStatuses
|
||||
// description: Requested filter statuses.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/filterStatus"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterStatusesGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().StatusesGetForFilterID(c.Request.Context(), authed.Account, filterID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiFilter)
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) getFilterStatuses(
|
||||
filterID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) ([]*apimodel.FilterStatus, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID+"/statuses", nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterStatusesGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := make([]*apimodel.FilterStatus, 0)
|
||||
if err := json.Unmarshal(b, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetFilterStatuses() {
|
||||
// Collect the sets of filter status IDs we expect to see.
|
||||
filterID := suite.testFilters["local_account_1_filter_3"].ID
|
||||
expectedFilterStatusIDs := []string{}
|
||||
for _, filterStatus := range suite.testFilterStatuses {
|
||||
if filterStatus.FilterID == filterID {
|
||||
expectedFilterStatusIDs = append(expectedFilterStatusIDs, filterStatus.ID)
|
||||
}
|
||||
}
|
||||
suite.NotEmpty(expectedFilterStatusIDs)
|
||||
|
||||
// Fetch all filter statuses for the test filter.
|
||||
filterStatuses, err := suite.getFilterStatuses(filterID, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
suite.NotEmpty(filterStatuses)
|
||||
|
||||
// Check that we got the right ones.
|
||||
suite.Len(filterStatuses, len(expectedFilterStatusIDs))
|
||||
actualFilterStatusIDs := []string{}
|
||||
for _, filterStatus := range filterStatuses {
|
||||
actualFilterStatusIDs = append(actualFilterStatusIDs, filterStatus.ID)
|
||||
}
|
||||
suite.ElementsMatch(expectedFilterStatusIDs, actualFilterStatusIDs)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// FilterStatusGETHandler swagger:operation GET /api/v2/filters/statuses/{id} filterStatusGet
|
||||
//
|
||||
// Get a single filter status with the given ID.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// type: string
|
||||
// description: ID of the filter status
|
||||
// in: path
|
||||
// required: true
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterStatus
|
||||
// description: Requested filter status.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterStatus"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterStatusGETHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().StatusGet(c.Request.Context(), authed.Account, id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apiFilter)
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) getFilterStatus(
|
||||
filterStatusID string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.FilterStatus, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.StatusPath+"/"+filterStatusID, nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
|
||||
ctx.AddParam("id", filterStatusID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterStatusGETHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterStatus{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetFilterStatus() {
|
||||
expectedFilterStatus := suite.testFilterStatuses["local_account_1_filter_3_status_1"]
|
||||
|
||||
filterStatus, err := suite.getFilterStatus(expectedFilterStatus.ID, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.NotEmpty(filterStatus)
|
||||
suite.Equal(expectedFilterStatus.ID, filterStatus.ID)
|
||||
suite.Equal(expectedFilterStatus.StatusID, filterStatus.StatusID)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetAnotherAccountsFilterStatus() {
|
||||
id := suite.testFilterStatuses["local_account_2_filter_1_status_1"].ID
|
||||
|
||||
_, err := suite.getFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestGetNonexistentFilterStatus() {
|
||||
id := "not_even_a_real_ULID"
|
||||
|
||||
_, err := suite.getFilterStatus(id, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// 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 v2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
// FilterStatusPOSTHandler swagger:operation POST /api/v2/filters/{id}/statuses filterStatusPost
|
||||
//
|
||||
// Add a filter status to an existing filter.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - filters
|
||||
//
|
||||
// consumes:
|
||||
// - application/json
|
||||
// - application/xml
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// in: path
|
||||
// type: string
|
||||
// required: true
|
||||
// description: ID of the filter to add the filtered status to.
|
||||
// -
|
||||
// name: status_id
|
||||
// in: formData
|
||||
// required: true
|
||||
// description: |-
|
||||
// The ID of the status to filter.
|
||||
//
|
||||
// Sample: 01HXA2NE0K8T1C70K90E74GYD0
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - write:filters
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: filterStatus
|
||||
// description: New filter status.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/filterStatus"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict (duplicate status)
|
||||
// '422':
|
||||
// description: unprocessable content
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) FilterStatusPOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
filterID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
form := &apimodel.FilterStatusCreateRequest{}
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateCreateFilterStatus(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiFilter, errWithCode := m.processor.FiltersV2().StatusCreate(c.Request.Context(), authed.Account, filterID, form)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, apiFilter)
|
||||
}
|
||||
|
||||
func validateCreateFilterStatus(form *apimodel.FilterStatusCreateRequest) error {
|
||||
return validate.ULID(form.StatusID, "status_id")
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
// 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 v2_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
filtersV2 "github.com/superseriousbusiness/gotosocial/internal/api/client/filters/v2"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func (suite *FiltersTestSuite) postFilterStatus(
|
||||
filterID string,
|
||||
statusID *string,
|
||||
requestJson *string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.FilterStatus, error) {
|
||||
// instantiate recorder + test context
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
|
||||
// create the request
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, config.GetProtocol()+"://"+config.GetHost()+"/api/"+filtersV2.BasePath+"/"+filterID+"/statuses", nil)
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
if requestJson != nil {
|
||||
ctx.Request.Header.Set("content-type", "application/json")
|
||||
ctx.Request.Body = io.NopCloser(strings.NewReader(*requestJson))
|
||||
} else {
|
||||
ctx.Request.Form = make(url.Values)
|
||||
if statusID != nil {
|
||||
ctx.Request.Form["status_id"] = []string{*statusID}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AddParam("id", filterID)
|
||||
|
||||
// trigger the handler
|
||||
suite.filtersModule.FilterStatusPOSTHandler(ctx)
|
||||
|
||||
// read the response
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gtserror.NewMultiError(2)
|
||||
|
||||
// check code + body
|
||||
if resultCode := recorder.Code; expectedHTTPStatus != resultCode {
|
||||
errs.Appendf("expected %d got %d", expectedHTTPStatus, resultCode)
|
||||
if expectedBody == "" {
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
}
|
||||
|
||||
// if we got an expected body, return early
|
||||
if expectedBody != "" {
|
||||
if string(b) != expectedBody {
|
||||
errs.Appendf("expected %s got %s", expectedBody, string(b))
|
||||
}
|
||||
return nil, errs.Combine()
|
||||
}
|
||||
|
||||
resp := &apimodel.FilterStatus{}
|
||||
if err := json.Unmarshal(b, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatus() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
statusID := suite.testStatuses["admin_account_status_1"].ID
|
||||
filterStatus, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(statusID, filterStatus.StatusID)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusJSON() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
requestJson := `{
|
||||
"status_id": "01F8MH75CBF9JFX4ZAD54N0W0R"
|
||||
}`
|
||||
filterStatus, err := suite.postFilterStatus(filterID, nil, &requestJson, http.StatusOK, "")
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Equal(suite.testStatuses["admin_account_status_1"].ID, filterStatus.StatusID)
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusEmptyStatusID() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
statusID := ""
|
||||
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: status_id must be provided"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusInvalidStatusID() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
statusID := "112401162517176488" // ma'am, that's clearly a Mastodon ID, this is a Wendy's
|
||||
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: status_id didn't match the expected ULID format for an ID (26 characters from the set 0123456789ABCDEFGHJKMNPQRSTVWXYZ)"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusMissingStatusID() {
|
||||
filterID := suite.testFilters["local_account_1_filter_1"].ID
|
||||
_, err := suite.postFilterStatus(filterID, nil, nil, http.StatusUnprocessableEntity, `{"error":"Unprocessable Entity: status_id must be provided"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Creating another filter status in the same filter with the same status ID should fail.
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusStatusIDConflict() {
|
||||
filterID := suite.testFilters["local_account_1_filter_3"].ID
|
||||
statusID := suite.testFilterStatuses["local_account_1_filter_3_status_1"].StatusID
|
||||
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusConflict, `{"error":"Conflict: duplicate status"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusAnotherAccountsFilter() {
|
||||
filterID := suite.testFilters["local_account_2_filter_1"].ID
|
||||
statusID := suite.testStatuses["admin_account_status_1"].ID
|
||||
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *FiltersTestSuite) TestPostFilterStatusNonexistentFilter() {
|
||||
filterID := "not_even_a_real_ULID"
|
||||
statusID := suite.testStatuses["admin_account_status_1"].ID
|
||||
_, err := suite.postFilterStatus(filterID, &statusID, nil, http.StatusNotFound, `{"error":"Not Found"}`)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -19,7 +19,6 @@ package media_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
@ -39,7 +38,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
|
@ -78,19 +76,22 @@ type MediaCreateTestSuite struct {
|
|||
TEST INFRASTRUCTURE
|
||||
*/
|
||||
|
||||
func (suite *MediaCreateTestSuite) SetupSuite() {
|
||||
suite.state.Caches.Init()
|
||||
func (suite *MediaCreateTestSuite) SetupTest() {
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
// setup standard items
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.state.Caches.Init()
|
||||
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
suite.state.Storage = suite.storage
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
||||
suite.tc = typeutils.NewConverter(&suite.state)
|
||||
|
||||
testrig.StartTimelines(
|
||||
|
@ -107,21 +108,8 @@ func (suite *MediaCreateTestSuite) SetupSuite() {
|
|||
|
||||
// setup module being tested
|
||||
suite.mediaModule = mediamodule.New(suite.processor)
|
||||
}
|
||||
|
||||
func (suite *MediaCreateTestSuite) TearDownSuite() {
|
||||
if err := suite.db.Close(); err != nil {
|
||||
log.Panicf(nil, "error closing db connection: %s", err)
|
||||
}
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
func (suite *MediaCreateTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
||||
// setup test data
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
suite.testApplications = testrig.NewTestApplications()
|
||||
|
@ -133,6 +121,7 @@ func (suite *MediaCreateTestSuite) SetupTest() {
|
|||
func (suite *MediaCreateTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StandardStorageTeardown(suite.storage)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -152,7 +141,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {
|
|||
|
||||
// see what's in storage *before* the request
|
||||
var storageKeysBeforeRequest []string
|
||||
if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
|
||||
if err := suite.storage.WalkKeys(ctx, func(key string) error {
|
||||
storageKeysBeforeRequest = append(storageKeysBeforeRequest, key)
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
@ -177,7 +166,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {
|
|||
|
||||
// check what's in storage *after* the request
|
||||
var storageKeysAfterRequest []string
|
||||
if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
|
||||
if err := suite.storage.WalkKeys(ctx, func(key string) error {
|
||||
storageKeysAfterRequest = append(storageKeysAfterRequest, key)
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
@ -237,7 +226,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() {
|
|||
|
||||
// see what's in storage *before* the request
|
||||
var storageKeysBeforeRequest []string
|
||||
if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
|
||||
if err := suite.storage.WalkKeys(ctx, func(key string) error {
|
||||
storageKeysBeforeRequest = append(storageKeysBeforeRequest, key)
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
@ -262,7 +251,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() {
|
|||
|
||||
// check what's in storage *after* the request
|
||||
var storageKeysAfterRequest []string
|
||||
if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
|
||||
if err := suite.storage.WalkKeys(ctx, func(key string) error {
|
||||
storageKeysAfterRequest = append(storageKeysAfterRequest, key)
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
|
@ -75,18 +74,22 @@ type MediaUpdateTestSuite struct {
|
|||
TEST INFRASTRUCTURE
|
||||
*/
|
||||
|
||||
func (suite *MediaUpdateTestSuite) SetupSuite() {
|
||||
func (suite *MediaUpdateTestSuite) SetupTest() {
|
||||
testrig.StartNoopWorkers(&suite.state)
|
||||
|
||||
// setup standard items
|
||||
testrig.InitTestConfig()
|
||||
testrig.InitTestLog()
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.state.Caches.Init()
|
||||
|
||||
suite.storage = testrig.NewInMemoryStorage()
|
||||
suite.state.Storage = suite.storage
|
||||
|
||||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
||||
suite.tc = typeutils.NewConverter(&suite.state)
|
||||
|
||||
testrig.StartTimelines(
|
||||
|
@ -103,21 +106,8 @@ func (suite *MediaUpdateTestSuite) SetupSuite() {
|
|||
|
||||
// setup module being tested
|
||||
suite.mediaModule = mediamodule.New(suite.processor)
|
||||
}
|
||||
|
||||
func (suite *MediaUpdateTestSuite) TearDownSuite() {
|
||||
if err := suite.db.Close(); err != nil {
|
||||
log.Panicf(nil, "error closing db connection: %s", err)
|
||||
}
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
func (suite *MediaUpdateTestSuite) SetupTest() {
|
||||
suite.state.Caches.Init()
|
||||
|
||||
testrig.StandardDBSetup(suite.db, nil)
|
||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||
|
||||
// setup test data
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testClients = testrig.NewTestClients()
|
||||
suite.testApplications = testrig.NewTestApplications()
|
||||
|
@ -129,6 +119,7 @@ func (suite *MediaUpdateTestSuite) SetupTest() {
|
|||
func (suite *MediaUpdateTestSuite) TearDownTest() {
|
||||
testrig.StandardDBTeardown(suite.db)
|
||||
testrig.StandardStorageTeardown(suite.storage)
|
||||
testrig.StopWorkers(&suite.state)
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// 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 mutes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
)
|
||||
|
||||
const (
|
||||
// BasePath is the base URI path for serving mutes, minus the api prefix.
|
||||
BasePath = "/v1/mutes"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
}
|
||||
|
||||
func New(processor *processing.Processor) *Module {
|
||||
return &Module{
|
||||
processor: processor,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {
|
||||
attachHandler(http.MethodGet, BasePath, m.MutesGETHandler)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// 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 mutes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// MutesGETHandler swagger:operation GET /api/v1/mutes mutesGet
|
||||
//
|
||||
// Get an array of accounts that requesting account has muted.
|
||||
//
|
||||
// NOT IMPLEMENTED YET: Will currently always return an array of length 0.
|
||||
//
|
||||
// The next and previous queries can be parsed from the returned Link header.
|
||||
// Example:
|
||||
//
|
||||
// ```
|
||||
// <https://example.org/api/v1/mutes?limit=80&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/mutes?limit=80&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - mutes
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: max_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Return only muted accounts *OLDER* than the given max ID.
|
||||
// The muted account with the specified ID will not be included in the response.
|
||||
// NOTE: the ID is of the internal mute, NOT any of the returned accounts.
|
||||
// in: query
|
||||
// required: false
|
||||
// -
|
||||
// name: since_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Return only muted accounts *NEWER* than the given since ID.
|
||||
// The muted account with the specified ID will not be included in the response.
|
||||
// NOTE: the ID is of the internal mute, NOT any of the returned accounts.
|
||||
// in: query
|
||||
// -
|
||||
// name: min_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Return only muted accounts *IMMEDIATELY NEWER* than the given min ID.
|
||||
// The muted account with the specified ID will not be included in the response.
|
||||
// NOTE: the ID is of the internal mute, NOT any of the returned accounts.
|
||||
// in: query
|
||||
// required: false
|
||||
// -
|
||||
// name: limit
|
||||
// type: integer
|
||||
// description: Number of muted accounts to return.
|
||||
// default: 40
|
||||
// minimum: 1
|
||||
// maximum: 80
|
||||
// in: query
|
||||
// required: false
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:mutes
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// headers:
|
||||
// Link:
|
||||
// type: string
|
||||
// description: Links to the next and previous queries.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/account"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) MutesGETHandler(c *gin.Context) {
|
||||
if _, err := oauth.Authed(c, true, true, true, true); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONArray)
|
||||
}
|
|
@ -99,6 +99,9 @@ import (
|
|||
// - `https://example.org/some/arbitrary/url` -- search for an account OR a status with the given URL. Will only ever return 1 result at most.
|
||||
// - `#[hashtag_name]` -- search for a hashtag with the given hashtag name, or starting with the given hashtag name. Case insensitive. Can return multiple results.
|
||||
// - any arbitrary string -- search for accounts or statuses containing the given string. Can return multiple results.
|
||||
//
|
||||
// Arbitrary string queries may include the following operators:
|
||||
// - `from:localuser`, `from:remoteuser@instance.tld`: restrict results to statuses created by the specified account.
|
||||
// in: query
|
||||
// required: true
|
||||
// -
|
||||
|
@ -138,6 +141,12 @@ import (
|
|||
// Currently this parameter is unused.
|
||||
// default: false
|
||||
// in: query
|
||||
// -
|
||||
// name: account_id
|
||||
// type: string
|
||||
// description: >-
|
||||
// Restrict results to statuses created by the specified account.
|
||||
// in: query
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
|
@ -238,6 +247,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
|
|||
Resolve: resolve,
|
||||
Following: following,
|
||||
ExcludeUnreviewed: excludeUnreviewed,
|
||||
AccountID: c.Query(apiutil.SearchAccountIDKey),
|
||||
APIv1: apiVersion == apiutil.APIv1,
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ func (suite *SearchGetTestSuite) getSearch(
|
|||
queryType *string,
|
||||
resolve *bool,
|
||||
following *bool,
|
||||
fromAccountID *string,
|
||||
expectedHTTPStatus int,
|
||||
expectedBody string,
|
||||
) (*apimodel.SearchResult, error) {
|
||||
|
@ -103,6 +104,10 @@ func (suite *SearchGetTestSuite) getSearch(
|
|||
queryParts = append(queryParts, apiutil.SearchFollowingKey+"="+strconv.FormatBool(*following))
|
||||
}
|
||||
|
||||
if fromAccountID != nil {
|
||||
queryParts = append(queryParts, apiutil.SearchAccountIDKey+"="+url.QueryEscape(*fromAccountID))
|
||||
}
|
||||
|
||||
requestURL.RawQuery = strings.Join(queryParts, "&")
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, requestURL.String(), nil)
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, requestingAccount)
|
||||
|
@ -174,6 +179,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByURI() {
|
|||
query = "https://unknown-instance.com/users/brand_new_person"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -191,6 +197,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByURI() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -218,6 +225,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestring() {
|
|||
query = "@brand_new_person@unknown-instance.com"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -235,6 +243,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestring() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -262,6 +271,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringUppercase()
|
|||
query = "@Some_User@example.org"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -279,6 +289,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringUppercase()
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -306,6 +317,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoLeadingAt(
|
|||
query = "brand_new_person@unknown-instance.com"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -323,6 +335,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoLeadingAt(
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -350,6 +363,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoResolve()
|
|||
query = "@brand_new_person@unknown-instance.com"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -367,6 +381,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringNoResolve()
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -389,6 +404,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars
|
|||
query = "@üser@ëxample.org"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -406,6 +422,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -431,6 +448,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars
|
|||
query = "@üser@xn--xample-ova.org"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -448,6 +466,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteAccountByNamestringSpecialChars
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -473,6 +492,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestring() {
|
|||
query = "@the_mighty_zork"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -490,6 +510,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestring() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -517,6 +538,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestringWithDomain()
|
|||
query = "@the_mighty_zork@localhost:8080"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -534,6 +556,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByNamestringWithDomain()
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -561,6 +584,7 @@ func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByNamestringRe
|
|||
query = "@somone_made_up@localhost:8080"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -578,6 +602,7 @@ func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByNamestringRe
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -600,6 +625,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByURI() {
|
|||
query = "http://localhost:8080/users/the_mighty_zork"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -617,6 +643,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByURI() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -644,6 +671,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByURL() {
|
|||
query = "http://localhost:8080/@the_mighty_zork"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -661,6 +689,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalAccountByURL() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -688,6 +717,7 @@ func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByURL() {
|
|||
query = "http://localhost:8080/@the_shmighty_shmork"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -705,6 +735,7 @@ func (suite *SearchGetTestSuite) TestSearchNonexistingLocalAccountByURL() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -727,6 +758,7 @@ func (suite *SearchGetTestSuite) TestSearchStatusByURL() {
|
|||
query = "https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042"
|
||||
queryType *string = func() *string { i := "statuses"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -744,6 +776,7 @@ func (suite *SearchGetTestSuite) TestSearchStatusByURL() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -771,6 +804,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedDomainURL() {
|
|||
query = "https://replyguys.com/@someone"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -788,6 +822,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedDomainURL() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -812,6 +847,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedDomainNamestring() {
|
|||
query = "@someone@replyguys.com"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -829,6 +865,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedDomainNamestring() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -853,6 +890,7 @@ func (suite *SearchGetTestSuite) TestSearchAAny() {
|
|||
query = "a"
|
||||
queryType *string = nil // Return anything.
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -870,6 +908,7 @@ func (suite *SearchGetTestSuite) TestSearchAAny() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -894,6 +933,7 @@ func (suite *SearchGetTestSuite) TestSearchAAnyFollowingOnly() {
|
|||
query = "a"
|
||||
queryType *string = nil // Return anything.
|
||||
following *bool = func() *bool { i := true; return &i }()
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -911,6 +951,7 @@ func (suite *SearchGetTestSuite) TestSearchAAnyFollowingOnly() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -935,6 +976,7 @@ func (suite *SearchGetTestSuite) TestSearchAStatuses() {
|
|||
query = "a"
|
||||
queryType *string = func() *string { i := "statuses"; return &i }() // Only statuses.
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -952,6 +994,7 @@ func (suite *SearchGetTestSuite) TestSearchAStatuses() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -963,6 +1006,92 @@ func (suite *SearchGetTestSuite) TestSearchAStatuses() {
|
|||
suite.Len(searchResult.Hashtags, 0)
|
||||
}
|
||||
|
||||
func (suite *SearchGetTestSuite) TestSearchHiStatusesWithAccountIDInQueryParam() {
|
||||
var (
|
||||
requestingAccount = suite.testAccounts["local_account_1"]
|
||||
token = suite.testTokens["local_account_1"]
|
||||
user = suite.testUsers["local_account_1"]
|
||||
maxID *string = nil
|
||||
minID *string = nil
|
||||
limit *int = nil
|
||||
offset *int = nil
|
||||
resolve *bool = func() *bool { i := true; return &i }()
|
||||
query = "hi"
|
||||
queryType *string = func() *string { i := "statuses"; return &i }() // Only statuses.
|
||||
following *bool = nil
|
||||
fromAccountID *string = func() *string { i := suite.testAccounts["local_account_2"].ID; return &i }()
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
||||
searchResult, err := suite.getSearch(
|
||||
requestingAccount,
|
||||
token,
|
||||
apiutil.APIv2,
|
||||
user,
|
||||
maxID,
|
||||
minID,
|
||||
limit,
|
||||
offset,
|
||||
query,
|
||||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Len(searchResult.Accounts, 0)
|
||||
suite.Len(searchResult.Statuses, 1)
|
||||
suite.Len(searchResult.Hashtags, 0)
|
||||
}
|
||||
|
||||
func (suite *SearchGetTestSuite) TestSearchHiStatusesWithAccountIDInQueryText() {
|
||||
var (
|
||||
requestingAccount = suite.testAccounts["local_account_1"]
|
||||
token = suite.testTokens["local_account_1"]
|
||||
user = suite.testUsers["local_account_1"]
|
||||
maxID *string = nil
|
||||
minID *string = nil
|
||||
limit *int = nil
|
||||
offset *int = nil
|
||||
resolve *bool = func() *bool { i := true; return &i }()
|
||||
query = "hi from:1happyturtle"
|
||||
queryType *string = func() *string { i := "statuses"; return &i }() // Only statuses.
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
||||
searchResult, err := suite.getSearch(
|
||||
requestingAccount,
|
||||
token,
|
||||
apiutil.APIv2,
|
||||
user,
|
||||
maxID,
|
||||
minID,
|
||||
limit,
|
||||
offset,
|
||||
query,
|
||||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.Len(searchResult.Accounts, 0)
|
||||
suite.Len(searchResult.Statuses, 1)
|
||||
suite.Len(searchResult.Hashtags, 0)
|
||||
}
|
||||
|
||||
func (suite *SearchGetTestSuite) TestSearchAAccounts() {
|
||||
var (
|
||||
requestingAccount = suite.testAccounts["local_account_1"]
|
||||
|
@ -976,6 +1105,7 @@ func (suite *SearchGetTestSuite) TestSearchAAccounts() {
|
|||
query = "a"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }() // Only accounts.
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -993,6 +1123,7 @@ func (suite *SearchGetTestSuite) TestSearchAAccounts() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1017,6 +1148,7 @@ func (suite *SearchGetTestSuite) TestSearchAccountsLimit1() {
|
|||
query = "a"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }() // Only accounts.
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1034,6 +1166,7 @@ func (suite *SearchGetTestSuite) TestSearchAccountsLimit1() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1058,6 +1191,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountByURI() {
|
|||
query = "http://localhost:8080/users/localhost:8080"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1075,6 +1209,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountByURI() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1107,6 +1242,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountFull() {
|
|||
query = "@" + newDomain + "@" + newDomain
|
||||
queryType *string = nil
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1124,6 +1260,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountFull() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1156,6 +1293,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountPartial() {
|
|||
query = "@" + newDomain
|
||||
queryType *string = nil
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1173,6 +1311,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountPartial() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1206,6 +1345,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountEvenMorePartial()
|
|||
query = newDomain
|
||||
queryType *string = nil
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1223,6 +1363,7 @@ func (suite *SearchGetTestSuite) TestSearchLocalInstanceAccountEvenMorePartial()
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1280,6 +1421,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteInstanceAccountPartial() {
|
|||
query = "@" + theirDomain
|
||||
queryType *string = nil
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1297,6 +1439,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteInstanceAccountPartial() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1323,6 +1466,7 @@ func (suite *SearchGetTestSuite) TestSearchBadQueryType() {
|
|||
query = "whatever"
|
||||
queryType *string = func() *string { i := "aaaaaaaaaaa"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusBadRequest
|
||||
expectedBody = `{"error":"Bad Request: search query type aaaaaaaaaaa was not recognized, valid options are ['', 'accounts', 'statuses', 'hashtags']"}`
|
||||
)
|
||||
|
@ -1340,6 +1484,7 @@ func (suite *SearchGetTestSuite) TestSearchBadQueryType() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1360,6 +1505,7 @@ func (suite *SearchGetTestSuite) TestSearchEmptyQuery() {
|
|||
query = ""
|
||||
queryType *string = func() *string { i := "aaaaaaaaaaa"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusBadRequest
|
||||
expectedBody = `{"error":"Bad Request: required key q was not set or had empty value"}`
|
||||
)
|
||||
|
@ -1377,6 +1523,7 @@ func (suite *SearchGetTestSuite) TestSearchEmptyQuery() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1397,6 +1544,7 @@ func (suite *SearchGetTestSuite) TestSearchHashtagV1() {
|
|||
query = "#welcome"
|
||||
queryType *string = func() *string { i := "hashtags"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = `{"accounts":[],"statuses":[],"hashtags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome","history":[]}]}`
|
||||
)
|
||||
|
@ -1414,6 +1562,7 @@ func (suite *SearchGetTestSuite) TestSearchHashtagV1() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1438,6 +1587,7 @@ func (suite *SearchGetTestSuite) TestSearchHashtagV2() {
|
|||
query = "#welcome"
|
||||
queryType *string = func() *string { i := "hashtags"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = `{"accounts":[],"statuses":[],"hashtags":["welcome"]}`
|
||||
)
|
||||
|
@ -1455,6 +1605,7 @@ func (suite *SearchGetTestSuite) TestSearchHashtagV2() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1479,6 +1630,7 @@ func (suite *SearchGetTestSuite) TestSearchHashtagButWithAccountSearch() {
|
|||
query = "#welcome"
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ``
|
||||
)
|
||||
|
@ -1496,6 +1648,7 @@ func (suite *SearchGetTestSuite) TestSearchHashtagButWithAccountSearch() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1520,6 +1673,7 @@ func (suite *SearchGetTestSuite) TestSearchNotHashtagButWithTypeHashtag() {
|
|||
query = "welco"
|
||||
queryType *string = func() *string { i := "hashtags"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ``
|
||||
)
|
||||
|
@ -1537,6 +1691,7 @@ func (suite *SearchGetTestSuite) TestSearchNotHashtagButWithTypeHashtag() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1562,6 +1717,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedAccountFullNamestring() {
|
|||
query = "@" + targetAccount.Username + "@" + targetAccount.Domain
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1593,6 +1749,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedAccountFullNamestring() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1624,6 +1781,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedAccountPartialNamestring() {
|
|||
query = "@" + targetAccount.Username
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1655,6 +1813,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedAccountPartialNamestring() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
@ -1683,6 +1842,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedAccountURI() {
|
|||
query = targetAccount.URI
|
||||
queryType *string = func() *string { i := "accounts"; return &i }()
|
||||
following *bool = nil
|
||||
fromAccountID *string = nil
|
||||
expectedHTTPStatus = http.StatusOK
|
||||
expectedBody = ""
|
||||
)
|
||||
|
@ -1714,6 +1874,7 @@ func (suite *SearchGetTestSuite) TestSearchBlockedAccountURI() {
|
|||
queryType,
|
||||
resolve,
|
||||
following,
|
||||
fromAccountID,
|
||||
expectedHTTPStatus,
|
||||
expectedBody)
|
||||
if err != nil {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue