Compare commits

..

2 commits

Author SHA1 Message Date
tobi 5aa520a17c boop boop 2024-10-29 16:49:47 +01:00
dependabot[bot] c8647f701c
[chore] Bump http-proxy-middleware from 2.0.6 to 2.0.7 in /web/source (#3481)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 22:28:03 +01:00
21 changed files with 1661 additions and 802 deletions

View file

@ -1124,6 +1124,73 @@ definitions:
type: object type: object
x-go-name: DomainPermission x-go-name: DomainPermission
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
domainPermissionSubscription:
properties:
as_draft:
description: If true, domain permissions arising from this subscription will be created as drafts that must be approved by a moderator to take effect. If false, domain permissions from this subscription will come into force immediately.
example: true
type: boolean
x-go-name: AsDraft
content_type:
description: MIME content type to expect at URI.
example: text/csv
type: string
x-go-name: ContentType
count:
description: Count of domain permission entries discovered at URI.
example: 53
format: uint64
readOnly: true
type: integer
x-go-name: Count
created_by_account_id:
description: ID of the account that created this subscription.
example: 01FBW21XJA09XYX51KV5JVBW0F
readOnly: true
type: string
x-go-name: CreatedByAccountID
error:
description: If most recent fetch attempt failed, this field will contain an error message related to the fetch attempt.
example: Oopsie doopsie, we made a fucky wucky.
readOnly: true
type: string
x-go-name: Error
fetch_password:
description: (Optional) password to set for basic auth when doing a fetch of URI.
example: admin123
type: string
x-go-name: FetchPassword
fetch_username:
description: (Optional) username to set for basic auth when doing a fetch of URI.
example: admin123
type: string
x-go-name: FetchUsername
fetched_at:
description: Time at which the most recent fetch was attempted (ISO 8601 Datetime).
example: "2021-07-30T09:20:25+00:00"
readOnly: true
type: string
x-go-name: FetchedAt
id:
description: The ID of the domain permission subscription.
example: 01FBW21XJA09XYX51KV5JVBW0F
readOnly: true
type: string
x-go-name: ID
permission_type:
description: The type of domain permission subscription (allow, block).
example: block
type: string
x-go-name: PermissionType
uri:
description: URI to call in order to fetch the permissions list.
example: https://www.example.org/blocklists/list1.csv
type: string
x-go-name: URI
title: DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
type: object
x-go-name: DomainPermissionSubscription
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
emoji: emoji:
properties: properties:
category: category:
@ -5572,6 +5639,249 @@ paths:
summary: Force expiry of cached public keys for all accounts on the given domain stored in your database. summary: Force expiry of cached public keys for all accounts on the given domain stored in your database.
tags: tags:
- admin - admin
/api/v1/admin/domain_permission_drafts:
get:
description: |-
The drafts will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer).
The next and previous queries can be parsed from the returned Link header.
Example:
```
<https://example.org/api/v1/admin/domain_permission_drafts?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_drafts?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
````
operationId: domainPermissionDraftsGet
parameters:
- description: Show only drafts created by the given subscription ID.
in: query
name: subscription_id
type: string
- description: Return only drafts that target the given domain.
in: query
name: domain
type: string
- description: Filter on "block" or "allow" type drafts.
in: query
name: permission_type
type: string
- description: Return only items *OLDER* than the given max ID (for paging downwards). The item with the specified ID will not be included in the response.
in: query
name: max_id
type: string
- description: Return only items *NEWER* than the given since ID. The item with the specified ID will not be included in the response.
in: query
name: since_id
type: string
- description: Return only items immediately *NEWER* than the given min ID (for paging upwards). The item with the specified ID will not be included in the response.
in: query
name: min_id
type: string
- default: 20
description: Number of items to return.
in: query
maximum: 100
minimum: 1
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: Domain permission drafts.
headers:
Link:
description: Links to the next and previous queries.
type: string
schema:
items:
$ref: '#/definitions/domainPermission'
type: array
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"404":
description: not found
"406":
description: not acceptable
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin
summary: View domain permission drafts.
tags:
- admin
post:
consumes:
- multipart/form-data
- application/json
operationId: domainPermissionDraftCreate
parameters:
- description: Domain to create the permission draft for.
in: formData
name: domain
type: string
- description: Create a draft "allow" or a draft "block".
in: query
name: permission_type
type: string
- description: Obfuscate the name of the domain when serving it publicly. Eg., `example.org` becomes something like `ex***e.org`.
in: formData
name: obfuscate
type: boolean
- description: Public comment about this domain permission. This will be displayed alongside the domain permission if you choose to share permissions.
in: formData
name: public_comment
type: string
- description: Private comment about this domain permission. Will only be shown to other admins, so this is a useful way of internally keeping track of why a certain domain ended up permissioned.
in: formData
name: private_comment
type: string
produces:
- application/json
responses:
"200":
description: The newly created domain permission draft.
schema:
$ref: '#/definitions/domainPermission'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"406":
description: not acceptable
"409":
description: conflict
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin
summary: Create a domain permission draft with the given parameters.
tags:
- admin
/api/v1/admin/domain_permission_drafts/{id}:
get:
operationId: domainPermissionDraftGet
parameters:
- description: ID of the domain permission draft.
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: Domain permission draft.
schema:
$ref: '#/definitions/domainPermission'
"401":
description: unauthorized
"403":
description: forbidden
"404":
description: not found
"406":
description: not acceptable
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin
summary: Get domain permission draft with the given ID.
tags:
- admin
/api/v1/admin/domain_permission_drafts/{id}/accept:
post:
consumes:
- multipart/form-data
- application/json
operationId: domainPermissionDraftAccept
parameters:
- description: ID of the domain permission draft.
in: path
name: id
required: true
type: string
- default: false
description: If a domain permission already exists with the same domain and permission type as the draft, overwrite the existing permission with fields from the draft.
in: formData
name: overwrite
type: boolean
produces:
- application/json
responses:
"200":
description: The newly created domain permission.
schema:
$ref: '#/definitions/domainPermission'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"406":
description: not acceptable
"409":
description: conflict
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin
summary: Accept a domain permission draft, turning it into an enforced domain permission.
tags:
- admin
/api/v1/admin/domain_permission_drafts/{id}/remove:
post:
consumes:
- multipart/form-data
- application/json
operationId: domainPermissionDraftRemove
parameters:
- description: ID of the domain permission draft.
in: path
name: id
required: true
type: string
- default: false
description: When removing the domain permission draft, also create a domain ignore entry for the target domain, so that drafts will not be created for this domain in the future.
in: formData
name: ignore_target
type: boolean
produces:
- application/json
responses:
"200":
description: The removed domain permission draft.
schema:
$ref: '#/definitions/domainPermission'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"406":
description: not acceptable
"409":
description: conflict
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin
summary: Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
tags:
- admin
/api/v1/admin/email/test: /api/v1/admin/email/test:
post: post:
consumes: consumes:

View file

@ -36,6 +36,10 @@
DomainBlocksPathWithID = DomainBlocksPath + "/:" + apiutil.IDKey DomainBlocksPathWithID = DomainBlocksPath + "/:" + apiutil.IDKey
DomainAllowsPath = BasePath + "/domain_allows" DomainAllowsPath = BasePath + "/domain_allows"
DomainAllowsPathWithID = DomainAllowsPath + "/:" + apiutil.IDKey DomainAllowsPathWithID = DomainAllowsPath + "/:" + apiutil.IDKey
DomainPermissionDraftsPath = BasePath + "/domain_permission_drafts"
DomainPermissionDraftsPathWithID = DomainPermissionDraftsPath + "/:" + apiutil.IDKey
DomainPermissionDraftAcceptPath = DomainPermissionDraftsPathWithID + "/accept"
DomainPermissionDraftRemovePath = DomainPermissionDraftsPathWithID + "/remove"
DomainKeysExpirePath = BasePath + "/domain_keys_expire" DomainKeysExpirePath = BasePath + "/domain_keys_expire"
HeaderAllowsPath = BasePath + "/header_allows" HeaderAllowsPath = BasePath + "/header_allows"
HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey
@ -99,6 +103,13 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
attachHandler(http.MethodGet, DomainAllowsPathWithID, m.DomainAllowGETHandler) attachHandler(http.MethodGet, DomainAllowsPathWithID, m.DomainAllowGETHandler)
attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler) attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler)
// domain permission draft stuff
attachHandler(http.MethodGet, DomainPermissionDraftsPath, m.DomainPermissionDraftsPOSTHandler)
attachHandler(http.MethodGet, DomainPermissionDraftsPath, m.DomainPermissionDraftsGETHandler)
attachHandler(http.MethodGet, DomainPermissionDraftsPathWithID, m.DomainPermissionDraftGETHandler)
attachHandler(http.MethodPost, DomainPermissionDraftAcceptPath, m.DomainPermissionDraftAcceptPOSTHandler)
attachHandler(http.MethodPost, DomainPermissionDraftRemovePath, m.DomainPermissionDraftRemovePOSTHandler)
// header filtering administration routes // header filtering administration routes
attachHandler(http.MethodGet, HeaderAllowsPathWithID, m.HeaderFilterAllowGET) attachHandler(http.MethodGet, HeaderAllowsPathWithID, m.HeaderFilterAllowGET)
attachHandler(http.MethodGet, HeaderBlocksPathWithID, m.HeaderFilterBlockGET) attachHandler(http.MethodGet, HeaderBlocksPathWithID, m.HeaderFilterBlockGET)

View file

@ -78,6 +78,11 @@ func (m *Module) DomainPermissionDraftGETHandler(c *gin.Context) {
return return
} }
if authed.Account.IsMoving() {
apiutil.ForbiddenAfterMove(c)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return return

View file

@ -18,15 +18,12 @@
package admin package admin
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
) )
@ -38,6 +35,10 @@
// tags: // tags:
// - admin // - admin
// //
// consumes:
// - multipart/form-data
// - application/json
//
// produces: // produces:
// - application/json // - application/json
// //
@ -48,6 +49,15 @@
// in: path // in: path
// description: ID of the domain permission draft. // description: ID of the domain permission draft.
// type: string // type: string
// -
// name: overwrite
// in: formData
// description: >-
// If a domain permission already exists with the same
// domain and permission type as the draft, overwrite
// the existing permission with fields from the draft.
// type: boolean
// default: false
// //
// security: // security:
// - OAuth2 Bearer: // - OAuth2 Bearer:
@ -83,6 +93,11 @@ func (m *Module) DomainPermissionDraftAcceptPOSTHandler(c *gin.Context) {
return return
} }
if authed.Account.IsMoving() {
apiutil.ForbiddenAfterMove(c)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return return
@ -94,14 +109,26 @@ func (m *Module) DomainPermissionDraftAcceptPOSTHandler(c *gin.Context) {
return return
} }
permDraft, actionID, errWithCode := m.processor.Admin().DomainPermissionDraftAccept( type AcceptForm struct {
Overwrite bool `json:"overwrite" form:"overwrite"`
}
form := new(AcceptForm)
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}
domainPerm, _, errWithCode := m.processor.Admin().DomainPermissionDraftAccept(
c.Request.Context(), c.Request.Context(),
authed.Account, authed.Account,
id, id,
c.Form form.Overwrite,
) )
if errWithCode != nil { if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return return
} }
apiutil.JSON(c, http.StatusOK, domainPerm)
} }

View file

@ -40,6 +40,7 @@
// //
// consumes: // consumes:
// - multipart/form-data // - multipart/form-data
// - application/json
// //
// produces: // produces:
// - application/json // - application/json

View file

@ -0,0 +1,134 @@
// 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"
)
// DomainPermissionDraftRemovePOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts/{id}/remove domainPermissionDraftRemove
//
// Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
//
// ---
// tags:
// - admin
//
// consumes:
// - multipart/form-data
// - application/json
//
// produces:
// - application/json
//
// parameters:
// -
// name: id
// required: true
// in: path
// description: ID of the domain permission draft.
// type: string
// -
// name: ignore_target
// in: formData
// description: >-
// When removing the domain permission draft, also create a
// domain ignore entry for the target domain, so that drafts
// will not be created for this domain in the future.
// type: boolean
// default: false
//
// security:
// - OAuth2 Bearer:
// - admin
//
// responses:
// '200':
// description: The removed domain permission draft.
// schema:
// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '406':
// description: not acceptable
// '409':
// description: conflict
// '500':
// description: internal server error
func (m *Module) DomainPermissionDraftRemovePOSTHandler(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
}
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
type RemoveForm struct {
IgnoreTarget bool `json:"ignore_target" form:"ignore_target"`
}
form := new(RemoveForm)
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}
domainPerm, errWithCode := m.processor.Admin().DomainPermissionDraftRemove(
c.Request.Context(),
authed.Account,
id,
form.IgnoreTarget,
)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
apiutil.JSON(c, http.StatusOK, domainPerm)
}

View file

@ -137,6 +137,11 @@ func (m *Module) DomainPermissionDraftsGETHandler(c *gin.Context) {
return return
} }
if authed.Account.IsMoving() {
apiutil.ForbiddenAfterMove(c)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return return

View file

@ -65,7 +65,7 @@ type DomainPermission struct {
// DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks). // DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
// //
// swagger:model domainPermission // swagger:model domainPermissionSubscription
type DomainPermissionSubscription struct { type DomainPermissionSubscription struct {
// The ID of the domain permission subscription. // The ID of the domain permission subscription.
// example: 01FBW21XJA09XYX51KV5JVBW0F // example: 01FBW21XJA09XYX51KV5JVBW0F

View file

@ -19,19 +19,13 @@
import ( import (
"context" "context"
"errors"
"net/url" "net/url"
"slices"
"time" "time"
"github.com/miekg/dns"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/paging"
"github.com/superseriousbusiness/gotosocial/internal/state" "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun" "github.com/uptrace/bun"
@ -395,701 +389,3 @@ func (d *domainDB) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, e
} }
return false, nil return false, nil
} }
func (d *domainDB) getDomainPermissionDraft(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.DomainPermissionDraft) error,
keyParts ...any,
) (*gtsmodel.DomainPermissionDraft, error) {
// Fetch perm draft from database cache with loader callback.
permDraft, err := d.state.Caches.DB.DomainPermissionDraft.LoadOne(
lookup,
// Only called if not cached.
func() (*gtsmodel.DomainPermissionDraft, error) {
var permDraft gtsmodel.DomainPermissionDraft
if err := dbQuery(&permDraft); err != nil {
return nil, err
}
return &permDraft, nil
},
keyParts...,
)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return permDraft, nil
}
if permDraft.CreatedByAccount == nil {
// Not set, fetch from database.
permDraft.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
permDraft.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return permDraft, nil
}
func (d *domainDB) GetDomainPermissionDraftByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionDraft, error) {
return d.getDomainPermissionDraft(
ctx,
"ID",
func(permDraft *gtsmodel.DomainPermissionDraft) error {
return d.db.
NewSelect().
Model(permDraft).
Where("? = ?", bun.Ident("domain_permission_draft.id"), id).
Scan(ctx)
},
id,
)
}
func (d *domainDB) GetDomainPermissionDrafts(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
permSubID string,
domain string,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionDraft,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
permDraftIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_drafts"),
bun.Ident("domain_permission_draft"),
).
// Select only IDs from table
Column("domain_permission_draft.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_draft.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_draft.id"),
minID,
)
}
// Return only items with
// given permission type.
if permType != gtsmodel.DomainPermissionUnknown {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.permission_type"),
permType,
)
}
// Return only items with
// given subscription ID.
if permSubID != "" {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.subscription_id"),
permSubID,
)
}
// Return only items
// with given domain.
if domain != "" {
var err error
// Normalize domain as punycode.
domain, err = util.Punify(domain)
if err != nil {
return nil, gtserror.Newf("error punifying domain %s: %w", domain, err)
}
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.domain"),
domain,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_draft.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_draft.id"),
)
}
if err := q.Scan(ctx, &permDraftIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(permDraftIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(permDraftIDs)
}
// Allocate return slice (will be at most len permDraftIDs)
permDrafts := make([]*gtsmodel.DomainPermissionDraft, 0, len(permDraftIDs))
for _, id := range permDraftIDs {
permDraft, err := d.GetDomainPermissionDraftByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission draft %q: %v", id, err)
continue
}
// Append to return slice
permDrafts = append(permDrafts, permDraft)
}
return permDrafts, nil
}
func (d *domainDB) PutDomainPermissionDraft(
ctx context.Context,
permDraft *gtsmodel.DomainPermissionDraft,
) error {
var err error
// Normalize the domain as punycode
permDraft.Domain, err = util.Punify(permDraft.Domain)
if err != nil {
return gtserror.Newf("error punifying domain %s: %w", permDraft.Domain, err)
}
return d.state.Caches.DB.DomainPermissionDraft.Store(
permDraft,
func() error {
_, err := d.db.
NewInsert().
Model(permDraft).
Exec(ctx)
return err
},
)
}
func (d *domainDB) DeleteDomainPermissionDraft(
ctx context.Context,
id string,
) error {
// Delete the permDraft from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_drafts"),
bun.Ident("domain_permission_draft"),
).
Where(
"? = ?",
bun.Ident("domain_permission_draft.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Invalidate any cached model by ID.
d.state.Caches.DB.DomainPermissionDraft.Invalidate("ID", id)
return nil
}
func (d *domainDB) getDomainPermissionSubscription(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.DomainPermissionSubscription) error,
keyParts ...any,
) (*gtsmodel.DomainPermissionSubscription, error) {
// Fetch perm subscription from database cache with loader callback.
permSub, err := d.state.Caches.DB.DomainPermissionSubscription.LoadOne(
lookup,
// Only called if not cached.
func() (*gtsmodel.DomainPermissionSubscription, error) {
var permSub gtsmodel.DomainPermissionSubscription
if err := dbQuery(&permSub); err != nil {
return nil, err
}
return &permSub, nil
},
keyParts...,
)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return permSub, nil
}
if permSub.CreatedByAccount == nil {
// Not set, fetch from database.
permSub.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
permSub.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return permSub, nil
}
func (d *domainDB) GetDomainPermissionSubscriptionByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionSubscription, error) {
return d.getDomainPermissionSubscription(
ctx,
"ID",
func(permSub *gtsmodel.DomainPermissionSubscription) error {
return d.db.
NewSelect().
Model(permSub).
Where("? = ?", bun.Ident("domain_permission_subscription.id"), id).
Scan(ctx)
},
id,
)
}
func (d *domainDB) GetDomainPermissionSubscriptions(
ctx context.Context,
permType *gtsmodel.DomainPermissionType,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionSubscription,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
permSubIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_subscriptions"),
bun.Ident("domain_permission_subscription"),
).
// Select only IDs from table
Column("domain_permission_subscription.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_subscription.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_subscription.id"),
minID,
)
}
// Return only items with
// given subscription ID.
if permType != nil {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_subscription.permission_type"),
*permType,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_subscription.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_subscription.id"),
)
}
if err := q.Scan(ctx, &permSubIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(permSubIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(permSubIDs)
}
// Allocate return slice (will be at most len permSubIDs).
permSubs := make([]*gtsmodel.DomainPermissionSubscription, 0, len(permSubIDs))
for _, id := range permSubIDs {
permSub, err := d.GetDomainPermissionSubscriptionByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission subscription %q: %v", id, err)
continue
}
// Append to return slice
permSubs = append(permSubs, permSub)
}
return permSubs, nil
}
func (d *domainDB) PutDomainPermissionSubscription(
ctx context.Context,
permSubscription *gtsmodel.DomainPermissionSubscription,
) error {
return d.state.Caches.DB.DomainPermissionSubscription.Store(
permSubscription,
func() error {
_, err := d.db.
NewInsert().
Model(permSubscription).
Exec(ctx)
return err
},
)
}
func (d *domainDB) DeleteDomainPermissionSubscription(
ctx context.Context,
id string,
) error {
// Delete the permSub from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_subscriptions"),
bun.Ident("domain_permission_subscription"),
).
Where(
"? = ?",
bun.Ident("domain_permission_subscription.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Invalidate any cached model by ID.
d.state.Caches.DB.DomainPermissionSubscription.Invalidate("ID", id)
return nil
}
func (d *domainDB) PutDomainPermissionIgnore(
ctx context.Context,
ignore *gtsmodel.DomainPermissionIgnore,
) error {
// Normalize the domain as punycode
var err error
ignore.Domain, err = util.Punify(ignore.Domain)
if err != nil {
return err
}
// Attempt to store domain perm ignore in DB
if _, err := d.db.NewInsert().
Model(ignore).
Exec(ctx); err != nil {
return err
}
// Clear the domain perm ignore cache (for later reload)
d.state.Caches.DB.DomainPermissionIgnore.Clear()
return nil
}
func (d *domainDB) IsDomainPermissionIgnored(ctx context.Context, domain string) (bool, error) {
// Normalize the domain as punycode
domain, err := util.Punify(domain)
if err != nil {
return false, err
}
// Check if our host and given domain are equal
// or part of the same second-level domain; we
// always ignore such perms as creating blocks
// or allows in such cases may break things.
if dns.CompareDomainName(domain, config.GetHost()) >= 2 {
return true, nil
}
// Func to scan list of all
// ignored domain perms from DB.
loadF := func() ([]string, error) {
var domains []string
if err := d.db.
NewSelect().
Table("domain_ignores").
Column("domain").
Scan(ctx, &domains); err != nil {
return nil, err
}
return domains, nil
}
// Check the cache for a domain perm ignore,
// hydrating the cache with loadF if necessary.
return d.state.Caches.DB.DomainPermissionIgnore.Matches(domain, loadF)
}
func (d *domainDB) GetDomainPermissionIgnoreByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionIgnore, error) {
ignore := new(gtsmodel.DomainPermissionIgnore)
q := d.db.
NewSelect().
Model(ignore).
Where("? = ?", bun.Ident("domain_permission_ignore.id"), id)
if err := q.Scan(ctx); err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return ignore, nil
}
if ignore.CreatedByAccount == nil {
// Not set, fetch from database.
var err error
ignore.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
ignore.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return ignore, nil
}
func (d *domainDB) GetDomainPermissionIgnores(
ctx context.Context,
permType *gtsmodel.DomainPermissionType,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionIgnore,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
ignoreIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_ignores"),
bun.Ident("domain_permission_ignore"),
).
// Select only IDs from table
Column("domain_permission_ignore.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_ignore.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_ignore.id"),
minID,
)
}
// Return only items with
// given subscription ID.
if permType != nil {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_ignore.permission_type"),
*permType,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_ignore.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_ignore.id"),
)
}
if err := q.Scan(ctx, &ignoreIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(ignoreIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(ignoreIDs)
}
// Allocate return slice (will be at most len permSubIDs).
ignores := make([]*gtsmodel.DomainPermissionIgnore, 0, len(ignoreIDs))
for _, id := range ignoreIDs {
ignore, err := d.GetDomainPermissionIgnoreByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission ignore %q: %v", id, err)
continue
}
// Append to return slice
ignores = append(ignores, ignore)
}
return ignores, nil
}
func (d *domainDB) DeleteDomainPermissionIgnore(
ctx context.Context,
id string,
) error {
// Delete the permSub from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_ignores"),
bun.Ident("domain_permission_ignore"),
).
Where(
"? = ?",
bun.Ident("domain_permission_ignore.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Clear the domain perm ignore cache (for later reload)
d.state.Caches.DB.DomainPermissionIgnore.Clear()
return nil
}

View file

@ -0,0 +1,285 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package bundb
import (
"context"
"errors"
"slices"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/paging"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun"
)
func (d *domainDB) getDomainPermissionDraft(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.DomainPermissionDraft) error,
keyParts ...any,
) (*gtsmodel.DomainPermissionDraft, error) {
// Fetch perm draft from database cache with loader callback.
permDraft, err := d.state.Caches.DB.DomainPermissionDraft.LoadOne(
lookup,
// Only called if not cached.
func() (*gtsmodel.DomainPermissionDraft, error) {
var permDraft gtsmodel.DomainPermissionDraft
if err := dbQuery(&permDraft); err != nil {
return nil, err
}
return &permDraft, nil
},
keyParts...,
)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return permDraft, nil
}
if permDraft.CreatedByAccount == nil {
// Not set, fetch from database.
permDraft.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
permDraft.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return permDraft, nil
}
func (d *domainDB) GetDomainPermissionDraftByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionDraft, error) {
return d.getDomainPermissionDraft(
ctx,
"ID",
func(permDraft *gtsmodel.DomainPermissionDraft) error {
return d.db.
NewSelect().
Model(permDraft).
Where("? = ?", bun.Ident("domain_permission_draft.id"), id).
Scan(ctx)
},
id,
)
}
func (d *domainDB) GetDomainPermissionDrafts(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
permSubID string,
domain string,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionDraft,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
permDraftIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_drafts"),
bun.Ident("domain_permission_draft"),
).
// Select only IDs from table
Column("domain_permission_draft.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_draft.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_draft.id"),
minID,
)
}
// Return only items with
// given permission type.
if permType != gtsmodel.DomainPermissionUnknown {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.permission_type"),
permType,
)
}
// Return only items with
// given subscription ID.
if permSubID != "" {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.subscription_id"),
permSubID,
)
}
// Return only items
// with given domain.
if domain != "" {
var err error
// Normalize domain as punycode.
domain, err = util.Punify(domain)
if err != nil {
return nil, gtserror.Newf("error punifying domain %s: %w", domain, err)
}
q = q.Where(
"? = ?",
bun.Ident("domain_permission_draft.domain"),
domain,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_draft.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_draft.id"),
)
}
if err := q.Scan(ctx, &permDraftIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(permDraftIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(permDraftIDs)
}
// Allocate return slice (will be at most len permDraftIDs)
permDrafts := make([]*gtsmodel.DomainPermissionDraft, 0, len(permDraftIDs))
for _, id := range permDraftIDs {
permDraft, err := d.GetDomainPermissionDraftByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission draft %q: %v", id, err)
continue
}
// Append to return slice
permDrafts = append(permDrafts, permDraft)
}
return permDrafts, nil
}
func (d *domainDB) PutDomainPermissionDraft(
ctx context.Context,
permDraft *gtsmodel.DomainPermissionDraft,
) error {
var err error
// Normalize the domain as punycode
permDraft.Domain, err = util.Punify(permDraft.Domain)
if err != nil {
return gtserror.Newf("error punifying domain %s: %w", permDraft.Domain, err)
}
return d.state.Caches.DB.DomainPermissionDraft.Store(
permDraft,
func() error {
_, err := d.db.
NewInsert().
Model(permDraft).
Exec(ctx)
return err
},
)
}
func (d *domainDB) DeleteDomainPermissionDraft(
ctx context.Context,
id string,
) error {
// Delete the permDraft from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_drafts"),
bun.Ident("domain_permission_draft"),
).
Where(
"? = ?",
bun.Ident("domain_permission_draft.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Invalidate any cached model by ID.
d.state.Caches.DB.DomainPermissionDraft.Invalidate("ID", id)
return nil
}

View file

@ -0,0 +1,275 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package bundb
import (
"context"
"errors"
"slices"
"github.com/miekg/dns"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/paging"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun"
)
func (d *domainDB) PutDomainPermissionIgnore(
ctx context.Context,
ignore *gtsmodel.DomainPermissionIgnore,
) error {
// Normalize the domain as punycode
var err error
ignore.Domain, err = util.Punify(ignore.Domain)
if err != nil {
return err
}
// Attempt to store domain perm ignore in DB
if _, err := d.db.NewInsert().
Model(ignore).
Exec(ctx); err != nil {
return err
}
// Clear the domain perm ignore cache (for later reload)
d.state.Caches.DB.DomainPermissionIgnore.Clear()
return nil
}
func (d *domainDB) IsDomainPermissionIgnored(ctx context.Context, domain string) (bool, error) {
// Normalize the domain as punycode
domain, err := util.Punify(domain)
if err != nil {
return false, err
}
// Check if our host and given domain are equal
// or part of the same second-level domain; we
// always ignore such perms as creating blocks
// or allows in such cases may break things.
if dns.CompareDomainName(domain, config.GetHost()) >= 2 {
return true, nil
}
// Func to scan list of all
// ignored domain perms from DB.
loadF := func() ([]string, error) {
var domains []string
if err := d.db.
NewSelect().
Table("domain_ignores").
Column("domain").
Scan(ctx, &domains); err != nil {
return nil, err
}
return domains, nil
}
// Check the cache for a domain perm ignore,
// hydrating the cache with loadF if necessary.
return d.state.Caches.DB.DomainPermissionIgnore.Matches(domain, loadF)
}
func (d *domainDB) GetDomainPermissionIgnoreByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionIgnore, error) {
ignore := new(gtsmodel.DomainPermissionIgnore)
q := d.db.
NewSelect().
Model(ignore).
Where("? = ?", bun.Ident("domain_permission_ignore.id"), id)
if err := q.Scan(ctx); err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return ignore, nil
}
if ignore.CreatedByAccount == nil {
// Not set, fetch from database.
var err error
ignore.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
ignore.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return ignore, nil
}
func (d *domainDB) GetDomainPermissionIgnores(
ctx context.Context,
domain string,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionIgnore,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
ignoreIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_ignores"),
bun.Ident("domain_permission_ignore"),
).
// Select only IDs from table
Column("domain_permission_ignore.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_ignore.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_ignore.id"),
minID,
)
}
// Return only items
// with given domain.
if domain != "" {
var err error
// Normalize domain as punycode.
domain, err = util.Punify(domain)
if err != nil {
return nil, gtserror.Newf("error punifying domain %s: %w", domain, err)
}
q = q.Where(
"? = ?",
bun.Ident("domain_permission_ignore.domain"),
domain,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_ignore.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_ignore.id"),
)
}
if err := q.Scan(ctx, &ignoreIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(ignoreIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(ignoreIDs)
}
// Allocate return slice (will be at most len permSubIDs).
ignores := make([]*gtsmodel.DomainPermissionIgnore, 0, len(ignoreIDs))
for _, id := range ignoreIDs {
ignore, err := d.GetDomainPermissionIgnoreByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission ignore %q: %v", id, err)
continue
}
// Append to return slice
ignores = append(ignores, ignore)
}
return ignores, nil
}
func (d *domainDB) DeleteDomainPermissionIgnore(
ctx context.Context,
id string,
) error {
// Delete the permSub from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_ignores"),
bun.Ident("domain_permission_ignore"),
).
Where(
"? = ?",
bun.Ident("domain_permission_ignore.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Clear the domain perm ignore cache (for later reload)
d.state.Caches.DB.DomainPermissionIgnore.Clear()
return nil
}

View file

@ -0,0 +1,246 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package bundb
import (
"context"
"errors"
"slices"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/paging"
"github.com/uptrace/bun"
)
func (d *domainDB) getDomainPermissionSubscription(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.DomainPermissionSubscription) error,
keyParts ...any,
) (*gtsmodel.DomainPermissionSubscription, error) {
// Fetch perm subscription from database cache with loader callback.
permSub, err := d.state.Caches.DB.DomainPermissionSubscription.LoadOne(
lookup,
// Only called if not cached.
func() (*gtsmodel.DomainPermissionSubscription, error) {
var permSub gtsmodel.DomainPermissionSubscription
if err := dbQuery(&permSub); err != nil {
return nil, err
}
return &permSub, nil
},
keyParts...,
)
if err != nil {
return nil, err
}
if gtscontext.Barebones(ctx) {
// No need to fully populate.
return permSub, nil
}
if permSub.CreatedByAccount == nil {
// Not set, fetch from database.
permSub.CreatedByAccount, err = d.state.DB.GetAccountByID(
gtscontext.SetBarebones(ctx),
permSub.CreatedByAccountID,
)
if err != nil {
return nil, gtserror.Newf("error populating created by account: %w", err)
}
}
return permSub, nil
}
func (d *domainDB) GetDomainPermissionSubscriptionByID(
ctx context.Context,
id string,
) (*gtsmodel.DomainPermissionSubscription, error) {
return d.getDomainPermissionSubscription(
ctx,
"ID",
func(permSub *gtsmodel.DomainPermissionSubscription) error {
return d.db.
NewSelect().
Model(permSub).
Where("? = ?", bun.Ident("domain_permission_subscription.id"), id).
Scan(ctx)
},
id,
)
}
func (d *domainDB) GetDomainPermissionSubscriptions(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
page *paging.Page,
) (
[]*gtsmodel.DomainPermissionSubscription,
error,
) {
var (
// Get paging params.
minID = page.GetMin()
maxID = page.GetMax()
limit = page.GetLimit()
order = page.GetOrder()
// Make educated guess for slice size
permSubIDs = make([]string, 0, limit)
)
q := d.db.
NewSelect().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_subscriptions"),
bun.Ident("domain_permission_subscription"),
).
// Select only IDs from table
Column("domain_permission_subscription.id")
// Return only items with id
// lower than provided maxID.
if maxID != "" {
q = q.Where(
"? < ?",
bun.Ident("domain_permission_subscription.id"),
maxID,
)
}
// Return only items with id
// greater than provided minID.
if minID != "" {
q = q.Where(
"? > ?",
bun.Ident("domain_permission_subscription.id"),
minID,
)
}
// Return only items with
// given permission type.
if permType != gtsmodel.DomainPermissionUnknown {
q = q.Where(
"? = ?",
bun.Ident("domain_permission_subscription.permission_type"),
permType,
)
}
if limit > 0 {
// Limit amount of
// items returned.
q = q.Limit(limit)
}
if order == paging.OrderAscending {
// Page up.
q = q.OrderExpr(
"? ASC",
bun.Ident("domain_permission_subscription.id"),
)
} else {
// Page down.
q = q.OrderExpr(
"? DESC",
bun.Ident("domain_permission_subscription.id"),
)
}
if err := q.Scan(ctx, &permSubIDs); err != nil {
return nil, err
}
// Catch case of no items early
if len(permSubIDs) == 0 {
return nil, db.ErrNoEntries
}
// If we're paging up, we still want items
// to be sorted by ID desc, so reverse slice.
if order == paging.OrderAscending {
slices.Reverse(permSubIDs)
}
// Allocate return slice (will be at most len permSubIDs).
permSubs := make([]*gtsmodel.DomainPermissionSubscription, 0, len(permSubIDs))
for _, id := range permSubIDs {
permSub, err := d.GetDomainPermissionSubscriptionByID(ctx, id)
if err != nil {
log.Errorf(ctx, "error getting domain permission subscription %q: %v", id, err)
continue
}
// Append to return slice
permSubs = append(permSubs, permSub)
}
return permSubs, nil
}
func (d *domainDB) PutDomainPermissionSubscription(
ctx context.Context,
permSubscription *gtsmodel.DomainPermissionSubscription,
) error {
return d.state.Caches.DB.DomainPermissionSubscription.Store(
permSubscription,
func() error {
_, err := d.db.
NewInsert().
Model(permSubscription).
Exec(ctx)
return err
},
)
}
func (d *domainDB) DeleteDomainPermissionSubscription(
ctx context.Context,
id string,
) error {
// Delete the permSub from DB.
q := d.db.NewDelete().
TableExpr(
"? AS ?",
bun.Ident("domain_permission_subscriptions"),
bun.Ident("domain_permission_subscription"),
).
Where(
"? = ?",
bun.Ident("domain_permission_subscription.id"),
id,
)
_, err := q.Exec(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Invalidate any cached model by ID.
d.state.Caches.DB.DomainPermissionSubscription.Invalidate("ID", id)
return nil
}

View file

@ -109,6 +109,27 @@ type Domain interface {
// DeleteDomainPermissionDraft deletes one DomainPermissionDraft with the given id. // DeleteDomainPermissionDraft deletes one DomainPermissionDraft with the given id.
DeleteDomainPermissionDraft(ctx context.Context, id string) error DeleteDomainPermissionDraft(ctx context.Context, id string) error
/*
Domain permission ignore stuff.
*/
// GetDomainPermissionIgnoreByID gets one DomainPermissionIgnore with the given ID.
GetDomainPermissionIgnoreByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionIgnore, error)
// GetDomainPermissionIgnores returns a page of
// DomainPermissionIgnores using the given parameters.
GetDomainPermissionIgnores(
ctx context.Context,
domain string,
page *paging.Page,
) ([]*gtsmodel.DomainPermissionIgnore, error)
// PutDomainPermissionIgnore stores one DomainPermissionIgnore.
PutDomainPermissionIgnore(ctx context.Context, permIgnore *gtsmodel.DomainPermissionIgnore) error
// DeleteDomainPermissionIgnore deletes one DomainPermissionIgnore with the given id.
DeleteDomainPermissionIgnore(ctx context.Context, id string) error
/* /*
Domain permission subscription stuff. Domain permission subscription stuff.
*/ */
@ -120,7 +141,7 @@ type Domain interface {
// DomainPermissionSubscriptions using the given parameters. // DomainPermissionSubscriptions using the given parameters.
GetDomainPermissionSubscriptions( GetDomainPermissionSubscriptions(
ctx context.Context, ctx context.Context,
permType *gtsmodel.DomainPermissionType, permType gtsmodel.DomainPermissionType,
page *paging.Page, page *paging.Page,
) ([]*gtsmodel.DomainPermissionSubscription, error) ) ([]*gtsmodel.DomainPermissionSubscription, error)

View file

@ -19,8 +19,8 @@
import "time" import "time"
// DomainPermission models a domain // DomainPermission models a domain permission
// permission entry (block/allow/draft). // entry -- block / allow / draft / ignore.
type DomainPermission interface { type DomainPermission interface {
GetID() string GetID() string
GetCreatedAt() time.Time GetCreatedAt() time.Time

View file

@ -17,16 +17,76 @@
package gtsmodel package gtsmodel
import "time" import (
"time"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// DomainPermissionIgnore represents one domain that should be ignored // DomainPermissionIgnore represents one domain that should be ignored
// when domain permission (ignores) are created from subscriptions. // when domain permission (ignores) are created from subscriptions.
type DomainPermissionIgnore struct { type DomainPermissionIgnore struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database. ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created. CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
PermissionType DomainPermissionType `bun:",notnull,unique:domain_permission_ignores_permission_type_domain_uniq"` // Permission type of the ignore. UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was last updated.
Domain string `bun:",nullzero,notnull,unique:domain_permission_ignores_permission_type_domain_uniq"` // Domain to ignore. Eg. 'whatever.com'. Domain string `bun:",nullzero,notnull,unique"` // Domain to ignore. Eg. 'whatever.com'.
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this ignore. CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this ignore.
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID. CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
PrivateComment string `bun:",nullzero"` // Private comment on this ignore, viewable to admins. PrivateComment string `bun:",nullzero"` // Private comment on this ignore, viewable to admins.
} }
func (d *DomainPermissionIgnore) GetID() string {
return d.ID
}
func (d *DomainPermissionIgnore) GetCreatedAt() time.Time {
return d.CreatedAt
}
func (d *DomainPermissionIgnore) GetUpdatedAt() time.Time {
return d.UpdatedAt
}
func (d *DomainPermissionIgnore) SetUpdatedAt(i time.Time) {
d.UpdatedAt = i
}
func (d *DomainPermissionIgnore) GetDomain() string {
return d.Domain
}
func (d *DomainPermissionIgnore) GetCreatedByAccountID() string {
return d.CreatedByAccountID
}
func (d *DomainPermissionIgnore) SetCreatedByAccountID(i string) {
d.CreatedByAccountID = i
}
func (d *DomainPermissionIgnore) GetCreatedByAccount() *Account {
return d.CreatedByAccount
}
func (d *DomainPermissionIgnore) SetCreatedByAccount(i *Account) {
d.CreatedByAccount = i
}
func (d *DomainPermissionIgnore) GetPrivateComment() string {
return d.PrivateComment
}
func (d *DomainPermissionIgnore) SetPrivateComment(i string) {
d.PrivateComment = i
}
/*
Stubbed functions for interface purposes.
*/
func (d *DomainPermissionIgnore) GetPublicComment() string { return "" }
func (d *DomainPermissionIgnore) SetPublicComment(_ string) {}
func (d *DomainPermissionIgnore) GetObfuscate() *bool { return util.Ptr(false) }
func (d *DomainPermissionIgnore) SetObfuscate(_ *bool) {}
func (d *DomainPermissionIgnore) GetSubscriptionID() string { return "" }
func (d *DomainPermissionIgnore) SetSubscriptionID(_ string) {}
func (d *DomainPermissionIgnore) GetType() DomainPermissionType { return DomainPermissionUnknown }

View file

@ -43,7 +43,7 @@
// from this call, and/or an error if something goes wrong. // from this call, and/or an error if something goes wrong.
func (p *Processor) DomainPermissionCreate( func (p *Processor) DomainPermissionCreate(
ctx context.Context, ctx context.Context,
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
adminAcct *gtsmodel.Account, adminAcct *gtsmodel.Account,
domain string, domain string,
obfuscate bool, obfuscate bool,
@ -51,7 +51,7 @@ func (p *Processor) DomainPermissionCreate(
privateComment string, privateComment string,
subscriptionID string, subscriptionID string,
) (*apimodel.DomainPermission, string, gtserror.WithCode) { ) (*apimodel.DomainPermission, string, gtserror.WithCode) {
switch permType { switch permissionType {
// Explicitly block a domain. // Explicitly block a domain.
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
@ -79,7 +79,7 @@ func (p *Processor) DomainPermissionCreate(
// Weeping, roaring, red-faced. // Weeping, roaring, red-faced.
default: default:
err := gtserror.Newf("unrecognized permission type %d", permType) err := gtserror.Newf("unrecognized permission type %d", permissionType)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
} }
@ -91,11 +91,11 @@ func (p *Processor) DomainPermissionCreate(
// action resulting from this call, and/or an error if something goes wrong. // action resulting from this call, and/or an error if something goes wrong.
func (p *Processor) DomainPermissionDelete( func (p *Processor) DomainPermissionDelete(
ctx context.Context, ctx context.Context,
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
adminAcct *gtsmodel.Account, adminAcct *gtsmodel.Account,
domainBlockID string, domainBlockID string,
) (*apimodel.DomainPermission, string, gtserror.WithCode) { ) (*apimodel.DomainPermission, string, gtserror.WithCode) {
switch permType { switch permissionType {
// Delete explicit domain block. // Delete explicit domain block.
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
@ -116,7 +116,7 @@ func (p *Processor) DomainPermissionDelete(
// You do the hokey-cokey and you turn // You do the hokey-cokey and you turn
// around, that's what it's all about. // around, that's what it's all about.
default: default:
err := gtserror.Newf("unrecognized permission type %d", permType) err := gtserror.Newf("unrecognized permission type %d", permissionType)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
} }
@ -134,14 +134,14 @@ func (p *Processor) DomainPermissionDelete(
// as they wish. // as they wish.
func (p *Processor) DomainPermissionsImport( func (p *Processor) DomainPermissionsImport(
ctx context.Context, ctx context.Context,
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
account *gtsmodel.Account, account *gtsmodel.Account,
domainsF *multipart.FileHeader, domainsF *multipart.FileHeader,
) (*apimodel.MultiStatus, gtserror.WithCode) { ) (*apimodel.MultiStatus, gtserror.WithCode) {
// Ensure known permission type. // Ensure known permission type.
if permType != gtsmodel.DomainPermissionBlock && if permissionType != gtsmodel.DomainPermissionBlock &&
permType != gtsmodel.DomainPermissionAllow { permissionType != gtsmodel.DomainPermissionAllow {
err := gtserror.Newf("unrecognized permission type %d", permType) err := gtserror.Newf("unrecognized permission type %d", permissionType)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }
@ -183,7 +183,7 @@ func (p *Processor) DomainPermissionsImport(
domainPerm, _, errWithCode = p.DomainPermissionCreate( domainPerm, _, errWithCode = p.DomainPermissionCreate(
ctx, ctx,
permType, permissionType,
account, account,
domain, domain,
obfuscate, obfuscate,
@ -222,7 +222,7 @@ func (p *Processor) DomainPermissionsImport(
// to an export. // to an export.
func (p *Processor) DomainPermissionsGet( func (p *Processor) DomainPermissionsGet(
ctx context.Context, ctx context.Context,
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
account *gtsmodel.Account, account *gtsmodel.Account,
export bool, export bool,
) ([]*apimodel.DomainPermission, gtserror.WithCode) { ) ([]*apimodel.DomainPermission, gtserror.WithCode) {
@ -231,7 +231,7 @@ func (p *Processor) DomainPermissionsGet(
err error err error
) )
switch permType { switch permissionType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
var blocks []*gtsmodel.DomainBlock var blocks []*gtsmodel.DomainBlock
@ -261,7 +261,7 @@ func (p *Processor) DomainPermissionsGet(
} }
if err != nil { if err != nil {
err := gtserror.Newf("error getting %ss: %w", permType.String(), err) err := gtserror.Newf("error getting %ss: %w", permissionType.String(), err)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }
@ -285,7 +285,7 @@ func (p *Processor) DomainPermissionsGet(
// suitable for writing out to an export. // suitable for writing out to an export.
func (p *Processor) DomainPermissionGet( func (p *Processor) DomainPermissionGet(
ctx context.Context, ctx context.Context,
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
id string, id string,
export bool, export bool,
) (*apimodel.DomainPermission, gtserror.WithCode) { ) (*apimodel.DomainPermission, gtserror.WithCode) {
@ -294,7 +294,7 @@ func (p *Processor) DomainPermissionGet(
err error err error
) )
switch permType { switch permissionType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
domainPerm, err = p.state.DB.GetDomainBlockByID(ctx, id) domainPerm, err = p.state.DB.GetDomainBlockByID(ctx, id)
case gtsmodel.DomainPermissionAllow: case gtsmodel.DomainPermissionAllow:
@ -305,11 +305,11 @@ func (p *Processor) DomainPermissionGet(
if err != nil { if err != nil {
if errors.Is(err, db.ErrNoEntries) { if errors.Is(err, db.ErrNoEntries) {
err = fmt.Errorf("no domain %s exists with id %s", permType.String(), id) err = fmt.Errorf("no domain %s exists with id %s", permissionType.String(), id)
return nil, gtserror.NewErrorNotFound(err, err.Error()) return nil, gtserror.NewErrorNotFound(err, err.Error())
} }
err = gtserror.Newf("error getting domain %s with id %s: %w", permType.String(), id, err) err = gtserror.Newf("error getting domain %s with id %s: %w", permissionType.String(), id, err)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }

View file

@ -42,7 +42,7 @@ type domainPermAction struct {
// Type of permission // Type of permission
// to create or delete. // to create or delete.
permType gtsmodel.DomainPermissionType permissionType gtsmodel.DomainPermissionType
// Domain to target // Domain to target
// with the permission. // with the permission.
@ -89,9 +89,9 @@ func (suite *DomainBlockTestSuite) runDomainPermTest(t domainPermTest) {
var actionID string var actionID string
switch action.createOrDelete { switch action.createOrDelete {
case "create": case "create":
_, actionID = suite.createDomainPerm(action.permType, action.domain) _, actionID = suite.createDomainPerm(action.permissionType, action.domain)
case "delete": case "delete":
_, actionID = suite.deleteDomainPerm(action.permType, action.domain) _, actionID = suite.deleteDomainPerm(action.permissionType, action.domain)
default: default:
panic("createOrDelete was not 'create' or 'delete'") panic("createOrDelete was not 'create' or 'delete'")
} }
@ -118,16 +118,16 @@ func (suite *DomainBlockTestSuite) runDomainPermTest(t domainPermTest) {
} }
} }
// create given permType with default values. // create given permissionType with default values.
func (suite *DomainBlockTestSuite) createDomainPerm( func (suite *DomainBlockTestSuite) createDomainPerm(
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
domain string, domain string,
) (*apimodel.DomainPermission, string) { ) (*apimodel.DomainPermission, string) {
ctx := context.Background() ctx := context.Background()
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionCreate( apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionCreate(
ctx, ctx,
permType, permissionType,
suite.testAccounts["admin_account"], suite.testAccounts["admin_account"],
domain, domain,
false, false,
@ -144,7 +144,7 @@ func (suite *DomainBlockTestSuite) createDomainPerm(
// delete given permission type. // delete given permission type.
func (suite *DomainBlockTestSuite) deleteDomainPerm( func (suite *DomainBlockTestSuite) deleteDomainPerm(
permType gtsmodel.DomainPermissionType, permissionType gtsmodel.DomainPermissionType,
domain string, domain string,
) (*apimodel.DomainPermission, string) { ) (*apimodel.DomainPermission, string) {
var ( var (
@ -154,7 +154,7 @@ func (suite *DomainBlockTestSuite) deleteDomainPerm(
// To delete the permission, // To delete the permission,
// first get it from the db. // first get it from the db.
switch permType { switch permissionType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
domainPermission, _ = suite.db.GetDomainBlock(ctx, domain) domainPermission, _ = suite.db.GetDomainBlock(ctx, domain)
case gtsmodel.DomainPermissionAllow: case gtsmodel.DomainPermissionAllow:
@ -170,7 +170,7 @@ func (suite *DomainBlockTestSuite) deleteDomainPerm(
// Now use the ID to delete it. // Now use the ID to delete it.
apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionDelete( apiPerm, actionID, errWithCode := suite.adminProcessor.DomainPermissionDelete(
ctx, ctx,
permType, permissionType,
suite.testAccounts["admin_account"], suite.testAccounts["admin_account"],
domainPermission.GetID(), domainPermission.GetID(),
) )
@ -246,7 +246,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndUnblockDomain() {
actions: []domainPermAction{ actions: []domainPermAction{
{ {
createOrDelete: "create", createOrDelete: "create",
permType: gtsmodel.DomainPermissionBlock, permissionType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(_ context.Context, account *gtsmodel.Account) bool { expected: func(_ context.Context, account *gtsmodel.Account) bool {
// Domain was blocked, so each // Domain was blocked, so each
@ -256,7 +256,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndUnblockDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permType: gtsmodel.DomainPermissionBlock, permissionType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(_ context.Context, account *gtsmodel.Account) bool { expected: func(_ context.Context, account *gtsmodel.Account) bool {
// Domain was unblocked, so each // Domain was unblocked, so each
@ -279,7 +279,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
actions: []domainPermAction{ actions: []domainPermAction{
{ {
createOrDelete: "create", createOrDelete: "create",
permType: gtsmodel.DomainPermissionBlock, permissionType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was blocked, so each // Domain was blocked, so each
@ -316,7 +316,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
}, },
{ {
createOrDelete: "create", createOrDelete: "create",
permType: gtsmodel.DomainPermissionAllow, permissionType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was explicitly allowed, so each // Domain was explicitly allowed, so each
@ -355,7 +355,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permType: gtsmodel.DomainPermissionAllow, permissionType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the allow now, while there's // Deleting the allow now, while there's
@ -382,7 +382,7 @@ func (suite *DomainBlockTestSuite) TestBlockAndAllowDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permType: gtsmodel.DomainPermissionBlock, permissionType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the block now should // Deleting the block now should
@ -421,7 +421,7 @@ func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
actions: []domainPermAction{ actions: []domainPermAction{
{ {
createOrDelete: "create", createOrDelete: "create",
permType: gtsmodel.DomainPermissionAllow, permissionType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Domain was explicitly allowed, // Domain was explicitly allowed,
@ -458,7 +458,7 @@ func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
}, },
{ {
createOrDelete: "create", createOrDelete: "create",
permType: gtsmodel.DomainPermissionBlock, permissionType: gtsmodel.DomainPermissionBlock,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Create a block. An allow existed, so // Create a block. An allow existed, so
@ -497,7 +497,7 @@ func (suite *DomainBlockTestSuite) TestAllowAndBlockDomain() {
}, },
{ {
createOrDelete: "delete", createOrDelete: "delete",
permType: gtsmodel.DomainPermissionAllow, permissionType: gtsmodel.DomainPermissionAllow,
domain: domain, domain: domain,
expected: func(ctx context.Context, account *gtsmodel.Account) bool { expected: func(ctx context.Context, account *gtsmodel.Account) bool {
// Deleting the allow now, while there's // Deleting the allow now, while there's

View file

@ -42,12 +42,12 @@ func (p *Processor) DomainPermissionDraftGet(
) (*apimodel.DomainPermission, gtserror.WithCode) { ) (*apimodel.DomainPermission, gtserror.WithCode) {
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id) permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
if err != nil && !errors.Is(err, db.ErrNoEntries) { if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("db error getting domain permission draft %s: %w", id, err) err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
return nil, gtserror.NewErrorInternalError(err) return nil, gtserror.NewErrorInternalError(err)
} }
if permDraft == nil { if permDraft == nil {
err = fmt.Errorf("domain permission draft %s not found", id) err := fmt.Errorf("domain permission draft %s not found", id)
return nil, gtserror.NewErrorNotFound(err, err.Error()) return nil, gtserror.NewErrorNotFound(err, err.Error())
} }
@ -138,8 +138,9 @@ func (p *Processor) DomainPermissionDraftCreate(
if err := p.state.DB.PutDomainPermissionDraft(ctx, permDraft); err != nil { if err := p.state.DB.PutDomainPermissionDraft(ctx, permDraft); err != nil {
if errors.Is(err, db.ErrAlreadyExists) { if errors.Is(err, db.ErrAlreadyExists) {
const text = "a domain permission draft already exists with this permission type and domain" const text = "a domain permission draft already exists with this permission type, domain, and subscription ID"
return nil, gtserror.NewErrorConflict(errors.New(text), text) err := fmt.Errorf("%w: %s", err, text)
return nil, gtserror.NewErrorConflict(err, text)
} }
// Real error. // Real error.
@ -158,12 +159,12 @@ func (p *Processor) DomainPermissionDraftAccept(
) (*apimodel.DomainPermission, string, gtserror.WithCode) { ) (*apimodel.DomainPermission, string, gtserror.WithCode) {
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id) permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
if err != nil && !errors.Is(err, db.ErrNoEntries) { if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("db error getting domain permission draft %s: %w", id, err) err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
if permDraft == nil { if permDraft == nil {
err = fmt.Errorf("domain permission draft %s not found", id) err := fmt.Errorf("domain permission draft %s not found", id)
return nil, "", gtserror.NewErrorNotFound(err, err.Error()) return nil, "", gtserror.NewErrorNotFound(err, err.Error())
} }
@ -188,7 +189,7 @@ func (p *Processor) DomainPermissionDraftAccept(
} }
if err != nil && !errors.Is(err, db.ErrNoEntries) { if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("db error getting domain permission %s: %w", id, err) err := gtserror.Newf("db error getting domain permission %s: %w", id, err)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
@ -196,7 +197,7 @@ func (p *Processor) DomainPermissionDraftAccept(
// creating or updating permission from draft is successful. // creating or updating permission from draft is successful.
deleteDraft := func() { deleteDraft := func() {
if err := p.state.DB.DeleteDomainPermissionDraft(ctx, permDraft.ID); err != nil { if err := p.state.DB.DeleteDomainPermissionDraft(ctx, permDraft.ID); err != nil {
log.Errorf(ctx, "error deleting domain permission draft: %v", err) log.Errorf(ctx, "db error deleting domain permission draft: %v", err)
} }
} }
@ -235,7 +236,10 @@ func (p *Processor) DomainPermissionDraftAccept(
) )
} }
// Clean up the draft
// before returning.
deleteDraft() deleteDraft()
return new, actionID, errWithCode return new, actionID, errWithCode
// Domain permission exists but we should overwrite // Domain permission exists but we should overwrite
@ -259,10 +263,12 @@ func (p *Processor) DomainPermissionDraftAccept(
} }
if err != nil { if err != nil {
err = gtserror.Newf("db error updating existing domain permission: %w", err) err := gtserror.Newf("db error updating existing domain permission: %w", err)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
// Clean up the draft
// before returning.
deleteDraft() deleteDraft()
apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false) apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false)
@ -272,6 +278,48 @@ func (p *Processor) DomainPermissionDraftAccept(
// overwrite it, leave everything alone. // overwrite it, leave everything alone.
default: default:
const text = "a domain permission already exists with this permission type and domain" const text = "a domain permission already exists with this permission type and domain"
return nil, "", gtserror.NewErrorConflict(errors.New(text), text) err := fmt.Errorf("%w: %s", err, text)
return nil, "", gtserror.NewErrorConflict(err, text)
} }
} }
func (p *Processor) DomainPermissionDraftRemove(
ctx context.Context,
acct *gtsmodel.Account,
id string,
ignoreTarget 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 ignoreTarget {
// Add a domain permission ignore
// targeting the permDraft's domain.
_, err = p.DomainPermissionIgnoreCreate(
ctx,
acct,
permDraft.Domain,
permDraft.PrivateComment,
)
if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
err := gtserror.Newf("db error creating domain permission ignore: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
}
return p.apiDomainPerm(ctx, permDraft, false)
}

View file

@ -0,0 +1,134 @@
// 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) DomainPermissionIgnoreCreate(
ctx context.Context,
acct *gtsmodel.Account,
domain string,
privateComment string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
permIgnore := &gtsmodel.DomainPermissionIgnore{
ID: id.NewULID(),
Domain: domain,
CreatedByAccountID: acct.ID,
CreatedByAccount: acct,
PrivateComment: privateComment,
}
if err := p.state.DB.PutDomainPermissionIgnore(ctx, permIgnore); err != nil {
if errors.Is(err, db.ErrAlreadyExists) {
const text = "a domain permission ignore 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 ignore: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiDomainPerm(ctx, permIgnore, false)
}
// DomainPermissionIgnoreGet returns one
// domain permission ignore with the given id.
func (p *Processor) DomainPermissionIgnoreGet(
ctx context.Context,
id string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
permIgnore, err := p.state.DB.GetDomainPermissionIgnoreByID(ctx, id)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("db error getting domain permission ignore %s: %w", id, err)
return nil, gtserror.NewErrorInternalError(err)
}
if permIgnore == nil {
err := fmt.Errorf("domain permission ignore %s not found", id)
return nil, gtserror.NewErrorNotFound(err, err.Error())
}
return p.apiDomainPerm(ctx, permIgnore, false)
}
// DomainPermissionIgnoresGet returns a page of
// DomainPermissionIgnores with the given parameters.
func (p *Processor) DomainPermissionIgnoresGet(
ctx context.Context,
domain string,
page *paging.Page,
) (*apimodel.PageableResponse, gtserror.WithCode) {
permIgnores, err := p.state.DB.GetDomainPermissionIgnores(
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(permIgnores)
if count == 0 {
return paging.EmptyResponse(), nil
}
// Get the lowest and highest
// ID values, used for paging.
lo := permIgnores[count-1].ID
hi := permIgnores[0].ID
// Convert each perm ignore to API model.
items := make([]any, len(permIgnores))
for i, permIgnore := range permIgnores {
apiPermIgnore, err := p.apiDomainPerm(ctx, permIgnore, false)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
items[i] = apiPermIgnore
}
// 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_ignores",
Next: page.Next(lo, hi),
Prev: page.Prev(lo, hi),
Query: query,
}), nil
}

View file

@ -1937,7 +1937,8 @@ func (c *Converter) ConversationToAPIConversation(
return apiConversation, nil return apiConversation, nil
} }
// DomainPermToAPIDomainPerm converts a gts model domin block or allow into an api domain permission. // DomainPermToAPIDomainPerm converts a gtsmodel domain block,
// allow, draft, or ignore into an api domain permission.
func (c *Converter) DomainPermToAPIDomainPerm( func (c *Converter) DomainPermToAPIDomainPerm(
ctx context.Context, ctx context.Context,
d gtsmodel.DomainPermission, d gtsmodel.DomainPermission,

View file

@ -4134,9 +4134,9 @@ http-parser-js@>=0.5.1:
integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
http-proxy-middleware@^2.0.6: http-proxy-middleware@^2.0.6:
version "2.0.6" version "2.0.7"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6"
integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==
dependencies: dependencies:
"@types/http-proxy" "^1.17.8" "@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1" http-proxy "^1.18.1"