weeeeeeeeeeeee
This commit is contained in:
parent
312cb8b9c7
commit
dc2a3351b0
|
@ -1130,6 +1130,78 @@ 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 use when parsing the permissions list.
|
||||||
|
example: text/csv
|
||||||
|
type: string
|
||||||
|
x-go-name: ContentType
|
||||||
|
count:
|
||||||
|
description: Count of domain permission entries discovered at URI on last (successful) fetch.
|
||||||
|
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
|
||||||
|
title:
|
||||||
|
description: Title of this list, as set by admin who created or updated it.f
|
||||||
|
example: really cool list of neato pals
|
||||||
|
type: string
|
||||||
|
x-go-name: Title
|
||||||
|
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:
|
||||||
|
@ -5997,6 +6069,70 @@ paths:
|
||||||
summary: Get domain permission exclude with the given ID.
|
summary: Get domain permission exclude with the given ID.
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/api/v1/admin/domain_permission_subscriptions:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- multipart/form-data
|
||||||
|
- application/json
|
||||||
|
operationId: domainPermissionSubscriptionCreate
|
||||||
|
parameters:
|
||||||
|
- description: Optional title for this subscription.
|
||||||
|
in: formData
|
||||||
|
name: title
|
||||||
|
type: string
|
||||||
|
- description: Type of permissions to create by parsing the targeted file/list. One of "allow" or "block".
|
||||||
|
in: formData
|
||||||
|
name: permission_type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- default: true
|
||||||
|
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. Defaults to "true".
|
||||||
|
in: formData
|
||||||
|
name: as_draft
|
||||||
|
type: boolean
|
||||||
|
- description: URI to call in order to fetch the permissions list.
|
||||||
|
in: formData
|
||||||
|
name: uri
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: MIME content type to use when parsing the permissions list. One of "text/plain", "text/csv", and "application/json".
|
||||||
|
in: formData
|
||||||
|
name: content_type
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Optional basic auth username to provide when fetching given uri. If set, will be transmitted along with `fetch_password` when doing the fetch.
|
||||||
|
in: formData
|
||||||
|
name: fetch_username
|
||||||
|
type: string
|
||||||
|
- description: Optional basic auth password to provide when fetching given uri. If set, will be transmitted along with `fetch_username` when doing the fetch.
|
||||||
|
in: formData
|
||||||
|
name: fetch_password
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The newly created domain permission subscription.
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domainPermissionSubscription'
|
||||||
|
"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 subscription with the given parameters.
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/api/v1/admin/email/test:
|
/api/v1/admin/email/test:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
@ -28,43 +28,45 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BasePath = "/v1/admin"
|
BasePath = "/v1/admin"
|
||||||
EmojiPath = BasePath + "/custom_emojis"
|
EmojiPath = BasePath + "/custom_emojis"
|
||||||
EmojiPathWithID = EmojiPath + "/:" + apiutil.IDKey
|
EmojiPathWithID = EmojiPath + "/:" + apiutil.IDKey
|
||||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||||
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"
|
DomainPermissionDraftsPath = BasePath + "/domain_permission_drafts"
|
||||||
DomainPermissionDraftsPathWithID = DomainPermissionDraftsPath + "/:" + apiutil.IDKey
|
DomainPermissionDraftsPathWithID = DomainPermissionDraftsPath + "/:" + apiutil.IDKey
|
||||||
DomainPermissionDraftAcceptPath = DomainPermissionDraftsPathWithID + "/accept"
|
DomainPermissionDraftAcceptPath = DomainPermissionDraftsPathWithID + "/accept"
|
||||||
DomainPermissionDraftRemovePath = DomainPermissionDraftsPathWithID + "/remove"
|
DomainPermissionDraftRemovePath = DomainPermissionDraftsPathWithID + "/remove"
|
||||||
DomainPermissionExcludesPath = BasePath + "/domain_permission_excludes"
|
DomainPermissionExcludesPath = BasePath + "/domain_permission_excludes"
|
||||||
DomainPermissionExcludesPathWithID = DomainPermissionExcludesPath + "/:" + apiutil.IDKey
|
DomainPermissionExcludesPathWithID = DomainPermissionExcludesPath + "/:" + apiutil.IDKey
|
||||||
DomainKeysExpirePath = BasePath + "/domain_keys_expire"
|
DomainPermissionSubscriptionsPath = BasePath + "/domain_permission_subscriptions"
|
||||||
HeaderAllowsPath = BasePath + "/header_allows"
|
DomainPermissionSubscriptionsPathWithID = DomainPermissionSubscriptionsPath + "/:" + apiutil.IDKey
|
||||||
HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey
|
DomainKeysExpirePath = BasePath + "/domain_keys_expire"
|
||||||
HeaderBlocksPath = BasePath + "/header_blocks"
|
HeaderAllowsPath = BasePath + "/header_allows"
|
||||||
HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + apiutil.IDKey
|
HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey
|
||||||
AccountsV1Path = BasePath + "/accounts"
|
HeaderBlocksPath = BasePath + "/header_blocks"
|
||||||
AccountsV2Path = "/v2/admin/accounts"
|
HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + apiutil.IDKey
|
||||||
AccountsPathWithID = AccountsV1Path + "/:" + apiutil.IDKey
|
AccountsV1Path = BasePath + "/accounts"
|
||||||
AccountsActionPath = AccountsPathWithID + "/action"
|
AccountsV2Path = "/v2/admin/accounts"
|
||||||
AccountsApprovePath = AccountsPathWithID + "/approve"
|
AccountsPathWithID = AccountsV1Path + "/:" + apiutil.IDKey
|
||||||
AccountsRejectPath = AccountsPathWithID + "/reject"
|
AccountsActionPath = AccountsPathWithID + "/action"
|
||||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
AccountsApprovePath = AccountsPathWithID + "/approve"
|
||||||
MediaRefetchPath = BasePath + "/media_refetch"
|
AccountsRejectPath = AccountsPathWithID + "/reject"
|
||||||
ReportsPath = BasePath + "/reports"
|
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||||
ReportsPathWithID = ReportsPath + "/:" + apiutil.IDKey
|
MediaRefetchPath = BasePath + "/media_refetch"
|
||||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
ReportsPath = BasePath + "/reports"
|
||||||
EmailPath = BasePath + "/email"
|
ReportsPathWithID = ReportsPath + "/:" + apiutil.IDKey
|
||||||
EmailTestPath = EmailPath + "/test"
|
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||||
InstanceRulesPath = BasePath + "/instance/rules"
|
EmailPath = BasePath + "/email"
|
||||||
InstanceRulesPathWithID = InstanceRulesPath + "/:" + apiutil.IDKey
|
EmailTestPath = EmailPath + "/test"
|
||||||
DebugPath = BasePath + "/debug"
|
InstanceRulesPath = BasePath + "/instance/rules"
|
||||||
DebugAPUrlPath = DebugPath + "/apurl"
|
InstanceRulesPathWithID = InstanceRulesPath + "/:" + apiutil.IDKey
|
||||||
DebugClearCachesPath = DebugPath + "/caches/clear"
|
DebugPath = BasePath + "/debug"
|
||||||
|
DebugAPUrlPath = DebugPath + "/apurl"
|
||||||
|
DebugClearCachesPath = DebugPath + "/caches/clear"
|
||||||
|
|
||||||
FilterQueryKey = "filter"
|
FilterQueryKey = "filter"
|
||||||
MaxShortcodeDomainKey = "max_shortcode_domain"
|
MaxShortcodeDomainKey = "max_shortcode_domain"
|
||||||
|
@ -118,6 +120,11 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
||||||
attachHandler(http.MethodGet, DomainPermissionExcludesPathWithID, m.DomainPermissionExcludeGETHandler)
|
attachHandler(http.MethodGet, DomainPermissionExcludesPathWithID, m.DomainPermissionExcludeGETHandler)
|
||||||
attachHandler(http.MethodDelete, DomainPermissionExcludesPathWithID, m.DomainPermissionExcludeDELETEHandler)
|
attachHandler(http.MethodDelete, DomainPermissionExcludesPathWithID, m.DomainPermissionExcludeDELETEHandler)
|
||||||
|
|
||||||
|
// domain permission subscriptions stuff
|
||||||
|
attachHandler(http.MethodPost, DomainPermissionSubscriptionsPath, m.DomainPermissionSubscriptionPOSTHandler)
|
||||||
|
attachHandler(http.MethodGet, DomainPermissionSubscriptionsPath, m.DomainPermissionSubscriptionsGETHandler)
|
||||||
|
attachHandler(http.MethodGet, DomainPermissionSubscriptionsPathWithID, m.DomainPermissionSubscriptionGETHandler)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
|
@ -302,3 +302,45 @@ func (m *Module) getDomainPermissions(
|
||||||
|
|
||||||
apiutil.JSON(c, http.StatusOK, domainPerm)
|
apiutil.JSON(c, http.StatusOK, domainPerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseDomainPermissionType is a util function to parse i
|
||||||
|
// to a DomainPermissionType, or return a suitable error.
|
||||||
|
func parseDomainPermissionType(i string) (
|
||||||
|
permType gtsmodel.DomainPermissionType,
|
||||||
|
errWithCode gtserror.WithCode,
|
||||||
|
) {
|
||||||
|
if i == "" {
|
||||||
|
const errText = "permission_type not set, must be one of block or allow"
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
permType = gtsmodel.ParseDomainPermissionType(i)
|
||||||
|
if permType == gtsmodel.DomainPermissionUnknown {
|
||||||
|
var errText = fmt.Sprintf("permission_type %s not recognized, must be one of block or allow", i)
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDomainPermSubContentType is a util function to parse i
|
||||||
|
// to a DomainPermSubContentType, or return a suitable error.
|
||||||
|
func parseDomainPermSubContentType(i string) (
|
||||||
|
contentType gtsmodel.DomainPermSubContentType,
|
||||||
|
errWithCode gtserror.WithCode,
|
||||||
|
) {
|
||||||
|
if i == "" {
|
||||||
|
const errText = "content_type not set, must be one of text/csv, text/plain or application/json"
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType = gtsmodel.NewDomainPermSubContentType(i)
|
||||||
|
if contentType == gtsmodel.DomainPermSubContentTypeUnknown {
|
||||||
|
var errText = fmt.Sprintf("content_type %s not recognized, must be one of text/csv, text/plain or application/json", i)
|
||||||
|
errWithCode = gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,24 +135,8 @@ func (m *Module) DomainPermissionDraftsPOSTHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
permType, errWithCode := parseDomainPermissionType(form.PermissionType)
|
||||||
permType gtsmodel.DomainPermissionType
|
if errWithCode != nil {
|
||||||
errText string
|
|
||||||
)
|
|
||||||
|
|
||||||
switch pt := form.PermissionType; pt {
|
|
||||||
case "block":
|
|
||||||
permType = gtsmodel.DomainPermissionBlock
|
|
||||||
case "allow":
|
|
||||||
permType = gtsmodel.DomainPermissionAllow
|
|
||||||
case "":
|
|
||||||
errText = "permission_type not set, must be one of block or allow"
|
|
||||||
default:
|
|
||||||
errText = fmt.Sprintf("permission_type %s not recognized, must be one of block or allow", pt)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errText != "" {
|
|
||||||
errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
|
||||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
// 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 (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
|
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_subscriptions domainPermissionSubscriptionCreate
|
||||||
|
//
|
||||||
|
// Create a domain permission subscription with the given parameters.
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// tags:
|
||||||
|
// - admin
|
||||||
|
//
|
||||||
|
// consumes:
|
||||||
|
// - multipart/form-data
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// parameters:
|
||||||
|
// -
|
||||||
|
// name: title
|
||||||
|
// in: formData
|
||||||
|
// description: Optional title for this subscription.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: permission_type
|
||||||
|
// required: true
|
||||||
|
// in: formData
|
||||||
|
// description: >-
|
||||||
|
// Type of permissions to create by parsing the targeted file/list.
|
||||||
|
// One of "allow" or "block".
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: as_draft
|
||||||
|
// in: formData
|
||||||
|
// 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.
|
||||||
|
// Defaults to "true".
|
||||||
|
// type: boolean
|
||||||
|
// default: true
|
||||||
|
// -
|
||||||
|
// name: uri
|
||||||
|
// required: true
|
||||||
|
// in: formData
|
||||||
|
// description: URI to call in order to fetch the permissions list.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: content_type
|
||||||
|
// required: true
|
||||||
|
// in: formData
|
||||||
|
// description: >-
|
||||||
|
// MIME content type to use when parsing the permissions list.
|
||||||
|
// One of "text/plain", "text/csv", and "application/json".
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: fetch_username
|
||||||
|
// in: formData
|
||||||
|
// description: >-
|
||||||
|
// Optional basic auth username to provide when fetching given uri.
|
||||||
|
// If set, will be transmitted along with `fetch_password` when doing the fetch.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: fetch_password
|
||||||
|
// in: formData
|
||||||
|
// description: >-
|
||||||
|
// Optional basic auth password to provide when fetching given uri.
|
||||||
|
// If set, will be transmitted along with `fetch_username` when doing the fetch.
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// security:
|
||||||
|
// - OAuth2 Bearer:
|
||||||
|
// - admin
|
||||||
|
//
|
||||||
|
// responses:
|
||||||
|
// '200':
|
||||||
|
// description: The newly created domain permission subscription.
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/domainPermissionSubscription"
|
||||||
|
// '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) DomainPermissionSubscriptionPOSTHandler(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse + validate form.
|
||||||
|
form := new(apimodel.DomainPermissionSubscriptionRequest)
|
||||||
|
if err := c.ShouldBind(form); err != nil {
|
||||||
|
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure URI is set.
|
||||||
|
if form.URI == "" {
|
||||||
|
const errText = "uri must be set"
|
||||||
|
errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure URI is parseable.
|
||||||
|
uri, err := url.Parse(form.URI)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("invalid uri provided: %w", err)
|
||||||
|
errWithCode := gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize URI by converting back to string.
|
||||||
|
uriStr := uri.String()
|
||||||
|
|
||||||
|
// Content type must be set.
|
||||||
|
contentType, errWithCode := parseDomainPermSubContentType(form.ContentType)
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission type must be set.
|
||||||
|
permType, errWithCode := parseDomainPermissionType(form.PermissionType)
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default `as_draft` to true.
|
||||||
|
asDraft := util.PtrOrValue(form.AsDraft, true)
|
||||||
|
|
||||||
|
permSub, errWithCode := m.processor.Admin().DomainPermissionSubscriptionCreate(
|
||||||
|
c.Request.Context(),
|
||||||
|
authed.Account,
|
||||||
|
form.Title,
|
||||||
|
uriStr,
|
||||||
|
contentType,
|
||||||
|
permType,
|
||||||
|
asDraft,
|
||||||
|
form.FetchUsername,
|
||||||
|
form.FetchPassword,
|
||||||
|
)
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiutil.JSON(c, http.StatusOK, permSub)
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionGETHandler swagger:operation GET /api/v1/admin/domain_permission_subscriptions/{id} domainPermissionSubscriptionGet
|
||||||
|
//
|
||||||
|
// Get domain permission subscription with the given ID.
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// tags:
|
||||||
|
// - admin
|
||||||
|
//
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// parameters:
|
||||||
|
// -
|
||||||
|
// name: id
|
||||||
|
// required: true
|
||||||
|
// in: path
|
||||||
|
// description: ID of the domain permission subscription.
|
||||||
|
// type: string
|
||||||
|
//
|
||||||
|
// security:
|
||||||
|
// - OAuth2 Bearer:
|
||||||
|
// - admin
|
||||||
|
//
|
||||||
|
// responses:
|
||||||
|
// '200':
|
||||||
|
// description: Domain permission subscription.
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/domainPermissionSubscription"
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
|
func (m *Module) DomainPermissionSubscriptionGETHandler(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
|
||||||
|
}
|
||||||
|
|
||||||
|
permSub, errWithCode := m.processor.Admin().DomainPermissionSubscriptionGet(c.Request.Context(), id)
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiutil.JSON(c, http.StatusOK, permSub)
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
// 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 (
|
||||||
|
"errors"
|
||||||
|
"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/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionsGETHandler swagger:operation GET /api/v1/admin/domain_permission_subscriptions domainPermissionSubscriptionsGet
|
||||||
|
//
|
||||||
|
// View domain permission subscriptions.
|
||||||
|
//
|
||||||
|
// The subscriptions 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_subscriptions?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_subscriptions?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||||
|
// ````
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// tags:
|
||||||
|
// - admin
|
||||||
|
//
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
//
|
||||||
|
// parameters:
|
||||||
|
// -
|
||||||
|
// name: permission_type
|
||||||
|
// type: string
|
||||||
|
// description: Filter on "block" or "allow" type subscriptions.
|
||||||
|
// in: query
|
||||||
|
// -
|
||||||
|
// name: max_id
|
||||||
|
// 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: since_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: min_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: limit
|
||||||
|
// type: integer
|
||||||
|
// description: Number of items to return.
|
||||||
|
// default: 20
|
||||||
|
// minimum: 1
|
||||||
|
// maximum: 100
|
||||||
|
// in: query
|
||||||
|
//
|
||||||
|
// security:
|
||||||
|
// - OAuth2 Bearer:
|
||||||
|
// - admin
|
||||||
|
//
|
||||||
|
// responses:
|
||||||
|
// '200':
|
||||||
|
// description: Domain permission subscriptions.
|
||||||
|
// schema:
|
||||||
|
// type: array
|
||||||
|
// items:
|
||||||
|
// "$ref": "#/definitions/domainPermissionSubscription"
|
||||||
|
// headers:
|
||||||
|
// Link:
|
||||||
|
// type: string
|
||||||
|
// description: Links to the next and previous queries.
|
||||||
|
// '400':
|
||||||
|
// description: bad request
|
||||||
|
// '401':
|
||||||
|
// description: unauthorized
|
||||||
|
// '403':
|
||||||
|
// description: forbidden
|
||||||
|
// '404':
|
||||||
|
// description: not found
|
||||||
|
// '406':
|
||||||
|
// description: not acceptable
|
||||||
|
// '500':
|
||||||
|
// description: internal server error
|
||||||
|
func (m *Module) DomainPermissionSubscriptionsGETHandler(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
|
||||||
|
}
|
||||||
|
|
||||||
|
permType := c.Query(apiutil.DomainPermissionPermTypeKey)
|
||||||
|
switch permType {
|
||||||
|
case "", "block", "allow":
|
||||||
|
// No problem.
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Invalid.
|
||||||
|
text := fmt.Sprintf(
|
||||||
|
"permission_type %s not recognized, valid values are empty string, block, or allow",
|
||||||
|
permType,
|
||||||
|
)
|
||||||
|
errWithCode := gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page, errWithCode := paging.ParseIDPage(c, 1, 200, 20)
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, errWithCode := m.processor.Admin().DomainPermissionSubscriptionsGet(
|
||||||
|
c.Request.Context(),
|
||||||
|
gtsmodel.ParseDomainPermissionType(permType),
|
||||||
|
page,
|
||||||
|
)
|
||||||
|
if errWithCode != nil {
|
||||||
|
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.LinkHeader != "" {
|
||||||
|
c.Header("Link", resp.LinkHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiutil.JSON(c, http.StatusOK, resp.Items)
|
||||||
|
}
|
|
@ -99,3 +99,84 @@ type DomainKeysExpireRequest struct {
|
||||||
// hostname/domain to expire keys for.
|
// hostname/domain to expire keys for.
|
||||||
Domain string `form:"domain" json:"domain"`
|
Domain string `form:"domain" json:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
|
||||||
|
//
|
||||||
|
// swagger:model domainPermissionSubscription
|
||||||
|
type DomainPermissionSubscription struct {
|
||||||
|
// The ID of the domain permission subscription.
|
||||||
|
// example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||||
|
// readonly: true
|
||||||
|
ID string `json:"id"`
|
||||||
|
// Title of this subscription, as set by admin who created or updated it.
|
||||||
|
// example: really cool list of neato pals
|
||||||
|
Title string `json:"title"`
|
||||||
|
// The type of domain permission subscription (allow, block).
|
||||||
|
// example: block
|
||||||
|
PermissionType string `json:"permission_type"`
|
||||||
|
// 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
|
||||||
|
AsDraft bool `json:"as_draft"`
|
||||||
|
// Time at which the subscription was created (ISO 8601 Datetime).
|
||||||
|
// example: 2021-07-30T09:20:25+00:00
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
// ID of the account that created this subscription.
|
||||||
|
// example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||||
|
// readonly: true
|
||||||
|
CreatedBy string `json:"created_by"`
|
||||||
|
// URI to call in order to fetch the permissions list.
|
||||||
|
// example: https://www.example.org/blocklists/list1.csv
|
||||||
|
URI string `json:"uri"`
|
||||||
|
// MIME content type to use when parsing the permissions list.
|
||||||
|
// example: text/csv
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
// (Optional) username to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchUsername string `json:"fetch_username,omitempty"`
|
||||||
|
// (Optional) password to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchPassword string `json:"fetch_password,omitempty"`
|
||||||
|
// Time of the most recent fetch attempt (successful or otherwise) (ISO 8601 Datetime).
|
||||||
|
// example: 2021-07-30T09:20:25+00:00
|
||||||
|
// readonly: true
|
||||||
|
FetchedAt string `json:"fetched_at,omitempty"`
|
||||||
|
// Time of the most recent successful fetch (ISO 8601 Datetime).
|
||||||
|
// example: 2021-07-30T09:20:25+00:00
|
||||||
|
// readonly: true
|
||||||
|
SuccessfullyFetchedAt string `json:"successfully_fetched_at,omitempty"`
|
||||||
|
// 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
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
// Count of domain permission entries discovered at URI on last (successful) fetch.
|
||||||
|
// example: 53
|
||||||
|
// readonly: true
|
||||||
|
Count uint64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionRequest represents a request to create or update a domain permission subscription..
|
||||||
|
//
|
||||||
|
// swagger:ignore
|
||||||
|
type DomainPermissionSubscriptionRequest struct {
|
||||||
|
// Title of this subscription, as set by admin who created or updated it.
|
||||||
|
// example: really cool list of neato pals
|
||||||
|
Title string `form:"title" json:"title"`
|
||||||
|
// The type of domain permission subscription (allow, block).
|
||||||
|
// example: block
|
||||||
|
PermissionType string `form:"permission_type" json:"permission_type"`
|
||||||
|
// URI to call in order to fetch the permissions list.
|
||||||
|
// example: https://www.example.org/blocklists/list1.csv
|
||||||
|
URI string `form:"uri" json:"uri"`
|
||||||
|
// MIME content type to use when parsing the permissions list.
|
||||||
|
// example: text/csv
|
||||||
|
ContentType string `form:"content_type" json:"content_type"`
|
||||||
|
// 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
|
||||||
|
AsDraft *bool `form:"as_draft" json:"as_draft"`
|
||||||
|
// (Optional) username to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchUsername string `form:"fetch_username" json:"fetch_username"`
|
||||||
|
// (Optional) password to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchPassword string `form:"fetch_password" json:"fetch_password"`
|
||||||
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ func (c *Caches) Init() {
|
||||||
c.initDomainAllow()
|
c.initDomainAllow()
|
||||||
c.initDomainBlock()
|
c.initDomainBlock()
|
||||||
c.initDomainPermissionDraft()
|
c.initDomainPermissionDraft()
|
||||||
|
c.initDomainPermissionSubscription()
|
||||||
c.initDomainPermissionExclude()
|
c.initDomainPermissionExclude()
|
||||||
c.initEmoji()
|
c.initEmoji()
|
||||||
c.initEmojiCategory()
|
c.initEmojiCategory()
|
||||||
|
|
|
@ -70,6 +70,9 @@ type DBCaches struct {
|
||||||
// DomainPermissionDraft provides access to the domain permission draft database cache.
|
// DomainPermissionDraft provides access to the domain permission draft database cache.
|
||||||
DomainPermissionDraft StructCache[*gtsmodel.DomainPermissionDraft]
|
DomainPermissionDraft StructCache[*gtsmodel.DomainPermissionDraft]
|
||||||
|
|
||||||
|
// DomainPermissionSubscription provides access to the domain permission subscription database cache.
|
||||||
|
DomainPermissionSubscription StructCache[*gtsmodel.DomainPermissionSubscription]
|
||||||
|
|
||||||
// DomainPermissionExclude provides access to the domain permission exclude database cache.
|
// DomainPermissionExclude provides access to the domain permission exclude database cache.
|
||||||
DomainPermissionExclude *domain.Cache
|
DomainPermissionExclude *domain.Cache
|
||||||
|
|
||||||
|
@ -586,6 +589,37 @@ func (c *Caches) initDomainPermissionDraft() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Caches) initDomainPermissionSubscription() {
|
||||||
|
// Calculate maximum cache size.
|
||||||
|
cap := calculateResultCacheMax(
|
||||||
|
sizeofDomainPermissionSubscription(), // model in-mem size.
|
||||||
|
config.GetCacheDomainPermissionSubscriptionMemRation(),
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Infof(nil, "cache size = %d", cap)
|
||||||
|
|
||||||
|
copyF := func(d1 *gtsmodel.DomainPermissionSubscription) *gtsmodel.DomainPermissionSubscription {
|
||||||
|
d2 := new(gtsmodel.DomainPermissionSubscription)
|
||||||
|
*d2 = *d1
|
||||||
|
|
||||||
|
// Don't include ptr fields that
|
||||||
|
// will be populated separately.
|
||||||
|
d2.CreatedByAccount = nil
|
||||||
|
|
||||||
|
return d2
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DB.DomainPermissionSubscription.Init(structr.CacheConfig[*gtsmodel.DomainPermissionSubscription]{
|
||||||
|
Indices: []structr.IndexConfig{
|
||||||
|
{Fields: "ID"},
|
||||||
|
{Fields: "URI"},
|
||||||
|
},
|
||||||
|
MaxSize: cap,
|
||||||
|
IgnoreErr: ignoreErrors,
|
||||||
|
Copy: copyF,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Caches) initDomainPermissionExclude() {
|
func (c *Caches) initDomainPermissionExclude() {
|
||||||
c.DB.DomainPermissionExclude = new(domain.Cache)
|
c.DB.DomainPermissionExclude = new(domain.Cache)
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,6 +357,20 @@ func sizeofDomainPermissionDraft() uintptr {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sizeofDomainPermissionSubscription() uintptr {
|
||||||
|
return uintptr(size.Of(>smodel.DomainPermissionSubscription{
|
||||||
|
ID: exampleID,
|
||||||
|
CreatedAt: exampleTime,
|
||||||
|
PermissionType: gtsmodel.DomainPermissionBlock,
|
||||||
|
CreatedByAccountID: exampleID,
|
||||||
|
URI: exampleURI,
|
||||||
|
FetchUsername: "username",
|
||||||
|
FetchPassword: "password",
|
||||||
|
FetchedAt: exampleTime,
|
||||||
|
AsDraft: util.Ptr(true),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func sizeofEmoji() uintptr {
|
func sizeofEmoji() uintptr {
|
||||||
return uintptr(size.Of(>smodel.Emoji{
|
return uintptr(size.Of(>smodel.Emoji{
|
||||||
ID: exampleID,
|
ID: exampleID,
|
||||||
|
|
|
@ -196,59 +196,60 @@ type HTTPClientConfiguration struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheConfiguration struct {
|
type CacheConfiguration struct {
|
||||||
MemoryTarget bytesize.Size `name:"memory-target"`
|
MemoryTarget bytesize.Size `name:"memory-target"`
|
||||||
AccountMemRatio float64 `name:"account-mem-ratio"`
|
AccountMemRatio float64 `name:"account-mem-ratio"`
|
||||||
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
||||||
AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
|
AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
|
||||||
AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"`
|
AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"`
|
||||||
ApplicationMemRatio float64 `name:"application-mem-ratio"`
|
ApplicationMemRatio float64 `name:"application-mem-ratio"`
|
||||||
BlockMemRatio float64 `name:"block-mem-ratio"`
|
BlockMemRatio float64 `name:"block-mem-ratio"`
|
||||||
BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"`
|
BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"`
|
||||||
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
||||||
ClientMemRatio float64 `name:"client-mem-ratio"`
|
ClientMemRatio float64 `name:"client-mem-ratio"`
|
||||||
ConversationMemRatio float64 `name:"conversation-mem-ratio"`
|
ConversationMemRatio float64 `name:"conversation-mem-ratio"`
|
||||||
ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"`
|
ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"`
|
||||||
DomainPermissionDraftMemRation float64 `name:"domain-permission-draft-mem-ratio"`
|
DomainPermissionDraftMemRation float64 `name:"domain-permission-draft-mem-ratio"`
|
||||||
EmojiMemRatio float64 `name:"emoji-mem-ratio"`
|
DomainPermissionSubscriptionMemRation float64 `name:"domain-permission-subscription-mem-ratio"`
|
||||||
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
|
EmojiMemRatio float64 `name:"emoji-mem-ratio"`
|
||||||
FilterMemRatio float64 `name:"filter-mem-ratio"`
|
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
|
||||||
FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"`
|
FilterMemRatio float64 `name:"filter-mem-ratio"`
|
||||||
FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"`
|
FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"`
|
||||||
FollowMemRatio float64 `name:"follow-mem-ratio"`
|
FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"`
|
||||||
FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
|
FollowMemRatio float64 `name:"follow-mem-ratio"`
|
||||||
FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
|
FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
|
||||||
FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
|
FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
|
||||||
FollowingTagIDsMemRatio float64 `name:"following-tag-ids-mem-ratio"`
|
FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
|
||||||
InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
|
FollowingTagIDsMemRatio float64 `name:"following-tag-ids-mem-ratio"`
|
||||||
InstanceMemRatio float64 `name:"instance-mem-ratio"`
|
InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
|
||||||
InteractionRequestMemRatio float64 `name:"interaction-request-mem-ratio"`
|
InstanceMemRatio float64 `name:"instance-mem-ratio"`
|
||||||
ListMemRatio float64 `name:"list-mem-ratio"`
|
InteractionRequestMemRatio float64 `name:"interaction-request-mem-ratio"`
|
||||||
ListIDsMemRatio float64 `name:"list-ids-mem-ratio"`
|
ListMemRatio float64 `name:"list-mem-ratio"`
|
||||||
ListedIDsMemRatio float64 `name:"listed-ids-mem-ratio"`
|
ListIDsMemRatio float64 `name:"list-ids-mem-ratio"`
|
||||||
MarkerMemRatio float64 `name:"marker-mem-ratio"`
|
ListedIDsMemRatio float64 `name:"listed-ids-mem-ratio"`
|
||||||
MediaMemRatio float64 `name:"media-mem-ratio"`
|
MarkerMemRatio float64 `name:"marker-mem-ratio"`
|
||||||
MentionMemRatio float64 `name:"mention-mem-ratio"`
|
MediaMemRatio float64 `name:"media-mem-ratio"`
|
||||||
MoveMemRatio float64 `name:"move-mem-ratio"`
|
MentionMemRatio float64 `name:"mention-mem-ratio"`
|
||||||
NotificationMemRatio float64 `name:"notification-mem-ratio"`
|
MoveMemRatio float64 `name:"move-mem-ratio"`
|
||||||
PollMemRatio float64 `name:"poll-mem-ratio"`
|
NotificationMemRatio float64 `name:"notification-mem-ratio"`
|
||||||
PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`
|
PollMemRatio float64 `name:"poll-mem-ratio"`
|
||||||
PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"`
|
PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`
|
||||||
ReportMemRatio float64 `name:"report-mem-ratio"`
|
PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"`
|
||||||
SinBinStatusMemRatio float64 `name:"sin-bin-status-mem-ratio"`
|
ReportMemRatio float64 `name:"report-mem-ratio"`
|
||||||
StatusMemRatio float64 `name:"status-mem-ratio"`
|
SinBinStatusMemRatio float64 `name:"sin-bin-status-mem-ratio"`
|
||||||
StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"`
|
StatusMemRatio float64 `name:"status-mem-ratio"`
|
||||||
StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"`
|
StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"`
|
||||||
StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
|
StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"`
|
||||||
StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
|
StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
|
||||||
TagMemRatio float64 `name:"tag-mem-ratio"`
|
StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
|
||||||
ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"`
|
TagMemRatio float64 `name:"tag-mem-ratio"`
|
||||||
TokenMemRatio float64 `name:"token-mem-ratio"`
|
ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"`
|
||||||
TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
|
TokenMemRatio float64 `name:"token-mem-ratio"`
|
||||||
UserMemRatio float64 `name:"user-mem-ratio"`
|
TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
|
||||||
UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
|
UserMemRatio float64 `name:"user-mem-ratio"`
|
||||||
UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"`
|
UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
|
||||||
WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
|
UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"`
|
||||||
VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
|
WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
|
||||||
|
VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMap will marshal current Configuration into a map structure (useful for JSON/TOML/YAML).
|
// MarshalMap will marshal current Configuration into a map structure (useful for JSON/TOML/YAML).
|
||||||
|
|
|
@ -158,58 +158,59 @@ var Defaults = Configuration{
|
||||||
// when TODO items in the size.go source
|
// when TODO items in the size.go source
|
||||||
// file have been addressed, these should
|
// file have been addressed, these should
|
||||||
// be able to make some more sense :D
|
// be able to make some more sense :D
|
||||||
AccountMemRatio: 5,
|
AccountMemRatio: 5,
|
||||||
AccountNoteMemRatio: 1,
|
AccountNoteMemRatio: 1,
|
||||||
AccountSettingsMemRatio: 0.1,
|
AccountSettingsMemRatio: 0.1,
|
||||||
AccountStatsMemRatio: 2,
|
AccountStatsMemRatio: 2,
|
||||||
ApplicationMemRatio: 0.1,
|
ApplicationMemRatio: 0.1,
|
||||||
BlockMemRatio: 2,
|
BlockMemRatio: 2,
|
||||||
BlockIDsMemRatio: 3,
|
BlockIDsMemRatio: 3,
|
||||||
BoostOfIDsMemRatio: 3,
|
BoostOfIDsMemRatio: 3,
|
||||||
ClientMemRatio: 0.1,
|
ClientMemRatio: 0.1,
|
||||||
ConversationMemRatio: 1,
|
ConversationMemRatio: 1,
|
||||||
ConversationLastStatusIDsMemRatio: 2,
|
ConversationLastStatusIDsMemRatio: 2,
|
||||||
DomainPermissionDraftMemRation: 0.5,
|
DomainPermissionDraftMemRation: 0.5,
|
||||||
EmojiMemRatio: 3,
|
DomainPermissionSubscriptionMemRation: 0.5,
|
||||||
EmojiCategoryMemRatio: 0.1,
|
EmojiMemRatio: 3,
|
||||||
FilterMemRatio: 0.5,
|
EmojiCategoryMemRatio: 0.1,
|
||||||
FilterKeywordMemRatio: 0.5,
|
FilterMemRatio: 0.5,
|
||||||
FilterStatusMemRatio: 0.5,
|
FilterKeywordMemRatio: 0.5,
|
||||||
FollowMemRatio: 2,
|
FilterStatusMemRatio: 0.5,
|
||||||
FollowIDsMemRatio: 4,
|
FollowMemRatio: 2,
|
||||||
FollowRequestMemRatio: 2,
|
FollowIDsMemRatio: 4,
|
||||||
FollowRequestIDsMemRatio: 2,
|
FollowRequestMemRatio: 2,
|
||||||
FollowingTagIDsMemRatio: 2,
|
FollowRequestIDsMemRatio: 2,
|
||||||
InReplyToIDsMemRatio: 3,
|
FollowingTagIDsMemRatio: 2,
|
||||||
InstanceMemRatio: 1,
|
InReplyToIDsMemRatio: 3,
|
||||||
InteractionRequestMemRatio: 1,
|
InstanceMemRatio: 1,
|
||||||
ListMemRatio: 1,
|
InteractionRequestMemRatio: 1,
|
||||||
ListIDsMemRatio: 2,
|
ListMemRatio: 1,
|
||||||
ListedIDsMemRatio: 2,
|
ListIDsMemRatio: 2,
|
||||||
MarkerMemRatio: 0.5,
|
ListedIDsMemRatio: 2,
|
||||||
MediaMemRatio: 4,
|
MarkerMemRatio: 0.5,
|
||||||
MentionMemRatio: 2,
|
MediaMemRatio: 4,
|
||||||
MoveMemRatio: 0.1,
|
MentionMemRatio: 2,
|
||||||
NotificationMemRatio: 2,
|
MoveMemRatio: 0.1,
|
||||||
PollMemRatio: 1,
|
NotificationMemRatio: 2,
|
||||||
PollVoteMemRatio: 2,
|
PollMemRatio: 1,
|
||||||
PollVoteIDsMemRatio: 2,
|
PollVoteMemRatio: 2,
|
||||||
ReportMemRatio: 1,
|
PollVoteIDsMemRatio: 2,
|
||||||
SinBinStatusMemRatio: 0.5,
|
ReportMemRatio: 1,
|
||||||
StatusMemRatio: 5,
|
SinBinStatusMemRatio: 0.5,
|
||||||
StatusBookmarkMemRatio: 0.5,
|
StatusMemRatio: 5,
|
||||||
StatusBookmarkIDsMemRatio: 2,
|
StatusBookmarkMemRatio: 0.5,
|
||||||
StatusFaveMemRatio: 2,
|
StatusBookmarkIDsMemRatio: 2,
|
||||||
StatusFaveIDsMemRatio: 3,
|
StatusFaveMemRatio: 2,
|
||||||
TagMemRatio: 2,
|
StatusFaveIDsMemRatio: 3,
|
||||||
ThreadMuteMemRatio: 0.2,
|
TagMemRatio: 2,
|
||||||
TokenMemRatio: 0.75,
|
ThreadMuteMemRatio: 0.2,
|
||||||
TombstoneMemRatio: 0.5,
|
TokenMemRatio: 0.75,
|
||||||
UserMemRatio: 0.25,
|
TombstoneMemRatio: 0.5,
|
||||||
UserMuteMemRatio: 2,
|
UserMemRatio: 0.25,
|
||||||
UserMuteIDsMemRatio: 3,
|
UserMuteMemRatio: 2,
|
||||||
WebfingerMemRatio: 0.1,
|
UserMuteIDsMemRatio: 3,
|
||||||
VisibilityMemRatio: 2,
|
WebfingerMemRatio: 0.1,
|
||||||
|
VisibilityMemRatio: 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
HTTPClient: HTTPClientConfiguration{
|
HTTPClient: HTTPClientConfiguration{
|
||||||
|
|
|
@ -3187,6 +3187,37 @@ func SetCacheDomainPermissionDraftMemRation(v float64) {
|
||||||
global.SetCacheDomainPermissionDraftMemRation(v)
|
global.SetCacheDomainPermissionDraftMemRation(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCacheDomainPermissionSubscriptionMemRation safely fetches the Configuration value for state's 'Cache.DomainPermissionSubscriptionMemRation' field
|
||||||
|
func (st *ConfigState) GetCacheDomainPermissionSubscriptionMemRation() (v float64) {
|
||||||
|
st.mutex.RLock()
|
||||||
|
v = st.config.Cache.DomainPermissionSubscriptionMemRation
|
||||||
|
st.mutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCacheDomainPermissionSubscriptionMemRation safely sets the Configuration value for state's 'Cache.DomainPermissionSubscriptionMemRation' field
|
||||||
|
func (st *ConfigState) SetCacheDomainPermissionSubscriptionMemRation(v float64) {
|
||||||
|
st.mutex.Lock()
|
||||||
|
defer st.mutex.Unlock()
|
||||||
|
st.config.Cache.DomainPermissionSubscriptionMemRation = v
|
||||||
|
st.reloadToViper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheDomainPermissionSubscriptionMemRationFlag returns the flag name for the 'Cache.DomainPermissionSubscriptionMemRation' field
|
||||||
|
func CacheDomainPermissionSubscriptionMemRationFlag() string {
|
||||||
|
return "cache-domain-permission-subscription-mem-ratio"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheDomainPermissionSubscriptionMemRation safely fetches the value for global configuration 'Cache.DomainPermissionSubscriptionMemRation' field
|
||||||
|
func GetCacheDomainPermissionSubscriptionMemRation() float64 {
|
||||||
|
return global.GetCacheDomainPermissionSubscriptionMemRation()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCacheDomainPermissionSubscriptionMemRation safely sets the value for global configuration 'Cache.DomainPermissionSubscriptionMemRation' field
|
||||||
|
func SetCacheDomainPermissionSubscriptionMemRation(v float64) {
|
||||||
|
global.SetCacheDomainPermissionSubscriptionMemRation(v)
|
||||||
|
}
|
||||||
|
|
||||||
// GetCacheEmojiMemRatio safely fetches the Configuration value for state's 'Cache.EmojiMemRatio' field
|
// GetCacheEmojiMemRatio safely fetches the Configuration value for state's 'Cache.EmojiMemRatio' field
|
||||||
func (st *ConfigState) GetCacheEmojiMemRatio() (v float64) {
|
func (st *ConfigState) GetCacheEmojiMemRatio() (v float64) {
|
||||||
st.mutex.RLock()
|
st.mutex.RLock()
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
up := func(ctx context.Context, db *bun.DB) error {
|
||||||
|
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||||
|
// Create `domain_permission_subscriptions`.
|
||||||
|
if _, err := tx.
|
||||||
|
NewCreateTable().
|
||||||
|
Model((*gtsmodel.DomainPermissionSubscription)(nil)).
|
||||||
|
IfNotExists().
|
||||||
|
Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indexes. Indices. Indie sexes.
|
||||||
|
for table, indexes := range map[string]map[string][]string{
|
||||||
|
"domain_permission_subscriptions": {
|
||||||
|
"domain_permission_subscriptions_permission_type_idx": {"permission_type"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
for index, columns := range indexes {
|
||||||
|
if _, err := tx.
|
||||||
|
NewCreateIndex().
|
||||||
|
Table(table).
|
||||||
|
Index(index).
|
||||||
|
Column(columns...).
|
||||||
|
IfNotExists().
|
||||||
|
Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
down := func(ctx context.Context, db *bun.DB) error {
|
||||||
|
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Migrations.Register(up, down); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,4 +132,25 @@ type Domain interface {
|
||||||
|
|
||||||
// IsDomainPermissionExcluded returns true if the given domain matches in the list of excluded domains.
|
// IsDomainPermissionExcluded returns true if the given domain matches in the list of excluded domains.
|
||||||
IsDomainPermissionExcluded(ctx context.Context, domain string) (bool, error)
|
IsDomainPermissionExcluded(ctx context.Context, domain string) (bool, error)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Domain permission subscription stuff.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// GetDomainPermissionSubscriptionByID gets one DomainPermissionSubscription with the given ID.
|
||||||
|
GetDomainPermissionSubscriptionByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionSubscription, error)
|
||||||
|
|
||||||
|
// GetDomainPermissionSubscriptions returns a page of
|
||||||
|
// DomainPermissionSubscriptions using the given parameters.
|
||||||
|
GetDomainPermissionSubscriptions(
|
||||||
|
ctx context.Context,
|
||||||
|
permType gtsmodel.DomainPermissionType,
|
||||||
|
page *paging.Page,
|
||||||
|
) ([]*gtsmodel.DomainPermissionSubscription, error)
|
||||||
|
|
||||||
|
// PutDomainPermissionSubscription stores one DomainPermissionSubscription.
|
||||||
|
PutDomainPermissionSubscription(ctx context.Context, permSub *gtsmodel.DomainPermissionSubscription) error
|
||||||
|
|
||||||
|
// DeleteDomainPermissionSubscription deletes one DomainPermissionSubscription with the given id.
|
||||||
|
DeleteDomainPermissionSubscription(ctx context.Context, id string) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package gtsmodel
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type DomainPermissionSubscription struct {
|
||||||
|
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
|
||||||
|
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
|
||||||
|
Title string `bun:",nullzero,unique"` // Moderator-set title for this list.
|
||||||
|
PermissionType DomainPermissionType `bun:",nullzero,notnull"` // Permission type of the subscription.
|
||||||
|
AsDraft *bool `bun:",nullzero,notnull,default:true"` // Create domain permission entries resulting from this subscription as drafts.
|
||||||
|
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription.
|
||||||
|
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
|
||||||
|
URI string `bun:",nullzero,notnull,unique"` // URI of the domain permission list.
|
||||||
|
ContentType DomainPermSubContentType `bun:",nullzero,notnull"` // Content type to expect from the URI.
|
||||||
|
FetchUsername string `bun:",nullzero"` // Username to send when doing a GET of URI using basic auth.
|
||||||
|
FetchPassword string `bun:",nullzero"` // Password to send when doing a GET of URI using basic auth.
|
||||||
|
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // Time when fetch of URI was last attempted.
|
||||||
|
SuccessfullyFetchedAt time.Time `bun:"type:timestamptz,nullzero"` // Time when the domain permission list was last *successfuly* fetched, to be transmitted as If-Modified-Since header.
|
||||||
|
ETag string `bun:",nullzero"` // Etag last received from the server (if any) on successful fetch.
|
||||||
|
Error string `bun:",nullzero"` // If latest fetch attempt errored, this field stores the error message. Cleared on latest successful fetch.
|
||||||
|
Count uint64 `bun:""` // Count of domain permission entries discovered at URI.
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainPermSubContentType enumType
|
||||||
|
|
||||||
|
const (
|
||||||
|
DomainPermSubContentTypeUnknown DomainPermSubContentType = 0 // ???
|
||||||
|
DomainPermSubContentTypeCSV DomainPermSubContentType = 1 // text/csv
|
||||||
|
DomainPermSubContentTypeJSON DomainPermSubContentType = 2 // application/json
|
||||||
|
DomainPermSubContentTypePlain DomainPermSubContentType = 3 // text/plain
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p DomainPermSubContentType) String() string {
|
||||||
|
switch p {
|
||||||
|
case DomainPermSubContentTypeCSV:
|
||||||
|
return "text/csv"
|
||||||
|
case DomainPermSubContentTypeJSON:
|
||||||
|
return "application/json"
|
||||||
|
case DomainPermSubContentTypePlain:
|
||||||
|
return "text/plain"
|
||||||
|
default:
|
||||||
|
panic("unknown content type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDomainPermSubContentType(in string) DomainPermSubContentType {
|
||||||
|
switch in {
|
||||||
|
case "text/csv":
|
||||||
|
return DomainPermSubContentTypeCSV
|
||||||
|
case "application/json":
|
||||||
|
return DomainPermSubContentTypeCSV
|
||||||
|
case "text/plain":
|
||||||
|
return DomainPermSubContentTypeCSV
|
||||||
|
default:
|
||||||
|
return DomainPermSubContentTypeUnknown
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionGet returns one
|
||||||
|
// domain permission subscription with the given id.
|
||||||
|
func (p *Processor) DomainPermissionSubscriptionGet(
|
||||||
|
ctx context.Context,
|
||||||
|
id string,
|
||||||
|
) (*apimodel.DomainPermissionSubscription, gtserror.WithCode) {
|
||||||
|
permSubscription, err := p.state.DB.GetDomainPermissionSubscriptionByID(ctx, id)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error getting domain permission subscription %s: %w", id, err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if permSubscription == nil {
|
||||||
|
err := fmt.Errorf("domain permission subscription %s not found", id)
|
||||||
|
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.apiDomainPermSub(ctx, permSubscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionsGet returns a page of
|
||||||
|
// DomainPermissionSubscriptions with the given parameters.
|
||||||
|
func (p *Processor) DomainPermissionSubscriptionsGet(
|
||||||
|
ctx context.Context,
|
||||||
|
permType gtsmodel.DomainPermissionType,
|
||||||
|
page *paging.Page,
|
||||||
|
) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||||
|
permSubs, err := p.state.DB.GetDomainPermissionSubscriptions(
|
||||||
|
ctx,
|
||||||
|
permType,
|
||||||
|
page,
|
||||||
|
)
|
||||||
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
|
err := gtserror.Newf("db error: %w", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := len(permSubs)
|
||||||
|
if count == 0 {
|
||||||
|
return paging.EmptyResponse(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the lowest and highest
|
||||||
|
// ID values, used for paging.
|
||||||
|
lo := permSubs[count-1].ID
|
||||||
|
hi := permSubs[0].ID
|
||||||
|
|
||||||
|
// Convert each perm sub to API model.
|
||||||
|
items := make([]any, len(permSubs))
|
||||||
|
for i, permSub := range permSubs {
|
||||||
|
apiPermSub, err := p.converter.DomainPermSubToAPIDomainPermSub(ctx, permSub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
items[i] = apiPermSub
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble next/prev page queries.
|
||||||
|
query := make(url.Values, 1)
|
||||||
|
if permType != gtsmodel.DomainPermissionUnknown {
|
||||||
|
query.Set(apiutil.DomainPermissionPermTypeKey, permType.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return paging.PackageResponse(paging.ResponseParams{
|
||||||
|
Items: items,
|
||||||
|
Path: "/api/v1/admin/domain_permission_subscriptions",
|
||||||
|
Next: page.Next(lo, hi),
|
||||||
|
Prev: page.Prev(lo, hi),
|
||||||
|
Query: query,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Processor) DomainPermissionSubscriptionCreate(
|
||||||
|
ctx context.Context,
|
||||||
|
acct *gtsmodel.Account,
|
||||||
|
title string,
|
||||||
|
uri string,
|
||||||
|
contentType gtsmodel.DomainPermSubContentType,
|
||||||
|
permType gtsmodel.DomainPermissionType,
|
||||||
|
asDraft bool,
|
||||||
|
fetchUsername string,
|
||||||
|
fetchPassword string,
|
||||||
|
) (*apimodel.DomainPermissionSubscription, gtserror.WithCode) {
|
||||||
|
permSub := >smodel.DomainPermissionSubscription{
|
||||||
|
ID: id.NewULID(),
|
||||||
|
Title: title,
|
||||||
|
PermissionType: permType,
|
||||||
|
AsDraft: &asDraft,
|
||||||
|
CreatedByAccountID: acct.ID,
|
||||||
|
CreatedByAccount: acct,
|
||||||
|
URI: uri,
|
||||||
|
ContentType: contentType,
|
||||||
|
FetchUsername: fetchUsername,
|
||||||
|
FetchPassword: fetchPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.state.DB.PutDomainPermissionSubscription(ctx, permSub)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, db.ErrAlreadyExists) {
|
||||||
|
// Unique constraint conflict.
|
||||||
|
const errText = "domain permission subscription with given URI or title already exists"
|
||||||
|
return nil, gtserror.NewErrorConflict(errors.New(errText), errText)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real database error.
|
||||||
|
err := gtserror.Newf("db error putting domain permission subscription: %w", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.apiDomainPermSub(ctx, permSub)
|
||||||
|
}
|
|
@ -115,3 +115,19 @@ func (p *Processor) apiDomainPerm(
|
||||||
|
|
||||||
return apiDomainPerm, nil
|
return apiDomainPerm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apiDomainPermSub is a cheeky shortcut for returning the
|
||||||
|
// API version of the given domain permission subscription,
|
||||||
|
// or an appropriate error if something goes wrong.
|
||||||
|
func (p *Processor) apiDomainPermSub(
|
||||||
|
ctx context.Context,
|
||||||
|
domainPermSub *gtsmodel.DomainPermissionSubscription,
|
||||||
|
) (*apimodel.DomainPermissionSubscription, gtserror.WithCode) {
|
||||||
|
apiDomainPermSub, err := p.converter.DomainPermSubToAPIDomainPermSub(ctx, domainPermSub)
|
||||||
|
if err != nil {
|
||||||
|
err := gtserror.NewfAt(3, "error converting domain permission subscription to api model: %w", err)
|
||||||
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiDomainPermSub, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2004,6 +2004,47 @@ func (c *Converter) DomainPermToAPIDomainPerm(
|
||||||
return domainPerm, nil
|
return domainPerm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Converter) DomainPermSubToAPIDomainPermSub(
|
||||||
|
ctx context.Context,
|
||||||
|
d *gtsmodel.DomainPermissionSubscription,
|
||||||
|
) (*apimodel.DomainPermissionSubscription, error) {
|
||||||
|
// URI may be in Punycode,
|
||||||
|
// de-punify it just in case.
|
||||||
|
uri, err := util.DePunify(d.URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gtserror.Newf("error de-punifying URI %s: %w", d.URI, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fetchedAt string
|
||||||
|
successfullyFetchedAt string
|
||||||
|
)
|
||||||
|
|
||||||
|
if !d.FetchedAt.IsZero() {
|
||||||
|
fetchedAt = util.FormatISO8601(d.FetchedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.SuccessfullyFetchedAt.IsZero() {
|
||||||
|
successfullyFetchedAt = util.FormatISO8601(d.SuccessfullyFetchedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apimodel.DomainPermissionSubscription{
|
||||||
|
ID: d.ID,
|
||||||
|
Title: d.Title,
|
||||||
|
PermissionType: d.PermissionType.String(),
|
||||||
|
AsDraft: *d.AsDraft,
|
||||||
|
CreatedBy: d.CreatedByAccountID,
|
||||||
|
CreatedAt: util.FormatISO8601(d.CreatedAt),
|
||||||
|
URI: uri,
|
||||||
|
ContentType: d.ContentType.String(),
|
||||||
|
FetchUsername: d.FetchUsername,
|
||||||
|
FetchPassword: d.FetchPassword,
|
||||||
|
FetchedAt: fetchedAt,
|
||||||
|
SuccessfullyFetchedAt: successfullyFetchedAt,
|
||||||
|
Error: d.Error,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReportToAPIReport converts a gts model report into an api model report, for serving at /api/v1/reports
|
// ReportToAPIReport converts a gts model report into an api model report, for serving at /api/v1/reports
|
||||||
func (c *Converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) (*apimodel.Report, error) {
|
func (c *Converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) (*apimodel.Report, error) {
|
||||||
report := &apimodel.Report{
|
report := &apimodel.Report{
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { gtsApi } from "../../gts-api";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
DomainPermSub,
|
||||||
|
DomainPermSubCreateParams,
|
||||||
|
DomainPermSubSearchParams,
|
||||||
|
DomainPermSubSearchResp,
|
||||||
|
} from "../../../types/domain-permission";
|
||||||
|
import parse from "parse-link-header";
|
||||||
|
|
||||||
|
const extended = gtsApi.injectEndpoints({
|
||||||
|
endpoints: (build) => ({
|
||||||
|
searchDomainPermissionSubscriptions: build.query<DomainPermSubSearchResp, DomainPermSubSearchParams>({
|
||||||
|
query: (form) => {
|
||||||
|
const params = new(URLSearchParams);
|
||||||
|
Object.entries(form).forEach(([k, v]) => {
|
||||||
|
if (v !== undefined) {
|
||||||
|
params.append(k, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let query = "";
|
||||||
|
if (params.size !== 0) {
|
||||||
|
query = `?${params.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: `/api/v1/admin/domain_permission_subscriptions${query}`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// Headers required for paging.
|
||||||
|
transformResponse: (apiResp: DomainPermSub[], meta) => {
|
||||||
|
const subs = apiResp;
|
||||||
|
const linksStr = meta?.response?.headers.get("Link");
|
||||||
|
const links = parse(linksStr);
|
||||||
|
return { subs, links };
|
||||||
|
},
|
||||||
|
// Only provide TRANSFORMED tag id since this model is not the same
|
||||||
|
// as getDomainPermissionSubscription model (due to transformResponse).
|
||||||
|
providesTags: [{ type: "DomainPermissionSubscription", id: "TRANSFORMED" }]
|
||||||
|
}),
|
||||||
|
|
||||||
|
getDomainPermissionSubscription: build.query<DomainPermSub, string>({
|
||||||
|
query: (id) => ({
|
||||||
|
url: `/api/v1/admin/domain_permission_subscriptions/${id}`
|
||||||
|
}),
|
||||||
|
providesTags: (_result, _error, id) => [
|
||||||
|
{ type: 'DomainPermissionSubscription', id }
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
createDomainPermissionSubscription: build.mutation<DomainPermSub, DomainPermSubCreateParams>({
|
||||||
|
query: (formData) => ({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/admin/domain_permission_subscriptions`,
|
||||||
|
asForm: true,
|
||||||
|
body: formData,
|
||||||
|
discardEmpty: true
|
||||||
|
}),
|
||||||
|
invalidatesTags: [{ type: "DomainPermissionSubscription", id: "TRANSFORMED" }],
|
||||||
|
}),
|
||||||
|
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View domain permission subscriptions.
|
||||||
|
*/
|
||||||
|
const useLazySearchDomainPermissionSubscriptionsQuery = extended.useLazySearchDomainPermissionSubscriptionsQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get domain permission subscription with the given ID.
|
||||||
|
*/
|
||||||
|
const useGetDomainPermissionSubscriptionQuery = extended.useGetDomainPermissionSubscriptionQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a domain permission subscription with the given parameters.
|
||||||
|
*/
|
||||||
|
const useCreateDomainPermissionSubscriptionMutation = extended.useCreateDomainPermissionSubscriptionMutation;
|
||||||
|
|
||||||
|
export {
|
||||||
|
useLazySearchDomainPermissionSubscriptionsQuery,
|
||||||
|
useGetDomainPermissionSubscriptionQuery,
|
||||||
|
useCreateDomainPermissionSubscriptionMutation,
|
||||||
|
};
|
|
@ -170,7 +170,8 @@ export const gtsApi = createApi({
|
||||||
"DefaultInteractionPolicies",
|
"DefaultInteractionPolicies",
|
||||||
"InteractionRequest",
|
"InteractionRequest",
|
||||||
"DomainPermissionDraft",
|
"DomainPermissionDraft",
|
||||||
"DomainPermissionExclude"
|
"DomainPermissionExclude",
|
||||||
|
"DomainPermissionSubscription"
|
||||||
],
|
],
|
||||||
endpoints: (build) => ({
|
endpoints: (build) => ({
|
||||||
instanceV1: build.query<InstanceV1, void>({
|
instanceV1: build.query<InstanceV1, void>({
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import typia from "typia";
|
import typia from "typia";
|
||||||
import { PermType } from "./perm";
|
import { PermType } from "./perm";
|
||||||
import { Links } from "parse-link-header";
|
import { Links } from "parse-link-header";
|
||||||
|
import { PermSubContentType } from "./permsubcontenttype";
|
||||||
|
|
||||||
export const validateDomainPerms = typia.createValidate<DomainPerm[]>();
|
export const validateDomainPerms = typia.createValidate<DomainPerm[]>();
|
||||||
|
|
||||||
|
@ -213,3 +214,131 @@ export interface DomainPermExcludeCreateParams {
|
||||||
*/
|
*/
|
||||||
private_comment?: string;
|
private_comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API model of one domain permission susbcription.
|
||||||
|
*/
|
||||||
|
export interface DomainPermSub {
|
||||||
|
/**
|
||||||
|
* The ID of the domain permission subscription.
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Time at which the subscription was created (ISO 8601 Datetime).
|
||||||
|
*/
|
||||||
|
created_at: string;
|
||||||
|
/**
|
||||||
|
* Title of this subscription, as set by admin who created or updated it.
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* The type of domain permission subscription (allow, block).
|
||||||
|
*/
|
||||||
|
permission_type: PermType;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
as_draft: boolean;
|
||||||
|
/**
|
||||||
|
* ID of the account that created this subscription.
|
||||||
|
*/
|
||||||
|
created_by: string;
|
||||||
|
/**
|
||||||
|
* URI to call in order to fetch the permissions list.
|
||||||
|
*/
|
||||||
|
uri: string;
|
||||||
|
/**
|
||||||
|
* MIME content type to use when parsing the permissions list.
|
||||||
|
*/
|
||||||
|
content_type: PermSubContentType;
|
||||||
|
/**
|
||||||
|
* (Optional) username to set for basic auth when doing a fetch of URI.
|
||||||
|
*/
|
||||||
|
fetch_username?: string;
|
||||||
|
/**
|
||||||
|
* (Optional) password to set for basic auth when doing a fetch of URI.
|
||||||
|
*/
|
||||||
|
fetch_password?: string;
|
||||||
|
/**
|
||||||
|
* Time at which the most recent fetch was attempted (ISO 8601 Datetime).
|
||||||
|
*/
|
||||||
|
fetched_at?: string;
|
||||||
|
/**
|
||||||
|
* If most recent fetch attempt failed, this field will contain an error message related to the fetch attempt.
|
||||||
|
*/
|
||||||
|
error?: string;
|
||||||
|
/**
|
||||||
|
* Count of domain permission entries discovered at URI on last (successful) fetch.
|
||||||
|
*/
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for GET to /api/v1/admin/domain_permission_subscriptions.
|
||||||
|
*/
|
||||||
|
export interface DomainPermSubSearchParams {
|
||||||
|
/**
|
||||||
|
* Return only block or allow subscriptions.
|
||||||
|
*/
|
||||||
|
permission_type?: PermType;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
max_id?: string;
|
||||||
|
/**
|
||||||
|
* Return only items *NEWER* than the given since ID.
|
||||||
|
* The item with the specified ID will not be included in the response.
|
||||||
|
*/
|
||||||
|
since_id?: string;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
min_id?: string;
|
||||||
|
/**
|
||||||
|
* Number of items to return.
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for POST to /api/v1/admin/domain_permission_subscriptions.
|
||||||
|
*/
|
||||||
|
export interface DomainPermSubCreateParams {
|
||||||
|
/**
|
||||||
|
* Title of this subscription, as set by admin who created or updated it.
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
/**
|
||||||
|
* The type of domain permission subscription (allow, block).
|
||||||
|
*/
|
||||||
|
permission_type: PermType;
|
||||||
|
/**
|
||||||
|
* URI to call in order to fetch the permissions list.
|
||||||
|
*/
|
||||||
|
uri: string;
|
||||||
|
/**
|
||||||
|
* MIME content type to use when parsing the permissions list.
|
||||||
|
*/
|
||||||
|
content_type: PermSubContentType;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
as_draft?: boolean;
|
||||||
|
/**
|
||||||
|
* (Optional) username to set for basic auth when doing a fetch of URI.
|
||||||
|
*/
|
||||||
|
fetch_username?: string;
|
||||||
|
/**
|
||||||
|
* (Optional) password to set for basic auth when doing a fetch of URI.
|
||||||
|
*/
|
||||||
|
fetch_password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DomainPermSubSearchResp {
|
||||||
|
subs: DomainPermSub[];
|
||||||
|
links: Links | null;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type PermSubContentType = "text/plain" | "text/csv" | "application/json";
|
|
@ -46,3 +46,22 @@ export function formDomainValidator(domain: string): string {
|
||||||
|
|
||||||
return "invalid domain";
|
return "invalid domain";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function urlValidator(urlStr: string): string {
|
||||||
|
if (urlStr.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(urlStr);
|
||||||
|
} catch (e) {
|
||||||
|
return e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
||||||
|
return `invalid protocol, must be http or https`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formDomainValidator(url.host);
|
||||||
|
}
|
||||||
|
|
|
@ -1360,9 +1360,11 @@ button.tab-button {
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain-permission-drafts-view,
|
.domain-permission-drafts-view,
|
||||||
.domain-permission-excludes-view {
|
.domain-permission-excludes-view,
|
||||||
|
.domain-permission-subscriptions-view {
|
||||||
.domain-permission-draft,
|
.domain-permission-draft,
|
||||||
.domain-permission-exclude {
|
.domain-permission-exclude,
|
||||||
|
.domain-permission-subscription {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
@ -1404,14 +1406,17 @@ button.tab-button {
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain-permission-draft-details,
|
.domain-permission-draft-details,
|
||||||
.domain-permission-exclude-details {
|
.domain-permission-exclude-details,
|
||||||
|
.domain-permission-subscription-details {
|
||||||
.info-list {
|
.info-list {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain-permission-drafts-view,
|
.domain-permission-drafts-view,
|
||||||
.domain-permission-draft-details {
|
.domain-permission-draft-details,
|
||||||
|
.domain-permission-subscriptions-view,
|
||||||
|
.domain-permission-subscription-details {
|
||||||
dd.permission-type {
|
dd.permission-type {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function DomainPermissionSubscriptionHelpText() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DomainPermissionSubscriptionDocsLink() {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://docs.gotosocial.org/en/latest/admin/settings/#domain-permission-subscriptions"
|
||||||
|
target="_blank"
|
||||||
|
className="docslink"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Learn more about domain permission subscriptions (opens in a new tab)
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { useParams } from "wouter";
|
||||||
|
import Loading from "../../../../components/loading";
|
||||||
|
import { useBaseUrl } from "../../../../lib/navigation/util";
|
||||||
|
import BackButton from "../../../../components/back-button";
|
||||||
|
import { Error as ErrorC } from "../../../../components/error";
|
||||||
|
import UsernameLozenge from "../../../../components/username-lozenge";
|
||||||
|
import { useGetDomainPermissionSubscriptionQuery } from "../../../../lib/query/admin/domain-permissions/subscriptions";
|
||||||
|
|
||||||
|
export default function DomainPermissionSubscriptionDetail() {
|
||||||
|
const baseUrl = useBaseUrl();
|
||||||
|
const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
|
||||||
|
|
||||||
|
const params = useParams();
|
||||||
|
let id = params.permSubId as string | undefined;
|
||||||
|
if (!id) {
|
||||||
|
throw "no permSub ID";
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: permSub,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
} = useGetDomainPermissionSubscriptionQuery(id);
|
||||||
|
|
||||||
|
if (isLoading || isFetching) {
|
||||||
|
return <Loading />;
|
||||||
|
} else if (isError) {
|
||||||
|
return <ErrorC error={error} />;
|
||||||
|
} else if (permSub === undefined) {
|
||||||
|
return <ErrorC error={new Error("permission subscription was undefined")} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = permSub.created_at ? new Date(permSub.created_at).toDateString(): "unknown";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="domain-permission-subscription-details">
|
||||||
|
<h1><BackButton to={backLocation} /> Domain Permission Subscription Detail</h1>
|
||||||
|
<dl className="info-list">
|
||||||
|
<div className="info-list-entry">
|
||||||
|
<dt>Created</dt>
|
||||||
|
<dd><time dateTime={permSub.created_at}>{created}</time></dd>
|
||||||
|
</div>
|
||||||
|
<div className="info-list-entry">
|
||||||
|
<dt>Created By</dt>
|
||||||
|
<dd>
|
||||||
|
<UsernameLozenge
|
||||||
|
account={permSub.created_by}
|
||||||
|
linkTo={`~/settings/moderation/accounts/${permSub.created_by}`}
|
||||||
|
backLocation={`~${location}`}
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div className="info-list-entry">
|
||||||
|
<dt>Pee pee</dt>
|
||||||
|
<dd>poo poo</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { ReactNode, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
|
import { useTextInput } from "../../../../lib/form";
|
||||||
|
import { PageableList } from "../../../../components/pageable-list";
|
||||||
|
import MutationButton from "../../../../components/form/mutation-button";
|
||||||
|
import { useLocation, useSearch } from "wouter";
|
||||||
|
import { useLazySearchDomainPermissionSubscriptionsQuery } from "../../../../lib/query/admin/domain-permissions/subscriptions";
|
||||||
|
import { DomainPermSub } from "../../../../lib/types/domain-permission";
|
||||||
|
import { Error as ErrorC } from "../../../../components/error";
|
||||||
|
import { Select, TextInput } from "../../../../components/form/inputs";
|
||||||
|
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||||
|
import { DomainPermissionSubscriptionDocsLink, DomainPermissionSubscriptionHelpText } from "./common";
|
||||||
|
import { useCapitalize } from "../../../../lib/util";
|
||||||
|
|
||||||
|
export default function DomainPermissionSubscriptionsSearch() {
|
||||||
|
return (
|
||||||
|
<div className="domain-permission-subscriptions-view">
|
||||||
|
<div className="form-section-docs">
|
||||||
|
<h1>Domain Permission Subscriptions</h1>
|
||||||
|
<p>
|
||||||
|
You can use the form below to search through domain permission subscriptions.
|
||||||
|
<br/>
|
||||||
|
<DomainPermissionSubscriptionHelpText />
|
||||||
|
</p>
|
||||||
|
<DomainPermissionSubscriptionDocsLink />
|
||||||
|
</div>
|
||||||
|
<DomainPermissionSubscriptionsSearchForm />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DomainPermissionSubscriptionsSearchForm() {
|
||||||
|
const [ location, setLocation ] = useLocation();
|
||||||
|
const search = useSearch();
|
||||||
|
const urlQueryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||||
|
const hasParams = urlQueryParams.size != 0;
|
||||||
|
const [ searchSubscriptions, searchRes ] = useLazySearchDomainPermissionSubscriptionsQuery();
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
domain: useTextInput("domain", {
|
||||||
|
defaultValue: urlQueryParams.get("domain") ?? "",
|
||||||
|
validator: formDomainValidator,
|
||||||
|
}),
|
||||||
|
limit: useTextInput("limit", { defaultValue: urlQueryParams.get("limit") ?? "20" })
|
||||||
|
};
|
||||||
|
|
||||||
|
// On mount, if urlQueryParams were provided,
|
||||||
|
// trigger the search. For example, if page
|
||||||
|
// was accessed at /search?origin=local&limit=20,
|
||||||
|
// then run a search with origin=local and
|
||||||
|
// limit=20 and immediately render the results.
|
||||||
|
//
|
||||||
|
// If no urlQueryParams set, trigger default
|
||||||
|
// search (first page, no filtering).
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasParams) {
|
||||||
|
searchSubscriptions(Object.fromEntries(urlQueryParams));
|
||||||
|
} else {
|
||||||
|
setLocation(location + "?limit=20");
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
urlQueryParams,
|
||||||
|
hasParams,
|
||||||
|
searchSubscriptions,
|
||||||
|
location,
|
||||||
|
setLocation,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Rather than triggering the search directly,
|
||||||
|
// the "submit" button changes the location
|
||||||
|
// based on form field params, and lets the
|
||||||
|
// useEffect hook above actually do the search.
|
||||||
|
function submitQuery(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Parse query parameters.
|
||||||
|
const entries = Object.entries(form).map(([k, v]) => {
|
||||||
|
// Take only defined form fields.
|
||||||
|
if (v.value === undefined || v.value.length === 0 || v.value === "any") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return [[k, v.value]];
|
||||||
|
}).flatMap(kv => {
|
||||||
|
// Remove any nulls.
|
||||||
|
return kv || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(entries);
|
||||||
|
setLocation(location + "?" + searchParams.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location to return to when user clicks "back" on the detail view.
|
||||||
|
const backLocation = location + (hasParams ? `?${urlQueryParams}` : "");
|
||||||
|
|
||||||
|
// Function to map an item to a list entry.
|
||||||
|
function itemToEntry(permSub: DomainPermSub): ReactNode {
|
||||||
|
return (
|
||||||
|
<SubscriptionListEntry
|
||||||
|
key={permSub.id}
|
||||||
|
permSub={permSub}
|
||||||
|
linkTo={`/subscriptions/${permSub.id}`}
|
||||||
|
backLocation={backLocation}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form
|
||||||
|
onSubmit={submitQuery}
|
||||||
|
// Prevent password managers
|
||||||
|
// trying to fill in fields.
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
field={form.domain}
|
||||||
|
label={`Domain (without "https://" prefix)`}
|
||||||
|
placeholder="example.org"
|
||||||
|
autoCapitalize="none"
|
||||||
|
spellCheck="false"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
field={form.limit}
|
||||||
|
label="Items per page"
|
||||||
|
options={
|
||||||
|
<>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
></Select>
|
||||||
|
<MutationButton
|
||||||
|
disabled={false}
|
||||||
|
label={"Search"}
|
||||||
|
result={searchRes}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<PageableList
|
||||||
|
isLoading={searchRes.isLoading}
|
||||||
|
isFetching={searchRes.isFetching}
|
||||||
|
isSuccess={searchRes.isSuccess}
|
||||||
|
items={searchRes.data?.subs}
|
||||||
|
itemToEntry={itemToEntry}
|
||||||
|
isError={searchRes.isError}
|
||||||
|
error={searchRes.error}
|
||||||
|
emptyMessage={<b>No subscriptions found that match your query.</b>}
|
||||||
|
prevNextLinks={searchRes.data?.links}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubscriptionEntryProps {
|
||||||
|
permSub: DomainPermSub;
|
||||||
|
linkTo: string;
|
||||||
|
backLocation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SubscriptionListEntry({ permSub, linkTo, backLocation }: SubscriptionEntryProps) {
|
||||||
|
const [ _location, setLocation ] = useLocation();
|
||||||
|
|
||||||
|
const permType = permSub.permission_type;
|
||||||
|
if (!permType) {
|
||||||
|
return <ErrorC error={new Error("permission_type was undefined")} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = permSub.title !== "" ? permSub.title : "[none]";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`pseudolink domain-permission-subscription entry`}
|
||||||
|
// aria-label={`Subscription ${domain}`}
|
||||||
|
// title={`Subscription ${domain}`}
|
||||||
|
onClick={() => {
|
||||||
|
// When clicking on a subscription, direct
|
||||||
|
// to the detail view for that subscription.
|
||||||
|
setLocation(linkTo, {
|
||||||
|
// Store the back location in history so
|
||||||
|
// the detail view can use it to return to
|
||||||
|
// this page (including query parameters).
|
||||||
|
state: { backLocation: backLocation }
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
role="link"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<dl className="info-list">
|
||||||
|
{ permSub.title !== "" &&
|
||||||
|
<span className="">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
<div className="info-list-entry">
|
||||||
|
<dt>URL:</dt>
|
||||||
|
<dd className="text-cutoff">{permSub.uri}</dd>
|
||||||
|
</div>
|
||||||
|
<div className="info-list-entry">
|
||||||
|
<dt>URL:</dt>
|
||||||
|
<dd className="text-cutoff">{permSub.uri}</dd>
|
||||||
|
</div>
|
||||||
|
<div className="info-list-entry">
|
||||||
|
<dt>Permission type:</dt>
|
||||||
|
<dd className={`permission-type ${permType}`}>
|
||||||
|
<i
|
||||||
|
aria-hidden={true}
|
||||||
|
className={`fa fa-${permType === "allow" ? "check" : "close"}`}
|
||||||
|
></i>
|
||||||
|
{permType}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import useFormSubmit from "../../../../lib/form/submit";
|
||||||
|
import { useCreateDomainPermissionSubscriptionMutation } from "../../../../lib/query/admin/domain-permissions/subscriptions";
|
||||||
|
import { useTextInput } from "../../../../lib/form";
|
||||||
|
import { urlValidator } from "../../../../lib/util/formvalidators";
|
||||||
|
import MutationButton from "../../../../components/form/mutation-button";
|
||||||
|
import { Select, TextInput } from "../../../../components/form/inputs";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
import { DomainPermissionSubscriptionDocsLink, DomainPermissionSubscriptionHelpText } from "./common";
|
||||||
|
|
||||||
|
export default function DomainPermissionSubscriptionNew() {
|
||||||
|
const [ _location, setLocation ] = useLocation();
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
uri: useTextInput("uri", {
|
||||||
|
validator: urlValidator,
|
||||||
|
}),
|
||||||
|
content_type: useTextInput("content_type", { defaultValue: "text/csv" }),
|
||||||
|
permission_type: useTextInput("permission_type", { defaultValue: "block" }),
|
||||||
|
title: useTextInput("title"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [formSubmit, result] = useFormSubmit(
|
||||||
|
form,
|
||||||
|
useCreateDomainPermissionSubscriptionMutation(),
|
||||||
|
{
|
||||||
|
changedOnly: false,
|
||||||
|
onFinish: (res) => {
|
||||||
|
if (res.data) {
|
||||||
|
// Creation successful,
|
||||||
|
// redirect to subscriptions overview.
|
||||||
|
setLocation(`/subscriptions/search`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={formSubmit}
|
||||||
|
// Prevent password managers
|
||||||
|
// trying to fill in fields.
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<div className="form-section-docs">
|
||||||
|
<h2>New Domain Permission Subscription</h2>
|
||||||
|
<p><DomainPermissionSubscriptionHelpText /></p>
|
||||||
|
<DomainPermissionSubscriptionDocsLink />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
field={form.uri}
|
||||||
|
label={`Permission list URL (http or https)`}
|
||||||
|
placeholder="https://example.org/some_list.csv"
|
||||||
|
autoCapitalize="none"
|
||||||
|
spellCheck="false"
|
||||||
|
type="url"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
field={form.content_type}
|
||||||
|
label="Content type"
|
||||||
|
options={
|
||||||
|
<>
|
||||||
|
<option value="text/csv">CSV</option>
|
||||||
|
<option value="application/json">JSON</option>
|
||||||
|
<option value="text/plan">Plain</option>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
field={form.permission_type}
|
||||||
|
label="Permission type"
|
||||||
|
options={
|
||||||
|
<>
|
||||||
|
<option value="block">Block</option>
|
||||||
|
<option value="accept">Accept</option>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
field={form.title}
|
||||||
|
label={`Subscription title (optional)`}
|
||||||
|
placeholder="Some List of Baddies"
|
||||||
|
autoCapitalize="words"
|
||||||
|
spellCheck="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MutationButton
|
||||||
|
label="Save"
|
||||||
|
result={result}
|
||||||
|
disabled={!form.uri || !form.uri.valid}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -150,6 +150,23 @@ function ModerationDomainPermsMenu() {
|
||||||
icon="fa-plus"
|
icon="fa-plus"
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
name="Subscriptions"
|
||||||
|
itemUrl="subscriptions"
|
||||||
|
defaultChild="search"
|
||||||
|
icon="fa-cloud-download"
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
name="Search"
|
||||||
|
itemUrl="search"
|
||||||
|
icon="fa-list"
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
name="New subscription"
|
||||||
|
itemUrl="new"
|
||||||
|
icon="fa-plus"
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,9 @@ import DomainPermissionDraftDetail from "./domain-permissions/drafts/detail";
|
||||||
import DomainPermissionExcludeDetail from "./domain-permissions/excludes/detail";
|
import DomainPermissionExcludeDetail from "./domain-permissions/excludes/detail";
|
||||||
import DomainPermissionExcludesSearch from "./domain-permissions/excludes";
|
import DomainPermissionExcludesSearch from "./domain-permissions/excludes";
|
||||||
import DomainPermissionExcludeNew from "./domain-permissions/excludes/new";
|
import DomainPermissionExcludeNew from "./domain-permissions/excludes/new";
|
||||||
|
import DomainPermissionSubscriptionsSearch from "./domain-permissions/subscriptions";
|
||||||
|
import DomainPermissionSubscriptionNew from "./domain-permissions/subscriptions/new";
|
||||||
|
import DomainPermissionSubscriptionDetail from "./domain-permissions/subscriptions/detail";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
EXPORTED COMPONENTS
|
EXPORTED COMPONENTS
|
||||||
|
@ -151,6 +154,9 @@ function ModerationDomainPermsRouter() {
|
||||||
<Route path="/excludes/search" component={DomainPermissionExcludesSearch} />
|
<Route path="/excludes/search" component={DomainPermissionExcludesSearch} />
|
||||||
<Route path="/excludes/new" component={DomainPermissionExcludeNew} />
|
<Route path="/excludes/new" component={DomainPermissionExcludeNew} />
|
||||||
<Route path="/excludes/:excludeId" component={DomainPermissionExcludeDetail} />
|
<Route path="/excludes/:excludeId" component={DomainPermissionExcludeDetail} />
|
||||||
|
<Route path="/subscriptions/search" component={DomainPermissionSubscriptionsSearch} />
|
||||||
|
<Route path="/subscriptions/new" component={DomainPermissionSubscriptionNew} />
|
||||||
|
<Route path="/subscriptions/:permSubId" component={DomainPermissionSubscriptionDetail} />
|
||||||
<Route path="/:permType" component={DomainPermissionsOverview} />
|
<Route path="/:permType" component={DomainPermissionsOverview} />
|
||||||
<Route path="/:permType/:domain" component={DomainPermDetail} />
|
<Route path="/:permType/:domain" component={DomainPermDetail} />
|
||||||
<Route><Redirect to="/blocks"/></Route>
|
<Route><Redirect to="/blocks"/></Route>
|
||||||
|
|
Loading…
Reference in New Issue