mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2026-01-01 14:33:16 -06:00
Merge branch 'main' into instance-custom-css
This commit is contained in:
commit
7cd9b0eae0
947 changed files with 689490 additions and 178161 deletions
|
|
@ -27,7 +27,7 @@ import (
|
|||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
)
|
||||
|
||||
func (p *Processor) Alias(
|
||||
|
|
@ -137,8 +137,8 @@ func (p *Processor) Alias(
|
|||
// Dedupe URIs + accounts, in case someone
|
||||
// provided both an account URL and an
|
||||
// account URI above, for the same account.
|
||||
account.AlsoKnownAsURIs = util.Deduplicate(account.AlsoKnownAsURIs)
|
||||
account.AlsoKnownAs = util.DeduplicateFunc(
|
||||
account.AlsoKnownAsURIs = xslices.Deduplicate(account.AlsoKnownAsURIs)
|
||||
account.AlsoKnownAs = xslices.DeduplicateFunc(
|
||||
account.AlsoKnownAs,
|
||||
func(a *gtsmodel.Account) string {
|
||||
return a.URI
|
||||
|
|
|
|||
|
|
@ -463,9 +463,10 @@ func (p *Processor) UpdateAvatar(
|
|||
) {
|
||||
// Get maximum supported local media size.
|
||||
maxsz := config.GetMediaLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if avatar.Size > int64(maxsz) {
|
||||
if avatar.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -478,7 +479,7 @@ func (p *Processor) UpdateAvatar(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
|
||||
// Write to instance storage.
|
||||
return p.c.StoreLocalMedia(ctx,
|
||||
|
|
@ -508,9 +509,10 @@ func (p *Processor) UpdateHeader(
|
|||
) {
|
||||
// Get maximum supported local media size.
|
||||
maxsz := config.GetMediaLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if header.Size > int64(maxsz) {
|
||||
if header.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -523,7 +525,7 @@ func (p *Processor) UpdateHeader(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
|
||||
// Write to instance storage.
|
||||
return p.c.StoreLocalMedia(ctx,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ func (p *Processor) AccountAction(
|
|||
return "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
switch gtsmodel.NewAdminActionType(request.Type) {
|
||||
switch gtsmodel.ParseAdminActionType(request.Type) {
|
||||
case gtsmodel.AdminActionSuspend:
|
||||
return p.accountActionSuspend(ctx, adminAcct, targetAcct, request.Text)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,24 +31,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// apiDomainPerm is a cheeky shortcut for returning
|
||||
// the API version of the given domain permission
|
||||
// (*gtsmodel.DomainBlock or *gtsmodel.DomainAllow),
|
||||
// or an appropriate error if something goes wrong.
|
||||
func (p *Processor) apiDomainPerm(
|
||||
ctx context.Context,
|
||||
domainPermission gtsmodel.DomainPermission,
|
||||
export bool,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
apiDomainPerm, err := p.converter.DomainPermToAPIDomainPerm(ctx, domainPermission, export)
|
||||
if err != nil {
|
||||
err := gtserror.NewfAt(3, "error converting domain permission to api model: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return apiDomainPerm, nil
|
||||
}
|
||||
|
||||
// DomainPermissionCreate creates an instance-level permission
|
||||
// targeting the given domain, and then processes any side
|
||||
// effects of the permission creation.
|
||||
|
|
|
|||
324
internal/processing/admin/domainpermissiondraft.go
Normal file
324
internal/processing/admin/domainpermissiondraft.go
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftGet returns one
|
||||
// domain permission draft with the given id.
|
||||
func (p *Processor) DomainPermissionDraftGet(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permDraft == nil {
|
||||
err := fmt.Errorf("domain permission draft %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permDraft, false)
|
||||
}
|
||||
|
||||
// DomainPermissionDraftsGet returns a page of
|
||||
// DomainPermissionDrafts with the given parameters.
|
||||
func (p *Processor) DomainPermissionDraftsGet(
|
||||
ctx context.Context,
|
||||
subscriptionID string,
|
||||
domain string,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
page *paging.Page,
|
||||
) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
permDrafts, err := p.state.DB.GetDomainPermissionDrafts(
|
||||
ctx,
|
||||
permType,
|
||||
subscriptionID,
|
||||
domain,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
count := len(permDrafts)
|
||||
if count == 0 {
|
||||
return paging.EmptyResponse(), nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := permDrafts[count-1].ID
|
||||
hi := permDrafts[0].ID
|
||||
|
||||
// Convert each perm draft to API model.
|
||||
items := make([]any, len(permDrafts))
|
||||
for i, permDraft := range permDrafts {
|
||||
apiPermDraft, err := p.apiDomainPerm(ctx, permDraft, false)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
items[i] = apiPermDraft
|
||||
}
|
||||
|
||||
// Assemble next/prev page queries.
|
||||
query := make(url.Values, 3)
|
||||
if subscriptionID != "" {
|
||||
query.Set(apiutil.DomainPermissionSubscriptionIDKey, subscriptionID)
|
||||
}
|
||||
if domain != "" {
|
||||
query.Set(apiutil.DomainPermissionDomainKey, domain)
|
||||
}
|
||||
if permType != gtsmodel.DomainPermissionUnknown {
|
||||
query.Set(apiutil.DomainPermissionPermTypeKey, permType.String())
|
||||
}
|
||||
|
||||
return paging.PackageResponse(paging.ResponseParams{
|
||||
Items: items,
|
||||
Path: "/api/v1/admin/domain_permission_drafts",
|
||||
Next: page.Next(lo, hi),
|
||||
Prev: page.Prev(lo, hi),
|
||||
Query: query,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionDraftCreate(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
domain string,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
obfuscate bool,
|
||||
publicComment string,
|
||||
privateComment string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft := >smodel.DomainPermissionDraft{
|
||||
ID: id.NewULID(),
|
||||
PermissionType: permType,
|
||||
Domain: domain,
|
||||
CreatedByAccountID: acct.ID,
|
||||
CreatedByAccount: acct,
|
||||
PrivateComment: privateComment,
|
||||
PublicComment: publicComment,
|
||||
Obfuscate: &obfuscate,
|
||||
}
|
||||
|
||||
if err := p.state.DB.PutDomainPermissionDraft(ctx, permDraft); err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
const text = "a domain permission draft already exists with this permission type, domain, and subscription ID"
|
||||
err := fmt.Errorf("%w: %s", err, text)
|
||||
return nil, gtserror.NewErrorConflict(err, text)
|
||||
}
|
||||
|
||||
// Real error.
|
||||
err := gtserror.Newf("db error putting domain permission draft: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permDraft, false)
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionDraftAccept(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
id string,
|
||||
overwrite bool,
|
||||
) (*apimodel.DomainPermission, string, gtserror.WithCode) {
|
||||
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
|
||||
return nil, "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permDraft == nil {
|
||||
err := fmt.Errorf("domain permission draft %s not found", id)
|
||||
return nil, "", gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
var (
|
||||
// Existing permission
|
||||
// entry, if it exists.
|
||||
existing gtsmodel.DomainPermission
|
||||
)
|
||||
|
||||
// Try to get existing entry.
|
||||
switch permDraft.PermissionType {
|
||||
case gtsmodel.DomainPermissionBlock:
|
||||
existing, err = p.state.DB.GetDomainBlock(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
permDraft.Domain,
|
||||
)
|
||||
case gtsmodel.DomainPermissionAllow:
|
||||
existing, err = p.state.DB.GetDomainAllow(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
permDraft.Domain,
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission %s: %w", id, err)
|
||||
return nil, "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Check if we got existing entry.
|
||||
existed := !util.IsNil(existing)
|
||||
if existed && !overwrite {
|
||||
// Domain permission exists and we shouldn't
|
||||
// overwrite it, leave everything alone.
|
||||
const text = "a domain permission already exists with this permission type and domain"
|
||||
return nil, "", gtserror.NewErrorConflict(errors.New(text), text)
|
||||
}
|
||||
|
||||
// Function to clean up the accepted draft, only called if
|
||||
// creating or updating permission from draft is successful.
|
||||
deleteDraft := func() {
|
||||
if err := p.state.DB.DeleteDomainPermissionDraft(ctx, permDraft.ID); err != nil {
|
||||
log.Errorf(ctx, "db error deleting domain permission draft: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
// Easy case, we just need to create a new domain
|
||||
// permission from the draft, and then delete it.
|
||||
var (
|
||||
new *apimodel.DomainPermission
|
||||
actionID string
|
||||
errWithCode gtserror.WithCode
|
||||
)
|
||||
|
||||
if permDraft.PermissionType == gtsmodel.DomainPermissionBlock {
|
||||
new, actionID, errWithCode = p.createDomainBlock(
|
||||
ctx,
|
||||
acct,
|
||||
permDraft.Domain,
|
||||
*permDraft.Obfuscate,
|
||||
permDraft.PublicComment,
|
||||
permDraft.PrivateComment,
|
||||
permDraft.SubscriptionID,
|
||||
)
|
||||
}
|
||||
|
||||
if permDraft.PermissionType == gtsmodel.DomainPermissionAllow {
|
||||
new, actionID, errWithCode = p.createDomainAllow(
|
||||
ctx,
|
||||
acct,
|
||||
permDraft.Domain,
|
||||
*permDraft.Obfuscate,
|
||||
permDraft.PublicComment,
|
||||
permDraft.PrivateComment,
|
||||
permDraft.SubscriptionID,
|
||||
)
|
||||
}
|
||||
|
||||
// Clean up the draft
|
||||
// before returning.
|
||||
deleteDraft()
|
||||
|
||||
return new, actionID, errWithCode
|
||||
}
|
||||
|
||||
// Domain permission exists but we should overwrite
|
||||
// it by just updating the existing domain permission.
|
||||
// Domain can't change, so no need to re-run side effects.
|
||||
existing.SetCreatedByAccountID(permDraft.CreatedByAccountID)
|
||||
existing.SetCreatedByAccount(permDraft.CreatedByAccount)
|
||||
existing.SetPrivateComment(permDraft.PrivateComment)
|
||||
existing.SetPublicComment(permDraft.PublicComment)
|
||||
existing.SetObfuscate(permDraft.Obfuscate)
|
||||
existing.SetSubscriptionID(permDraft.SubscriptionID)
|
||||
|
||||
switch dp := existing.(type) {
|
||||
case *gtsmodel.DomainBlock:
|
||||
err = p.state.DB.UpdateDomainBlock(ctx, dp)
|
||||
|
||||
case *gtsmodel.DomainAllow:
|
||||
err = p.state.DB.UpdateDomainAllow(ctx, dp)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := gtserror.Newf("db error updating existing domain permission: %w", err)
|
||||
return nil, "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Clean up the draft
|
||||
// before returning.
|
||||
deleteDraft()
|
||||
|
||||
apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false)
|
||||
return apiPerm, "", errWithCode
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionDraftRemove(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
id string,
|
||||
excludeTarget bool,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permDraft == nil {
|
||||
err := fmt.Errorf("domain permission draft %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
// Delete the permission draft.
|
||||
if err := p.state.DB.DeleteDomainPermissionDraft(ctx, permDraft.ID); err != nil {
|
||||
err := gtserror.Newf("db error deleting domain permission draft: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if excludeTarget {
|
||||
// Add a domain permission exclude
|
||||
// targeting the permDraft's domain.
|
||||
_, err = p.DomainPermissionExcludeCreate(
|
||||
ctx,
|
||||
acct,
|
||||
permDraft.Domain,
|
||||
permDraft.PrivateComment,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
|
||||
err := gtserror.Newf("db error creating domain permission exclude: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permDraft, false)
|
||||
}
|
||||
159
internal/processing/admin/domainpermissionexclude.go
Normal file
159
internal/processing/admin/domainpermissionexclude.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
func (p *Processor) DomainPermissionExcludeCreate(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
domain string,
|
||||
privateComment string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permExclude := >smodel.DomainPermissionExclude{
|
||||
ID: id.NewULID(),
|
||||
Domain: domain,
|
||||
CreatedByAccountID: acct.ID,
|
||||
CreatedByAccount: acct,
|
||||
PrivateComment: privateComment,
|
||||
}
|
||||
|
||||
if err := p.state.DB.PutDomainPermissionExclude(ctx, permExclude); err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
const text = "a domain permission exclude already exists with this permission type and domain"
|
||||
err := fmt.Errorf("%w: %s", err, text)
|
||||
return nil, gtserror.NewErrorConflict(err, text)
|
||||
}
|
||||
|
||||
// Real error.
|
||||
err := gtserror.Newf("db error putting domain permission exclude: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permExclude, false)
|
||||
}
|
||||
|
||||
// DomainPermissionExcludeGet returns one
|
||||
// domain permission exclude with the given id.
|
||||
func (p *Processor) DomainPermissionExcludeGet(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permExclude, err := p.state.DB.GetDomainPermissionExcludeByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission exclude %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permExclude == nil {
|
||||
err := fmt.Errorf("domain permission exclude %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permExclude, false)
|
||||
}
|
||||
|
||||
// DomainPermissionExcludesGet returns a page of
|
||||
// DomainPermissionExcludes with the given parameters.
|
||||
func (p *Processor) DomainPermissionExcludesGet(
|
||||
ctx context.Context,
|
||||
domain string,
|
||||
page *paging.Page,
|
||||
) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
permExcludes, err := p.state.DB.GetDomainPermissionExcludes(
|
||||
ctx,
|
||||
domain,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
count := len(permExcludes)
|
||||
if count == 0 {
|
||||
return paging.EmptyResponse(), nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := permExcludes[count-1].ID
|
||||
hi := permExcludes[0].ID
|
||||
|
||||
// Convert each perm exclude to API model.
|
||||
items := make([]any, len(permExcludes))
|
||||
for i, permExclude := range permExcludes {
|
||||
apiPermExclude, err := p.apiDomainPerm(ctx, permExclude, false)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
items[i] = apiPermExclude
|
||||
}
|
||||
|
||||
// Assemble next/prev page queries.
|
||||
query := make(url.Values, 1)
|
||||
if domain != "" {
|
||||
query.Set(apiutil.DomainPermissionDomainKey, domain)
|
||||
}
|
||||
|
||||
return paging.PackageResponse(paging.ResponseParams{
|
||||
Items: items,
|
||||
Path: "/api/v1/admin/domain_permission_excludes",
|
||||
Next: page.Next(lo, hi),
|
||||
Prev: page.Prev(lo, hi),
|
||||
Query: query,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionExcludeRemove(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
id string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permExclude, err := p.state.DB.GetDomainPermissionExcludeByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission exclude %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permExclude == nil {
|
||||
err := fmt.Errorf("domain permission exclude %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
// Delete the permission exclude.
|
||||
if err := p.state.DB.DeleteDomainPermissionExclude(ctx, permExclude.ID); err != nil {
|
||||
err := gtserror.Newf("db error deleting domain permission exclude: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permExclude, false)
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ import (
|
|||
"mime/multipart"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-bytesize"
|
||||
"codeberg.org/gruf/go-iotools"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
|
|
@ -46,9 +45,10 @@ func (p *Processor) EmojiCreate(
|
|||
|
||||
// Get maximum supported local emoji size.
|
||||
maxsz := config.GetMediaEmojiLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if form.Image.Size > int64(maxsz) {
|
||||
if form.Image.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ func (p *Processor) EmojiCreate(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return rc, nil
|
||||
}
|
||||
|
|
@ -301,9 +301,10 @@ func (p *Processor) emojiUpdateCopy(
|
|||
|
||||
// Get maximum supported local emoji size.
|
||||
maxsz := config.GetMediaEmojiLocalMaxSize()
|
||||
maxszInt := int(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure target emoji image within size bounds.
|
||||
if bytesize.Size(target.ImageFileSize) > maxsz {
|
||||
if target.ImageFileSize > maxszInt {
|
||||
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -442,9 +443,10 @@ func (p *Processor) emojiUpdateModify(
|
|||
|
||||
// Get maximum supported local emoji size.
|
||||
maxsz := config.GetMediaEmojiLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if image.Size > int64(maxsz) {
|
||||
if image.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -457,7 +459,7 @@ func (p *Processor) emojiUpdateModify(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) // #nosec G115 -- Already validated.
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return rc, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
|
@ -97,3 +98,20 @@ func (p *Processor) rangeDomainAccounts(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apiDomainPerm is a cheeky shortcut for returning
|
||||
// the API version of the given domain permission,
|
||||
// or an appropriate error if something goes wrong.
|
||||
func (p *Processor) apiDomainPerm(
|
||||
ctx context.Context,
|
||||
domainPermission gtsmodel.DomainPermission,
|
||||
export bool,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
apiDomainPerm, err := p.converter.DomainPermToAPIDomainPerm(ctx, domainPermission, export)
|
||||
if err != nil {
|
||||
err := gtserror.NewfAt(3, "error converting domain permission to api model: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return apiDomainPerm, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
|
|
@ -51,11 +52,18 @@ func (p *Processor) StoreLocalMedia(
|
|||
|
||||
// Immediately trigger write to storage.
|
||||
attachment, err := processing.Load(ctx)
|
||||
if err != nil {
|
||||
const text = "error processing emoji"
|
||||
switch {
|
||||
case gtserror.LimitReached(err):
|
||||
limit := config.GetMediaLocalMaxSize()
|
||||
text := fmt.Sprintf("local media size limit reached: %s", limit)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, text)
|
||||
|
||||
case err != nil:
|
||||
const text = "error processing media"
|
||||
err := gtserror.Newf("error processing media: %w", err)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, text)
|
||||
} else if attachment.Type == gtsmodel.FileTypeUnknown {
|
||||
|
||||
case attachment.Type == gtsmodel.FileTypeUnknown:
|
||||
text := fmt.Sprintf("could not process %s type media", attachment.File.ContentType)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -86,9 +94,15 @@ func (p *Processor) StoreLocalEmoji(
|
|||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Immediately write to storage.
|
||||
// Immediately trigger write to storage.
|
||||
emoji, err := processing.Load(ctx)
|
||||
if err != nil {
|
||||
switch {
|
||||
case gtserror.LimitReached(err):
|
||||
limit := config.GetMediaEmojiLocalMaxSize()
|
||||
text := fmt.Sprintf("local emoji size limit reached: %s", limit)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, text)
|
||||
|
||||
case err != nil:
|
||||
const text = "error processing emoji"
|
||||
err := gtserror.Newf("error processing emoji %s: %w", shortcode, err)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, text)
|
||||
|
|
|
|||
|
|
@ -247,6 +247,12 @@ func (p *Processor) GetVisibleAPIStatuses(
|
|||
continue
|
||||
}
|
||||
|
||||
if apiStatus == nil {
|
||||
// Status was
|
||||
// filtered out.
|
||||
continue
|
||||
}
|
||||
|
||||
// Append converted status to return slice.
|
||||
apiStatuses = append(apiStatuses, *apiStatus)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
if *form.Irreversible {
|
||||
filter.Action = gtsmodel.FilterActionHide
|
||||
}
|
||||
if form.ExpiresIn != nil {
|
||||
if form.ExpiresIn != nil && *form.ExpiresIn != 0 {
|
||||
filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn))
|
||||
}
|
||||
for _, context := range form.Context {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func (p *Processor) Update(
|
|||
action = gtsmodel.FilterActionHide
|
||||
}
|
||||
expiresAt := time.Time{}
|
||||
if form.ExpiresIn != nil {
|
||||
if form.ExpiresIn != nil && *form.ExpiresIn != 0 {
|
||||
expiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn))
|
||||
}
|
||||
contextHome := false
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
Title: form.Title,
|
||||
Action: typeutils.APIFilterActionToFilterAction(*form.FilterAction),
|
||||
}
|
||||
if form.ExpiresIn != nil {
|
||||
if form.ExpiresIn != nil && *form.ExpiresIn != 0 {
|
||||
filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn))
|
||||
}
|
||||
for _, context := range form.Context {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
|
|
@ -30,6 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Update an existing filter for the given account, using the provided parameters.
|
||||
|
|
@ -68,10 +67,16 @@ func (p *Processor) Update(
|
|||
filterColumns = append(filterColumns, "action")
|
||||
filter.Action = typeutils.APIFilterActionToFilterAction(*form.FilterAction)
|
||||
}
|
||||
// TODO: (Vyr) is it possible to unset a filter expiration with this API?
|
||||
if form.ExpiresIn != nil {
|
||||
expiresIn := *form.ExpiresIn
|
||||
filterColumns = append(filterColumns, "expires_at")
|
||||
filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(*form.ExpiresIn))
|
||||
if expiresIn == 0 {
|
||||
// Unset the expiration date.
|
||||
filter.ExpiresAt = time.Time{}
|
||||
} else {
|
||||
// Update the expiration date.
|
||||
filter.ExpiresAt = time.Now().Add(time.Second * time.Duration(expiresIn))
|
||||
}
|
||||
}
|
||||
if form.Context != nil {
|
||||
filterColumns = append(filterColumns,
|
||||
|
|
|
|||
|
|
@ -36,9 +36,10 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
|
||||
// Get maximum supported local media size.
|
||||
maxsz := config.GetMediaLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if form.File.Size > int64(maxsz) {
|
||||
if form.File.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
|
@ -58,7 +59,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
|
||||
// Create local media and write to instance storage.
|
||||
attachment, errWithCode := p.c.StoreLocalMedia(ctx,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func (p *Processor) PreferencesGet(ctx context.Context, accountID string) (*apim
|
|||
func mastoPrefVisibility(vis gtsmodel.Visibility) string {
|
||||
switch vis {
|
||||
case gtsmodel.VisibilityPublic, gtsmodel.VisibilityDirect:
|
||||
return string(vis)
|
||||
return vis.String()
|
||||
case gtsmodel.VisibilityUnlocked:
|
||||
return "unlisted"
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package status
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
|
|
@ -402,6 +403,10 @@ func (p *Processor) WebContextGet(
|
|||
// We should mark the next **VISIBLE**
|
||||
// reply as the first reply.
|
||||
markNextVisibleAsFirstReply bool
|
||||
|
||||
// Map of statuses that didn't pass visi
|
||||
// checks and won't be shown via the web.
|
||||
hiddenStatuses = make(map[string]struct{})
|
||||
)
|
||||
|
||||
for idx, status := range wholeThread {
|
||||
|
|
@ -427,11 +432,16 @@ func (p *Processor) WebContextGet(
|
|||
}
|
||||
}
|
||||
|
||||
// Ensure status is actually
|
||||
// visible to just anyone, and
|
||||
// hide / don't include it if not.
|
||||
// Ensure status is actually visible to just
|
||||
// anyone, and hide / don't include it if not.
|
||||
//
|
||||
// Include a check to see if the parent status
|
||||
// is hidden; if so, we shouldn't show the child
|
||||
// as it leads to weird-looking threading where
|
||||
// a status seems to reply to nothing.
|
||||
_, parentHidden := hiddenStatuses[status.InReplyToID]
|
||||
v, err := p.visFilter.StatusVisible(ctx, nil, status)
|
||||
if err != nil || !v {
|
||||
if err != nil || !v || parentHidden {
|
||||
if !inReplies {
|
||||
// Main thread entry hidden.
|
||||
wCtx.ThreadHidden++
|
||||
|
|
@ -439,12 +449,15 @@ func (p *Processor) WebContextGet(
|
|||
// Reply hidden.
|
||||
wCtx.ThreadRepliesHidden++
|
||||
}
|
||||
|
||||
hiddenStatuses[status.ID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
// Prepare visible status to add to thread context.
|
||||
webStatus, err := p.converter.StatusToWebStatus(ctx, status)
|
||||
if err != nil {
|
||||
hiddenStatuses[status.ID] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -512,9 +525,17 @@ func (p *Processor) WebContextGet(
|
|||
wCtx.ThreadLength = threadLength
|
||||
}
|
||||
|
||||
// Jot down number of hidden posts so template doesn't have to do it.
|
||||
// Jot down number of "main" thread entries shown.
|
||||
wCtx.ThreadShown = wCtx.ThreadLength - wCtx.ThreadHidden
|
||||
|
||||
// If there's no posts visible in the
|
||||
// "main" thread we shouldn't show replies
|
||||
// via the web as that's just weird.
|
||||
if wCtx.ThreadShown < 1 {
|
||||
const text = "no statuses visible in main thread"
|
||||
return nil, gtserror.NewErrorNotFound(errors.New(text))
|
||||
}
|
||||
|
||||
// Mark the last "main" visible status.
|
||||
wCtx.Statuses[wCtx.ThreadShown-1].ThreadLastMain = true
|
||||
|
||||
|
|
@ -523,7 +544,7 @@ func (p *Processor) WebContextGet(
|
|||
// part of the "main" thread.
|
||||
wCtx.ThreadReplies = threadLength - wCtx.ThreadLength
|
||||
|
||||
// Jot down number of hidden replies so template doesn't have to do it.
|
||||
// Jot down number of "replies" shown.
|
||||
wCtx.ThreadRepliesShown = wCtx.ThreadReplies - wCtx.ThreadRepliesHidden
|
||||
|
||||
// Return the finished context.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
)
|
||||
|
||||
// Create processes the given form to create a new status, returning the api model representation of that status if it's OK.
|
||||
|
|
@ -78,6 +79,10 @@ func (p *Processor) Create(
|
|||
Sensitive: &form.Sensitive,
|
||||
CreatedWithApplicationID: application.ID,
|
||||
Text: form.Status,
|
||||
|
||||
// Assume not pending approval; this may
|
||||
// change when permissivity is checked.
|
||||
PendingApproval: util.Ptr(false),
|
||||
}
|
||||
|
||||
if form.Poll != nil {
|
||||
|
|
@ -367,7 +372,7 @@ func (p *Processor) processVisibility(
|
|||
|
||||
// Fall back to account default, set
|
||||
// this back on the form for later use.
|
||||
case accountDefaultVis != "":
|
||||
case accountDefaultVis != 0:
|
||||
status.Visibility = accountDefaultVis
|
||||
form.Visibility = p.converter.VisToAPIVis(ctx, accountDefaultVis)
|
||||
|
||||
|
|
@ -532,9 +537,9 @@ func (p *Processor) processContent(ctx context.Context, parseMention gtsmodel.Pa
|
|||
}
|
||||
|
||||
// Gather all the database IDs from each of the gathered status mentions, tags, and emojis.
|
||||
status.MentionIDs = util.Gather(nil, status.Mentions, func(mention *gtsmodel.Mention) string { return mention.ID })
|
||||
status.TagIDs = util.Gather(nil, status.Tags, func(tag *gtsmodel.Tag) string { return tag.ID })
|
||||
status.EmojiIDs = util.Gather(nil, status.Emojis, func(emoji *gtsmodel.Emoji) string { return emoji.ID })
|
||||
status.MentionIDs = xslices.Gather(nil, status.Mentions, func(mention *gtsmodel.Mention) string { return mention.ID })
|
||||
status.TagIDs = xslices.Gather(nil, status.Tags, func(tag *gtsmodel.Tag) string { return tag.ID })
|
||||
status.EmojiIDs = xslices.Gather(nil, status.Emojis, func(emoji *gtsmodel.Emoji) string { return emoji.ID })
|
||||
|
||||
if status.ContentWarning != "" && len(status.AttachmentIDs) > 0 {
|
||||
// If a content-warning is set, and
|
||||
|
|
|
|||
|
|
@ -76,10 +76,11 @@ func (suite *NotificationTestSuite) TestStreamNotification() {
|
|||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_static": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_description": "Flat gray background (default header).",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 3,
|
||||
"last_status_at": "2021-09-11T09:40:37.000Z",
|
||||
"last_status_at": "2021-09-11",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,10 +87,11 @@ func (suite *StatusUpdateTestSuite) TestStreamNotification() {
|
|||
"avatar_static": "",
|
||||
"header": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_static": "http://localhost:8080/assets/default_header.webp",
|
||||
"header_description": "Flat gray background (default header).",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 3,
|
||||
"last_status_at": "2021-09-11T09:40:37.000Z",
|
||||
"last_status_at": "2021-09-11",
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
|
@ -31,26 +32,21 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
func (p *Processor) NotificationsGet(
|
||||
ctx context.Context,
|
||||
authed *oauth.Auth,
|
||||
maxID string,
|
||||
sinceID string,
|
||||
minID string,
|
||||
limit int,
|
||||
types []string,
|
||||
excludeTypes []string,
|
||||
page *paging.Page,
|
||||
types []gtsmodel.NotificationType,
|
||||
excludeTypes []gtsmodel.NotificationType,
|
||||
) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
notifs, err := p.state.DB.GetAccountNotifications(
|
||||
ctx,
|
||||
authed.Account.ID,
|
||||
maxID,
|
||||
sinceID,
|
||||
minID,
|
||||
limit,
|
||||
page,
|
||||
types,
|
||||
excludeTypes,
|
||||
)
|
||||
|
|
@ -78,22 +74,15 @@ func (p *Processor) NotificationsGet(
|
|||
compiledMutes := usermute.NewCompiledUserMuteList(mutes)
|
||||
|
||||
var (
|
||||
items = make([]interface{}, 0, count)
|
||||
nextMaxIDValue string
|
||||
prevMinIDValue string
|
||||
items = make([]interface{}, 0, count)
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo = notifs[count-1].ID
|
||||
hi = notifs[0].ID
|
||||
)
|
||||
|
||||
for i, n := range notifs {
|
||||
// Set next + prev values before filtering and API
|
||||
// converting, so caller can still page properly.
|
||||
if i == count-1 {
|
||||
nextMaxIDValue = n.ID
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
prevMinIDValue = n.ID
|
||||
}
|
||||
|
||||
for _, n := range notifs {
|
||||
visible, err := p.notifVisible(ctx, n, authed.Account)
|
||||
if err != nil {
|
||||
log.Debugf(ctx, "skipping notification %s because of an error checking notification visibility: %v", n.ID, err)
|
||||
|
|
@ -115,13 +104,22 @@ func (p *Processor) NotificationsGet(
|
|||
items = append(items, item)
|
||||
}
|
||||
|
||||
return util.PackagePageableResponse(util.PageableResponseParams{
|
||||
Items: items,
|
||||
Path: "api/v1/notifications",
|
||||
NextMaxIDValue: nextMaxIDValue,
|
||||
PrevMinIDValue: prevMinIDValue,
|
||||
Limit: limit,
|
||||
})
|
||||
// Build type query string.
|
||||
query := make(url.Values)
|
||||
for _, typ := range types {
|
||||
query.Add("types[]", typ.String())
|
||||
}
|
||||
for _, typ := range excludeTypes {
|
||||
query.Add("exclude_types[]", typ.String())
|
||||
}
|
||||
|
||||
return paging.PackageResponse(paging.ResponseParams{
|
||||
Items: items,
|
||||
Path: "/api/v1/notifications",
|
||||
Next: page.Next(lo, hi),
|
||||
Prev: page.Prev(lo, hi),
|
||||
Query: query,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *Processor) NotificationGet(ctx context.Context, account *gtsmodel.Account, targetNotifID string) (*apimodel.Notification, gtserror.WithCode) {
|
||||
|
|
|
|||
|
|
@ -217,18 +217,23 @@ func (f *federate) CreatePollVote(ctx context.Context, poll *gtsmodel.Poll, vote
|
|||
return err
|
||||
}
|
||||
|
||||
// Convert vote to AS Create with vote choices as Objects.
|
||||
create, err := f.converter.PollVoteToASCreate(ctx, vote)
|
||||
// Convert vote to AS Creates with vote choices as Objects.
|
||||
creates, err := f.converter.PollVoteToASCreates(ctx, vote)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error converting to notes: %w", err)
|
||||
}
|
||||
|
||||
// Send the Create via the Actor's outbox.
|
||||
if _, err := f.FederatingActor().Send(ctx, outboxIRI, create); err != nil {
|
||||
return gtserror.Newf("error sending Create activity via outbox %s: %w", outboxIRI, err)
|
||||
var errs gtserror.MultiError
|
||||
|
||||
// Send each create activity.
|
||||
actor := f.FederatingActor()
|
||||
for _, create := range creates {
|
||||
if _, err := actor.Send(ctx, outboxIRI, create); err != nil {
|
||||
errs.Appendf("error sending Create activity via outbox %s: %w", outboxIRI, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return errs.Combine()
|
||||
}
|
||||
|
||||
func (f *federate) DeleteStatus(ctx context.Context, status *gtsmodel.Status) error {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package workers
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
|
|
@ -144,6 +145,10 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
|
|||
// ACCEPT (pending) ANNOUNCE
|
||||
case ap.ActivityAnnounce:
|
||||
return p.fediAPI.AcceptAnnounce(ctx, fMsg)
|
||||
|
||||
// ACCEPT (remote) REPLY or ANNOUNCE
|
||||
case ap.ObjectUnknown:
|
||||
return p.fediAPI.AcceptRemoteStatus(ctx, fMsg)
|
||||
}
|
||||
|
||||
// REJECT SOMETHING
|
||||
|
|
@ -823,6 +828,60 @@ func (p *fediAPI) AcceptReply(ctx context.Context, fMsg *messages.FromFediAPI) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// See if we can accept a remote
|
||||
// status we don't have stored yet.
|
||||
objectIRI, ok := fMsg.APObject.(*url.URL)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *url.URL", fMsg.APObject)
|
||||
}
|
||||
|
||||
acceptIRI := fMsg.APIRI
|
||||
if acceptIRI == nil {
|
||||
return gtserror.New("acceptIRI was nil")
|
||||
}
|
||||
|
||||
// Assume we're accepting a status; create a
|
||||
// barebones status for dereferencing purposes.
|
||||
bareStatus := >smodel.Status{
|
||||
URI: objectIRI.String(),
|
||||
ApprovedByURI: acceptIRI.String(),
|
||||
}
|
||||
|
||||
// Call RefreshStatus() to process the provided
|
||||
// barebones status and insert it into the database,
|
||||
// if indeed it's actually a status URI we can fetch.
|
||||
//
|
||||
// This will also check whether the given AcceptIRI
|
||||
// actually grants permission for this status.
|
||||
status, _, err := p.federate.RefreshStatus(ctx,
|
||||
fMsg.Receiving.Username,
|
||||
bareStatus,
|
||||
nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error processing accepted status %s: %w", bareStatus.URI, err)
|
||||
}
|
||||
|
||||
// No error means it was indeed a remote status, and the
|
||||
// given acceptIRI permitted it. Timeline and notify it.
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the interacted status;
|
||||
// uncache the prepared version from all timelines.
|
||||
if status.InReplyToID != "" {
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID)
|
||||
}
|
||||
|
||||
if status.BoostOfID != "" {
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.BoostOfID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) AcceptAnnounce(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ func getNotifyLockURI(
|
|||
) string {
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString("notification:?")
|
||||
builder.WriteString("type=" + string(notificationType))
|
||||
builder.WriteString("type=" + notificationType.String())
|
||||
builder.WriteString("&target=" + targetAccount.URI)
|
||||
builder.WriteString("&origin=" + originAccount.URI)
|
||||
if statusID != "" {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func (suite *SurfaceNotifyTestSuite) TestSpamNotifs() {
|
|||
notifs, err := testStructs.State.DB.GetAccountNotifications(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
targetAccount.ID,
|
||||
"", "", "", 0, nil, nil,
|
||||
nil, nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue