mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-21 19:26:40 +00:00
Compare commits
1 commit
4f8542914e
...
61395a9f5a
Author | SHA1 | Date | |
---|---|---|---|
61395a9f5a |
|
@ -1095,6 +1095,12 @@ definitions:
|
|||
example: false
|
||||
type: boolean
|
||||
x-go-name: Obfuscate
|
||||
permission_type:
|
||||
description: |-
|
||||
Permission type of this entry (block, allow).
|
||||
Only set for domain permission drafts.
|
||||
type: string
|
||||
x-go-name: PermissionType
|
||||
private_comment:
|
||||
description: Private comment for this permission entry, visible to this instance's admins only.
|
||||
example: they are poopoo
|
||||
|
@ -1124,6 +1130,73 @@ definitions:
|
|||
type: object
|
||||
x-go-name: DomainPermission
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
domainPermissionSubscription:
|
||||
properties:
|
||||
as_draft:
|
||||
description: If true, domain permissions arising from this subscription will be created as drafts that must be approved by a moderator to take effect. If false, domain permissions from this subscription will come into force immediately.
|
||||
example: true
|
||||
type: boolean
|
||||
x-go-name: AsDraft
|
||||
content_type:
|
||||
description: MIME content type to expect at URI.
|
||||
example: text/csv
|
||||
type: string
|
||||
x-go-name: ContentType
|
||||
count:
|
||||
description: Count of domain permission entries discovered at URI.
|
||||
example: 53
|
||||
format: uint64
|
||||
readOnly: true
|
||||
type: integer
|
||||
x-go-name: Count
|
||||
created_by_account_id:
|
||||
description: ID of the account that created this subscription.
|
||||
example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||
readOnly: true
|
||||
type: string
|
||||
x-go-name: CreatedByAccountID
|
||||
error:
|
||||
description: If most recent fetch attempt failed, this field will contain an error message related to the fetch attempt.
|
||||
example: Oopsie doopsie, we made a fucky wucky.
|
||||
readOnly: true
|
||||
type: string
|
||||
x-go-name: Error
|
||||
fetch_password:
|
||||
description: (Optional) password to set for basic auth when doing a fetch of URI.
|
||||
example: admin123
|
||||
type: string
|
||||
x-go-name: FetchPassword
|
||||
fetch_username:
|
||||
description: (Optional) username to set for basic auth when doing a fetch of URI.
|
||||
example: admin123
|
||||
type: string
|
||||
x-go-name: FetchUsername
|
||||
fetched_at:
|
||||
description: Time at which the most recent fetch was attempted (ISO 8601 Datetime).
|
||||
example: "2021-07-30T09:20:25+00:00"
|
||||
readOnly: true
|
||||
type: string
|
||||
x-go-name: FetchedAt
|
||||
id:
|
||||
description: The ID of the domain permission subscription.
|
||||
example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||
readOnly: true
|
||||
type: string
|
||||
x-go-name: ID
|
||||
permission_type:
|
||||
description: The type of domain permission subscription (allow, block).
|
||||
example: block
|
||||
type: string
|
||||
x-go-name: PermissionType
|
||||
uri:
|
||||
description: URI to call in order to fetch the permissions list.
|
||||
example: https://www.example.org/blocklists/list1.csv
|
||||
type: string
|
||||
x-go-name: URI
|
||||
title: DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
|
||||
type: object
|
||||
x-go-name: DomainPermissionSubscription
|
||||
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
|
||||
emoji:
|
||||
properties:
|
||||
category:
|
||||
|
@ -5572,6 +5645,249 @@ paths:
|
|||
summary: Force expiry of cached public keys for all accounts on the given domain stored in your database.
|
||||
tags:
|
||||
- admin
|
||||
/api/v1/admin/domain_permission_drafts:
|
||||
get:
|
||||
description: |-
|
||||
The drafts will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer).
|
||||
|
||||
The next and previous queries can be parsed from the returned Link header.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
<https://example.org/api/v1/admin/domain_permission_drafts?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_drafts?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
````
|
||||
operationId: domainPermissionDraftsGet
|
||||
parameters:
|
||||
- description: Show only drafts created by the given subscription ID.
|
||||
in: query
|
||||
name: subscription_id
|
||||
type: string
|
||||
- description: Return only drafts that target the given domain.
|
||||
in: query
|
||||
name: domain
|
||||
type: string
|
||||
- description: Filter on "block" or "allow" type drafts.
|
||||
in: query
|
||||
name: permission_type
|
||||
type: string
|
||||
- description: Return only items *OLDER* than the given max ID (for paging downwards). The item with the specified ID will not be included in the response.
|
||||
in: query
|
||||
name: max_id
|
||||
type: string
|
||||
- description: Return only items *NEWER* than the given since ID. The item with the specified ID will not be included in the response.
|
||||
in: query
|
||||
name: since_id
|
||||
type: string
|
||||
- description: Return only items immediately *NEWER* than the given min ID (for paging upwards). The item with the specified ID will not be included in the response.
|
||||
in: query
|
||||
name: min_id
|
||||
type: string
|
||||
- default: 20
|
||||
description: Number of items to return.
|
||||
in: query
|
||||
maximum: 100
|
||||
minimum: 1
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Domain permission drafts.
|
||||
headers:
|
||||
Link:
|
||||
description: Links to the next and previous queries.
|
||||
type: string
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domainPermission'
|
||||
type: array
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"404":
|
||||
description: not found
|
||||
"406":
|
||||
description: not acceptable
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- admin
|
||||
summary: View domain permission drafts.
|
||||
tags:
|
||||
- admin
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
- application/json
|
||||
operationId: domainPermissionDraftCreate
|
||||
parameters:
|
||||
- description: Domain to create the permission draft for.
|
||||
in: formData
|
||||
name: domain
|
||||
type: string
|
||||
- description: Create a draft "allow" or a draft "block".
|
||||
in: formData
|
||||
name: permission_type
|
||||
type: string
|
||||
- description: Obfuscate the name of the domain when serving it publicly. Eg., `example.org` becomes something like `ex***e.org`.
|
||||
in: formData
|
||||
name: obfuscate
|
||||
type: boolean
|
||||
- description: Public comment about this domain permission. This will be displayed alongside the domain permission if you choose to share permissions.
|
||||
in: formData
|
||||
name: public_comment
|
||||
type: string
|
||||
- description: Private comment about this domain permission. Will only be shown to other admins, so this is a useful way of internally keeping track of why a certain domain ended up permissioned.
|
||||
in: formData
|
||||
name: private_comment
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The newly created domain permission draft.
|
||||
schema:
|
||||
$ref: '#/definitions/domainPermission'
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"406":
|
||||
description: not acceptable
|
||||
"409":
|
||||
description: conflict
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- admin
|
||||
summary: Create a domain permission draft with the given parameters.
|
||||
tags:
|
||||
- admin
|
||||
/api/v1/admin/domain_permission_drafts/{id}:
|
||||
get:
|
||||
operationId: domainPermissionDraftGet
|
||||
parameters:
|
||||
- description: ID of the domain permission draft.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Domain permission draft.
|
||||
schema:
|
||||
$ref: '#/definitions/domainPermission'
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"404":
|
||||
description: not found
|
||||
"406":
|
||||
description: not acceptable
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- admin
|
||||
summary: Get domain permission draft with the given ID.
|
||||
tags:
|
||||
- admin
|
||||
/api/v1/admin/domain_permission_drafts/{id}/accept:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
- application/json
|
||||
operationId: domainPermissionDraftAccept
|
||||
parameters:
|
||||
- description: ID of the domain permission draft.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- default: false
|
||||
description: If a domain permission already exists with the same domain and permission type as the draft, overwrite the existing permission with fields from the draft.
|
||||
in: formData
|
||||
name: overwrite
|
||||
type: boolean
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The newly created domain permission.
|
||||
schema:
|
||||
$ref: '#/definitions/domainPermission'
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"406":
|
||||
description: not acceptable
|
||||
"409":
|
||||
description: conflict
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- admin
|
||||
summary: Accept a domain permission draft, turning it into an enforced domain permission.
|
||||
tags:
|
||||
- admin
|
||||
/api/v1/admin/domain_permission_drafts/{id}/remove:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
- application/json
|
||||
operationId: domainPermissionDraftRemove
|
||||
parameters:
|
||||
- description: ID of the domain permission draft.
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- default: false
|
||||
description: When removing the domain permission draft, also create a domain ignore entry for the target domain, so that drafts will not be created for this domain in the future.
|
||||
in: formData
|
||||
name: ignore_target
|
||||
type: boolean
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The removed domain permission draft.
|
||||
schema:
|
||||
$ref: '#/definitions/domainPermission'
|
||||
"400":
|
||||
description: bad request
|
||||
"401":
|
||||
description: unauthorized
|
||||
"403":
|
||||
description: forbidden
|
||||
"406":
|
||||
description: not acceptable
|
||||
"409":
|
||||
description: conflict
|
||||
"500":
|
||||
description: internal server error
|
||||
security:
|
||||
- OAuth2 Bearer:
|
||||
- admin
|
||||
summary: Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
|
||||
tags:
|
||||
- admin
|
||||
/api/v1/admin/email/test:
|
||||
post:
|
||||
consumes:
|
||||
|
|
|
@ -28,37 +28,41 @@
|
|||
)
|
||||
|
||||
const (
|
||||
BasePath = "/v1/admin"
|
||||
EmojiPath = BasePath + "/custom_emojis"
|
||||
EmojiPathWithID = EmojiPath + "/:" + apiutil.IDKey
|
||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||
DomainBlocksPathWithID = DomainBlocksPath + "/:" + apiutil.IDKey
|
||||
DomainAllowsPath = BasePath + "/domain_allows"
|
||||
DomainAllowsPathWithID = DomainAllowsPath + "/:" + apiutil.IDKey
|
||||
DomainKeysExpirePath = BasePath + "/domain_keys_expire"
|
||||
HeaderAllowsPath = BasePath + "/header_allows"
|
||||
HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey
|
||||
HeaderBlocksPath = BasePath + "/header_blocks"
|
||||
HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + apiutil.IDKey
|
||||
AccountsV1Path = BasePath + "/accounts"
|
||||
AccountsV2Path = "/v2/admin/accounts"
|
||||
AccountsPathWithID = AccountsV1Path + "/:" + apiutil.IDKey
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
AccountsApprovePath = AccountsPathWithID + "/approve"
|
||||
AccountsRejectPath = AccountsPathWithID + "/reject"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
ReportsPath = BasePath + "/reports"
|
||||
ReportsPathWithID = ReportsPath + "/:" + apiutil.IDKey
|
||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||
EmailPath = BasePath + "/email"
|
||||
EmailTestPath = EmailPath + "/test"
|
||||
InstanceRulesPath = BasePath + "/instance/rules"
|
||||
InstanceRulesPathWithID = InstanceRulesPath + "/:" + apiutil.IDKey
|
||||
DebugPath = BasePath + "/debug"
|
||||
DebugAPUrlPath = DebugPath + "/apurl"
|
||||
DebugClearCachesPath = DebugPath + "/caches/clear"
|
||||
BasePath = "/v1/admin"
|
||||
EmojiPath = BasePath + "/custom_emojis"
|
||||
EmojiPathWithID = EmojiPath + "/:" + apiutil.IDKey
|
||||
EmojiCategoriesPath = EmojiPath + "/categories"
|
||||
DomainBlocksPath = BasePath + "/domain_blocks"
|
||||
DomainBlocksPathWithID = DomainBlocksPath + "/:" + apiutil.IDKey
|
||||
DomainAllowsPath = BasePath + "/domain_allows"
|
||||
DomainAllowsPathWithID = DomainAllowsPath + "/:" + apiutil.IDKey
|
||||
DomainPermissionDraftsPath = BasePath + "/domain_permission_drafts"
|
||||
DomainPermissionDraftsPathWithID = DomainPermissionDraftsPath + "/:" + apiutil.IDKey
|
||||
DomainPermissionDraftAcceptPath = DomainPermissionDraftsPathWithID + "/accept"
|
||||
DomainPermissionDraftRemovePath = DomainPermissionDraftsPathWithID + "/remove"
|
||||
DomainKeysExpirePath = BasePath + "/domain_keys_expire"
|
||||
HeaderAllowsPath = BasePath + "/header_allows"
|
||||
HeaderAllowsPathWithID = HeaderAllowsPath + "/:" + apiutil.IDKey
|
||||
HeaderBlocksPath = BasePath + "/header_blocks"
|
||||
HeaderBlocksPathWithID = HeaderBlocksPath + "/:" + apiutil.IDKey
|
||||
AccountsV1Path = BasePath + "/accounts"
|
||||
AccountsV2Path = "/v2/admin/accounts"
|
||||
AccountsPathWithID = AccountsV1Path + "/:" + apiutil.IDKey
|
||||
AccountsActionPath = AccountsPathWithID + "/action"
|
||||
AccountsApprovePath = AccountsPathWithID + "/approve"
|
||||
AccountsRejectPath = AccountsPathWithID + "/reject"
|
||||
MediaCleanupPath = BasePath + "/media_cleanup"
|
||||
MediaRefetchPath = BasePath + "/media_refetch"
|
||||
ReportsPath = BasePath + "/reports"
|
||||
ReportsPathWithID = ReportsPath + "/:" + apiutil.IDKey
|
||||
ReportsResolvePath = ReportsPathWithID + "/resolve"
|
||||
EmailPath = BasePath + "/email"
|
||||
EmailTestPath = EmailPath + "/test"
|
||||
InstanceRulesPath = BasePath + "/instance/rules"
|
||||
InstanceRulesPathWithID = InstanceRulesPath + "/:" + apiutil.IDKey
|
||||
DebugPath = BasePath + "/debug"
|
||||
DebugAPUrlPath = DebugPath + "/apurl"
|
||||
DebugClearCachesPath = DebugPath + "/caches/clear"
|
||||
|
||||
FilterQueryKey = "filter"
|
||||
MaxShortcodeDomainKey = "max_shortcode_domain"
|
||||
|
@ -99,6 +103,13 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
|||
attachHandler(http.MethodGet, DomainAllowsPathWithID, m.DomainAllowGETHandler)
|
||||
attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler)
|
||||
|
||||
// domain permission draft stuff
|
||||
attachHandler(http.MethodPost, DomainPermissionDraftsPath, m.DomainPermissionDraftsPOSTHandler)
|
||||
attachHandler(http.MethodGet, DomainPermissionDraftsPath, m.DomainPermissionDraftsGETHandler)
|
||||
attachHandler(http.MethodGet, DomainPermissionDraftsPathWithID, m.DomainPermissionDraftGETHandler)
|
||||
attachHandler(http.MethodPost, DomainPermissionDraftAcceptPath, m.DomainPermissionDraftAcceptPOSTHandler)
|
||||
attachHandler(http.MethodPost, DomainPermissionDraftRemovePath, m.DomainPermissionDraftRemovePOSTHandler)
|
||||
|
||||
// header filtering administration routes
|
||||
attachHandler(http.MethodGet, HeaderAllowsPathWithID, m.HeaderFilterAllowGET)
|
||||
attachHandler(http.MethodGet, HeaderBlocksPathWithID, m.HeaderFilterBlockGET)
|
||||
|
|
104
internal/api/client/admin/domainpermissiondraft.go
Normal file
104
internal/api/client/admin/domainpermissiondraft.go
Normal file
|
@ -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"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftGETHandler swagger:operation GET /api/v1/admin/domain_permission_drafts/{id} domainPermissionDraftGet
|
||||
//
|
||||
// Get domain permission draft with the given ID.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// required: true
|
||||
// in: path
|
||||
// description: ID of the domain permission draft.
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: Domain permission draft.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/domainPermission"
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) DomainPermissionDraftGETHandler(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
|
||||
}
|
||||
|
||||
permDraft, errWithCode := m.processor.Admin().DomainPermissionDraftGet(c.Request.Context(), id)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, permDraft)
|
||||
}
|
134
internal/api/client/admin/domainpermissiondraftaccept.go
Normal file
134
internal/api/client/admin/domainpermissiondraftaccept.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftAcceptPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts/{id}/accept domainPermissionDraftAccept
|
||||
//
|
||||
// Accept a domain permission draft, turning it into an enforced domain permission.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
// - application/json
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// required: true
|
||||
// in: path
|
||||
// description: ID of the domain permission draft.
|
||||
// type: string
|
||||
// -
|
||||
// name: overwrite
|
||||
// in: formData
|
||||
// description: >-
|
||||
// If a domain permission already exists with the same
|
||||
// domain and permission type as the draft, overwrite
|
||||
// the existing permission with fields from the draft.
|
||||
// type: boolean
|
||||
// default: false
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The newly created domain permission.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/domainPermission"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) DomainPermissionDraftAcceptPOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
type AcceptForm struct {
|
||||
Overwrite bool `json:"overwrite" form:"overwrite"`
|
||||
}
|
||||
|
||||
form := new(AcceptForm)
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
domainPerm, _, errWithCode := m.processor.Admin().DomainPermissionDraftAccept(
|
||||
c.Request.Context(),
|
||||
authed.Account,
|
||||
id,
|
||||
form.Overwrite,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, domainPerm)
|
||||
}
|
176
internal/api/client/admin/domainpermissiondraftcreate.go
Normal file
176
internal/api/client/admin/domainpermissiondraftcreate.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
// 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"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftsPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts domainPermissionDraftCreate
|
||||
//
|
||||
// Create a domain permission draft with the given parameters.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
// - application/json
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: domain
|
||||
// in: formData
|
||||
// description: Domain to create the permission draft for.
|
||||
// type: string
|
||||
// -
|
||||
// name: permission_type
|
||||
// in: formData
|
||||
// description: Create a draft "allow" or a draft "block".
|
||||
// type: string
|
||||
// -
|
||||
// name: obfuscate
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Obfuscate the name of the domain when serving it publicly.
|
||||
// Eg., `example.org` becomes something like `ex***e.org`.
|
||||
// type: boolean
|
||||
// -
|
||||
// name: public_comment
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Public comment about this domain permission.
|
||||
// This will be displayed alongside the domain permission if you choose to share permissions.
|
||||
// type: string
|
||||
// -
|
||||
// name: private_comment
|
||||
// in: formData
|
||||
// description: >-
|
||||
// Private comment about this domain permission. Will only be shown to other admins, so this
|
||||
// is a useful way of internally keeping track of why a certain domain ended up permissioned.
|
||||
// type: string
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The newly created domain permission draft.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/domainPermission"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) DomainPermissionDraftsPOSTHandler(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.DomainPermissionRequest)
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if form.Domain == "" {
|
||||
const errText = "domain must be set"
|
||||
errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText)
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
permType gtsmodel.DomainPermissionType
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
permDraft, errWithCode := m.processor.Admin().DomainPermissionDraftCreate(
|
||||
c.Request.Context(),
|
||||
authed.Account,
|
||||
form.Domain,
|
||||
permType,
|
||||
form.Obfuscate,
|
||||
form.PublicComment,
|
||||
form.PrivateComment,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, permDraft)
|
||||
}
|
134
internal/api/client/admin/domainpermissiondraftremove.go
Normal file
134
internal/api/client/admin/domainpermissiondraftremove.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftRemovePOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts/{id}/remove domainPermissionDraftRemove
|
||||
//
|
||||
// Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// consumes:
|
||||
// - multipart/form-data
|
||||
// - application/json
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: id
|
||||
// required: true
|
||||
// in: path
|
||||
// description: ID of the domain permission draft.
|
||||
// type: string
|
||||
// -
|
||||
// name: ignore_target
|
||||
// in: formData
|
||||
// description: >-
|
||||
// When removing the domain permission draft, also create a
|
||||
// domain ignore entry for the target domain, so that drafts
|
||||
// will not be created for this domain in the future.
|
||||
// type: boolean
|
||||
// default: false
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - admin
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// description: The removed domain permission draft.
|
||||
// schema:
|
||||
// "$ref": "#/definitions/domainPermission"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '409':
|
||||
// description: conflict
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) DomainPermissionDraftRemovePOSTHandler(c *gin.Context) {
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if !*authed.User.Admin {
|
||||
err := fmt.Errorf("user %s not an admin", authed.User.ID)
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if authed.Account.IsMoving() {
|
||||
apiutil.ForbiddenAfterMove(c)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
id, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
type RemoveForm struct {
|
||||
IgnoreTarget bool `json:"ignore_target" form:"ignore_target"`
|
||||
}
|
||||
|
||||
form := new(RemoveForm)
|
||||
if err := c.ShouldBind(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
domainPerm, errWithCode := m.processor.Admin().DomainPermissionDraftRemove(
|
||||
c.Request.Context(),
|
||||
authed.Account,
|
||||
id,
|
||||
form.IgnoreTarget,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, domainPerm)
|
||||
}
|
189
internal/api/client/admin/domainpermissiondrafts.go
Normal file
189
internal/api/client/admin/domainpermissiondrafts.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftsGETHandler swagger:operation GET /api/v1/admin/domain_permission_drafts domainPermissionDraftsGet
|
||||
//
|
||||
// View domain permission drafts.
|
||||
//
|
||||
// The drafts will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer).
|
||||
//
|
||||
// The next and previous queries can be parsed from the returned Link header.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ```
|
||||
// <https://example.org/api/v1/admin/domain_permission_drafts?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_drafts?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||
// ````
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - admin
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// parameters:
|
||||
// -
|
||||
// name: subscription_id
|
||||
// type: string
|
||||
// description: Show only drafts created by the given subscription ID.
|
||||
// in: query
|
||||
// -
|
||||
// name: domain
|
||||
// type: string
|
||||
// description: Return only drafts that target the given domain.
|
||||
// in: query
|
||||
// -
|
||||
// name: permission_type
|
||||
// type: string
|
||||
// description: Filter on "block" or "allow" type drafts.
|
||||
// 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 drafts.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/domainPermission"
|
||||
// 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) DomainPermissionDraftsGETHandler(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().DomainPermissionDraftsGet(
|
||||
c.Request.Context(),
|
||||
c.Query(apiutil.DomainPermissionSubscriptionIDKey),
|
||||
c.Query(apiutil.DomainPermissionDomainKey),
|
||||
gtsmodel.NewDomainPermissionType(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)
|
||||
}
|
|
@ -61,6 +61,53 @@ type DomainPermission struct {
|
|||
// Time at which the permission entry was created (ISO 8601 Datetime).
|
||||
// example: 2021-07-30T09:20:25+00:00
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
// Permission type of this entry (block, allow).
|
||||
// Only set for domain permission drafts.
|
||||
PermissionType string `json:"permission_type,omitempty"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
// 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"`
|
||||
// ID of the account that created this subscription.
|
||||
// example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||
// readonly: true
|
||||
CreatedByAccountID string `json:"created_by_account_id"`
|
||||
// MIME content type to expect at URI.
|
||||
// example: text/csv
|
||||
ContentType string `json:"content_type"`
|
||||
// URI to call in order to fetch the permissions list.
|
||||
// example: https://www.example.org/blocklists/list1.csv
|
||||
URI string `json:"uri"`
|
||||
// (Optional) username to set for basic auth when doing a fetch of URI.
|
||||
// example: admin123
|
||||
FetchUsername string `json:"fetch_username"`
|
||||
// (Optional) password to set for basic auth when doing a fetch of URI.
|
||||
// example: admin123
|
||||
FetchPassword string `json:"fetch_password"`
|
||||
// Time at which the most recent fetch was attempted (ISO 8601 Datetime).
|
||||
// example: 2021-07-30T09:20:25+00:00
|
||||
// readonly: true
|
||||
FetchedAt string `json:"fetched_at"`
|
||||
// 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"`
|
||||
// Count of domain permission entries discovered at URI.
|
||||
// example: 53
|
||||
// readonly: true
|
||||
Count uint64 `json:"count"`
|
||||
}
|
||||
|
||||
// DomainPermissionRequest is the form submitted as a POST to create a new domain permission entry (allow/block).
|
||||
|
@ -69,22 +116,24 @@ type DomainPermission struct {
|
|||
type DomainPermissionRequest struct {
|
||||
// A list of domains for which this permission request should apply.
|
||||
// Only used if import=true is specified.
|
||||
Domains *multipart.FileHeader `form:"domains" json:"domains" xml:"domains"`
|
||||
Domains *multipart.FileHeader `form:"domains" json:"domains"`
|
||||
// A single domain for which this permission request should apply.
|
||||
// Only used if import=true is NOT specified or if import=false.
|
||||
// example: example.org
|
||||
Domain string `form:"domain" json:"domain" xml:"domain"`
|
||||
Domain string `form:"domain" json:"domain"`
|
||||
// Obfuscate the domain name when displaying this permission entry publicly.
|
||||
// Ie., instead of 'example.org' show something like 'e**mpl*.or*'.
|
||||
// example: false
|
||||
Obfuscate bool `form:"obfuscate" json:"obfuscate" xml:"obfuscate"`
|
||||
Obfuscate bool `form:"obfuscate" json:"obfuscate"`
|
||||
// Private comment for other admins on why this permission entry was created.
|
||||
// example: don't like 'em!!!!
|
||||
PrivateComment string `form:"private_comment" json:"private_comment" xml:"private_comment"`
|
||||
PrivateComment string `form:"private_comment" json:"private_comment"`
|
||||
// Public comment on why this permission entry was created.
|
||||
// Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed.
|
||||
// example: foss dorks 😫
|
||||
PublicComment string `form:"public_comment" json:"public_comment" xml:"public_comment"`
|
||||
PublicComment string `form:"public_comment" json:"public_comment"`
|
||||
// Permission type to create (only applies to domain permission drafts, not explicit blocks and allows).
|
||||
PermissionType string `form:"permission_type" json:"permission_type"`
|
||||
}
|
||||
|
||||
// DomainKeysExpireRequest is the form submitted as a POST to /api/v1/admin/domain_keys_expire to expire a domain's public keys.
|
||||
|
@ -92,5 +141,5 @@ type DomainPermissionRequest struct {
|
|||
// swagger:parameters domainKeysExpire
|
||||
type DomainKeysExpireRequest struct {
|
||||
// hostname/domain to expire keys for.
|
||||
Domain string `form:"domain" json:"domain" xml:"domain"`
|
||||
Domain string `form:"domain" json:"domain"`
|
||||
}
|
||||
|
|
|
@ -69,8 +69,11 @@
|
|||
|
||||
/* Domain permission keys */
|
||||
|
||||
DomainPermissionExportKey = "export"
|
||||
DomainPermissionImportKey = "import"
|
||||
DomainPermissionExportKey = "export"
|
||||
DomainPermissionImportKey = "import"
|
||||
DomainPermissionSubscriptionIDKey = "subscription_id"
|
||||
DomainPermissionPermTypeKey = "permission_type"
|
||||
DomainPermissionDomainKey = "domain"
|
||||
|
||||
/* Admin query keys */
|
||||
|
||||
|
|
3
internal/cache/cache.go
vendored
3
internal/cache/cache.go
vendored
|
@ -74,6 +74,9 @@ func (c *Caches) Init() {
|
|||
c.initConversationLastStatusIDs()
|
||||
c.initDomainAllow()
|
||||
c.initDomainBlock()
|
||||
c.initDomainPermissionDraft()
|
||||
c.initDomainPermissionSubscription()
|
||||
c.initDomainPermissionIgnore()
|
||||
c.initEmoji()
|
||||
c.initEmojiCategory()
|
||||
c.initFilter()
|
||||
|
|
76
internal/cache/db.go
vendored
76
internal/cache/db.go
vendored
|
@ -67,6 +67,15 @@ type DBCaches struct {
|
|||
// DomainBlock provides access to the domain block database cache.
|
||||
DomainBlock *domain.Cache
|
||||
|
||||
// DomainPermissionDraft provides access to the domain permission draft database cache.
|
||||
DomainPermissionDraft StructCache[*gtsmodel.DomainPermissionDraft]
|
||||
|
||||
// DomainPermissionSubscription provides access to the domain permission subscription database cache.
|
||||
DomainPermissionSubscription StructCache[*gtsmodel.DomainPermissionSubscription]
|
||||
|
||||
// DomainPermissionIgnore provides access to the domain permission ignore database cache.
|
||||
DomainPermissionIgnore *domain.Cache
|
||||
|
||||
// Emoji provides access to the gtsmodel Emoji database cache.
|
||||
Emoji StructCache[*gtsmodel.Emoji]
|
||||
|
||||
|
@ -548,6 +557,73 @@ func (c *Caches) initDomainBlock() {
|
|||
c.DB.DomainBlock = new(domain.Cache)
|
||||
}
|
||||
|
||||
func (c *Caches) initDomainPermissionDraft() {
|
||||
// Calculate maximum cache size.
|
||||
cap := calculateResultCacheMax(
|
||||
sizeofDomainPermissionDraft(), // model in-mem size.
|
||||
config.GetCacheDomainPermissionDraftMemRation(),
|
||||
)
|
||||
|
||||
log.Infof(nil, "cache size = %d", cap)
|
||||
|
||||
copyF := func(d1 *gtsmodel.DomainPermissionDraft) *gtsmodel.DomainPermissionDraft {
|
||||
d2 := new(gtsmodel.DomainPermissionDraft)
|
||||
*d2 = *d1
|
||||
|
||||
// Don't include ptr fields that
|
||||
// will be populated separately.
|
||||
d2.CreatedByAccount = nil
|
||||
|
||||
return d2
|
||||
}
|
||||
|
||||
c.DB.DomainPermissionDraft.Init(structr.CacheConfig[*gtsmodel.DomainPermissionDraft]{
|
||||
Indices: []structr.IndexConfig{
|
||||
{Fields: "ID"},
|
||||
{Fields: "Domain", Multiple: true},
|
||||
{Fields: "SubscriptionID", Multiple: true},
|
||||
},
|
||||
MaxSize: cap,
|
||||
IgnoreErr: ignoreErrors,
|
||||
Copy: copyF,
|
||||
})
|
||||
}
|
||||
|
||||
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) initDomainPermissionIgnore() {
|
||||
c.DB.DomainPermissionIgnore = new(domain.Cache)
|
||||
}
|
||||
|
||||
func (c *Caches) initEmoji() {
|
||||
// Calculate maximum cache size.
|
||||
cap := calculateResultCacheMax(
|
||||
|
|
29
internal/cache/size.go
vendored
29
internal/cache/size.go
vendored
|
@ -342,6 +342,35 @@ func sizeofConversation() uintptr {
|
|||
}))
|
||||
}
|
||||
|
||||
func sizeofDomainPermissionDraft() uintptr {
|
||||
return uintptr(size.Of(>smodel.DomainPermissionDraft{
|
||||
ID: exampleID,
|
||||
CreatedAt: exampleTime,
|
||||
UpdatedAt: exampleTime,
|
||||
PermissionType: gtsmodel.DomainPermissionBlock,
|
||||
Domain: "example.org",
|
||||
CreatedByAccountID: exampleID,
|
||||
PrivateComment: exampleTextSmall,
|
||||
PublicComment: exampleTextSmall,
|
||||
Obfuscate: util.Ptr(false),
|
||||
SubscriptionID: exampleID,
|
||||
}))
|
||||
}
|
||||
|
||||
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 {
|
||||
return uintptr(size.Of(>smodel.Emoji{
|
||||
ID: exampleID,
|
||||
|
|
|
@ -194,58 +194,60 @@ type HTTPClientConfiguration struct {
|
|||
}
|
||||
|
||||
type CacheConfiguration struct {
|
||||
MemoryTarget bytesize.Size `name:"memory-target"`
|
||||
AccountMemRatio float64 `name:"account-mem-ratio"`
|
||||
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
||||
AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
|
||||
AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"`
|
||||
ApplicationMemRatio float64 `name:"application-mem-ratio"`
|
||||
BlockMemRatio float64 `name:"block-mem-ratio"`
|
||||
BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"`
|
||||
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
||||
ClientMemRatio float64 `name:"client-mem-ratio"`
|
||||
ConversationMemRatio float64 `name:"conversation-mem-ratio"`
|
||||
ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"`
|
||||
EmojiMemRatio float64 `name:"emoji-mem-ratio"`
|
||||
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
|
||||
FilterMemRatio float64 `name:"filter-mem-ratio"`
|
||||
FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"`
|
||||
FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"`
|
||||
FollowMemRatio float64 `name:"follow-mem-ratio"`
|
||||
FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
|
||||
FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
|
||||
FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
|
||||
FollowingTagIDsMemRatio float64 `name:"following-tag-ids-mem-ratio"`
|
||||
InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
|
||||
InstanceMemRatio float64 `name:"instance-mem-ratio"`
|
||||
InteractionRequestMemRatio float64 `name:"interaction-request-mem-ratio"`
|
||||
ListMemRatio float64 `name:"list-mem-ratio"`
|
||||
ListIDsMemRatio float64 `name:"list-ids-mem-ratio"`
|
||||
ListedIDsMemRatio float64 `name:"listed-ids-mem-ratio"`
|
||||
MarkerMemRatio float64 `name:"marker-mem-ratio"`
|
||||
MediaMemRatio float64 `name:"media-mem-ratio"`
|
||||
MentionMemRatio float64 `name:"mention-mem-ratio"`
|
||||
MoveMemRatio float64 `name:"move-mem-ratio"`
|
||||
NotificationMemRatio float64 `name:"notification-mem-ratio"`
|
||||
PollMemRatio float64 `name:"poll-mem-ratio"`
|
||||
PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`
|
||||
PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"`
|
||||
ReportMemRatio float64 `name:"report-mem-ratio"`
|
||||
SinBinStatusMemRatio float64 `name:"sin-bin-status-mem-ratio"`
|
||||
StatusMemRatio float64 `name:"status-mem-ratio"`
|
||||
StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"`
|
||||
StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"`
|
||||
StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
|
||||
StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
|
||||
TagMemRatio float64 `name:"tag-mem-ratio"`
|
||||
ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"`
|
||||
TokenMemRatio float64 `name:"token-mem-ratio"`
|
||||
TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
|
||||
UserMemRatio float64 `name:"user-mem-ratio"`
|
||||
UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
|
||||
UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"`
|
||||
WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
|
||||
VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
|
||||
MemoryTarget bytesize.Size `name:"memory-target"`
|
||||
AccountMemRatio float64 `name:"account-mem-ratio"`
|
||||
AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
|
||||
AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
|
||||
AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"`
|
||||
ApplicationMemRatio float64 `name:"application-mem-ratio"`
|
||||
BlockMemRatio float64 `name:"block-mem-ratio"`
|
||||
BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"`
|
||||
BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
|
||||
ClientMemRatio float64 `name:"client-mem-ratio"`
|
||||
ConversationMemRatio float64 `name:"conversation-mem-ratio"`
|
||||
ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"`
|
||||
DomainPermissionDraftMemRation float64 `name:"domain-permission-draft-mem-ratio"`
|
||||
DomainPermissionSubscriptionMemRation float64 `name:"domain-permission-subscription-mem-ratio"`
|
||||
EmojiMemRatio float64 `name:"emoji-mem-ratio"`
|
||||
EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
|
||||
FilterMemRatio float64 `name:"filter-mem-ratio"`
|
||||
FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"`
|
||||
FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"`
|
||||
FollowMemRatio float64 `name:"follow-mem-ratio"`
|
||||
FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
|
||||
FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
|
||||
FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
|
||||
FollowingTagIDsMemRatio float64 `name:"following-tag-ids-mem-ratio"`
|
||||
InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
|
||||
InstanceMemRatio float64 `name:"instance-mem-ratio"`
|
||||
InteractionRequestMemRatio float64 `name:"interaction-request-mem-ratio"`
|
||||
ListMemRatio float64 `name:"list-mem-ratio"`
|
||||
ListIDsMemRatio float64 `name:"list-ids-mem-ratio"`
|
||||
ListedIDsMemRatio float64 `name:"listed-ids-mem-ratio"`
|
||||
MarkerMemRatio float64 `name:"marker-mem-ratio"`
|
||||
MediaMemRatio float64 `name:"media-mem-ratio"`
|
||||
MentionMemRatio float64 `name:"mention-mem-ratio"`
|
||||
MoveMemRatio float64 `name:"move-mem-ratio"`
|
||||
NotificationMemRatio float64 `name:"notification-mem-ratio"`
|
||||
PollMemRatio float64 `name:"poll-mem-ratio"`
|
||||
PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`
|
||||
PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"`
|
||||
ReportMemRatio float64 `name:"report-mem-ratio"`
|
||||
SinBinStatusMemRatio float64 `name:"sin-bin-status-mem-ratio"`
|
||||
StatusMemRatio float64 `name:"status-mem-ratio"`
|
||||
StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"`
|
||||
StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"`
|
||||
StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
|
||||
StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
|
||||
TagMemRatio float64 `name:"tag-mem-ratio"`
|
||||
ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"`
|
||||
TokenMemRatio float64 `name:"token-mem-ratio"`
|
||||
TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
|
||||
UserMemRatio float64 `name:"user-mem-ratio"`
|
||||
UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
|
||||
UserMuteIDsMemRatio float64 `name:"user-mute-ids-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).
|
||||
|
|
|
@ -158,57 +158,59 @@
|
|||
// when TODO items in the size.go source
|
||||
// file have been addressed, these should
|
||||
// be able to make some more sense :D
|
||||
AccountMemRatio: 5,
|
||||
AccountNoteMemRatio: 1,
|
||||
AccountSettingsMemRatio: 0.1,
|
||||
AccountStatsMemRatio: 2,
|
||||
ApplicationMemRatio: 0.1,
|
||||
BlockMemRatio: 2,
|
||||
BlockIDsMemRatio: 3,
|
||||
BoostOfIDsMemRatio: 3,
|
||||
ClientMemRatio: 0.1,
|
||||
ConversationMemRatio: 1,
|
||||
ConversationLastStatusIDsMemRatio: 2,
|
||||
EmojiMemRatio: 3,
|
||||
EmojiCategoryMemRatio: 0.1,
|
||||
FilterMemRatio: 0.5,
|
||||
FilterKeywordMemRatio: 0.5,
|
||||
FilterStatusMemRatio: 0.5,
|
||||
FollowMemRatio: 2,
|
||||
FollowIDsMemRatio: 4,
|
||||
FollowRequestMemRatio: 2,
|
||||
FollowRequestIDsMemRatio: 2,
|
||||
FollowingTagIDsMemRatio: 2,
|
||||
InReplyToIDsMemRatio: 3,
|
||||
InstanceMemRatio: 1,
|
||||
InteractionRequestMemRatio: 1,
|
||||
ListMemRatio: 1,
|
||||
ListIDsMemRatio: 2,
|
||||
ListedIDsMemRatio: 2,
|
||||
MarkerMemRatio: 0.5,
|
||||
MediaMemRatio: 4,
|
||||
MentionMemRatio: 2,
|
||||
MoveMemRatio: 0.1,
|
||||
NotificationMemRatio: 2,
|
||||
PollMemRatio: 1,
|
||||
PollVoteMemRatio: 2,
|
||||
PollVoteIDsMemRatio: 2,
|
||||
ReportMemRatio: 1,
|
||||
SinBinStatusMemRatio: 0.5,
|
||||
StatusMemRatio: 5,
|
||||
StatusBookmarkMemRatio: 0.5,
|
||||
StatusBookmarkIDsMemRatio: 2,
|
||||
StatusFaveMemRatio: 2,
|
||||
StatusFaveIDsMemRatio: 3,
|
||||
TagMemRatio: 2,
|
||||
ThreadMuteMemRatio: 0.2,
|
||||
TokenMemRatio: 0.75,
|
||||
TombstoneMemRatio: 0.5,
|
||||
UserMemRatio: 0.25,
|
||||
UserMuteMemRatio: 2,
|
||||
UserMuteIDsMemRatio: 3,
|
||||
WebfingerMemRatio: 0.1,
|
||||
VisibilityMemRatio: 2,
|
||||
AccountMemRatio: 5,
|
||||
AccountNoteMemRatio: 1,
|
||||
AccountSettingsMemRatio: 0.1,
|
||||
AccountStatsMemRatio: 2,
|
||||
ApplicationMemRatio: 0.1,
|
||||
BlockMemRatio: 2,
|
||||
BlockIDsMemRatio: 3,
|
||||
BoostOfIDsMemRatio: 3,
|
||||
ClientMemRatio: 0.1,
|
||||
ConversationMemRatio: 1,
|
||||
ConversationLastStatusIDsMemRatio: 2,
|
||||
DomainPermissionDraftMemRation: 0.5,
|
||||
DomainPermissionSubscriptionMemRation: 0.5,
|
||||
EmojiMemRatio: 3,
|
||||
EmojiCategoryMemRatio: 0.1,
|
||||
FilterMemRatio: 0.5,
|
||||
FilterKeywordMemRatio: 0.5,
|
||||
FilterStatusMemRatio: 0.5,
|
||||
FollowMemRatio: 2,
|
||||
FollowIDsMemRatio: 4,
|
||||
FollowRequestMemRatio: 2,
|
||||
FollowRequestIDsMemRatio: 2,
|
||||
FollowingTagIDsMemRatio: 2,
|
||||
InReplyToIDsMemRatio: 3,
|
||||
InstanceMemRatio: 1,
|
||||
InteractionRequestMemRatio: 1,
|
||||
ListMemRatio: 1,
|
||||
ListIDsMemRatio: 2,
|
||||
ListedIDsMemRatio: 2,
|
||||
MarkerMemRatio: 0.5,
|
||||
MediaMemRatio: 4,
|
||||
MentionMemRatio: 2,
|
||||
MoveMemRatio: 0.1,
|
||||
NotificationMemRatio: 2,
|
||||
PollMemRatio: 1,
|
||||
PollVoteMemRatio: 2,
|
||||
PollVoteIDsMemRatio: 2,
|
||||
ReportMemRatio: 1,
|
||||
SinBinStatusMemRatio: 0.5,
|
||||
StatusMemRatio: 5,
|
||||
StatusBookmarkMemRatio: 0.5,
|
||||
StatusBookmarkIDsMemRatio: 2,
|
||||
StatusFaveMemRatio: 2,
|
||||
StatusFaveIDsMemRatio: 3,
|
||||
TagMemRatio: 2,
|
||||
ThreadMuteMemRatio: 0.2,
|
||||
TokenMemRatio: 0.75,
|
||||
TombstoneMemRatio: 0.5,
|
||||
UserMemRatio: 0.25,
|
||||
UserMuteMemRatio: 2,
|
||||
UserMuteIDsMemRatio: 3,
|
||||
WebfingerMemRatio: 0.1,
|
||||
VisibilityMemRatio: 2,
|
||||
},
|
||||
|
||||
HTTPClient: HTTPClientConfiguration{
|
||||
|
|
|
@ -3106,6 +3106,68 @@ func SetCacheConversationLastStatusIDsMemRatio(v float64) {
|
|||
global.SetCacheConversationLastStatusIDsMemRatio(v)
|
||||
}
|
||||
|
||||
// GetCacheDomainPermissionDraftMemRation safely fetches the Configuration value for state's 'Cache.DomainPermissionDraftMemRation' field
|
||||
func (st *ConfigState) GetCacheDomainPermissionDraftMemRation() (v float64) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.Cache.DomainPermissionDraftMemRation
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetCacheDomainPermissionDraftMemRation safely sets the Configuration value for state's 'Cache.DomainPermissionDraftMemRation' field
|
||||
func (st *ConfigState) SetCacheDomainPermissionDraftMemRation(v float64) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.Cache.DomainPermissionDraftMemRation = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// CacheDomainPermissionDraftMemRationFlag returns the flag name for the 'Cache.DomainPermissionDraftMemRation' field
|
||||
func CacheDomainPermissionDraftMemRationFlag() string {
|
||||
return "cache-domain-permission-draft-mem-ratio"
|
||||
}
|
||||
|
||||
// GetCacheDomainPermissionDraftMemRation safely fetches the value for global configuration 'Cache.DomainPermissionDraftMemRation' field
|
||||
func GetCacheDomainPermissionDraftMemRation() float64 {
|
||||
return global.GetCacheDomainPermissionDraftMemRation()
|
||||
}
|
||||
|
||||
// SetCacheDomainPermissionDraftMemRation safely sets the value for global configuration 'Cache.DomainPermissionDraftMemRation' field
|
||||
func SetCacheDomainPermissionDraftMemRation(v float64) {
|
||||
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
|
||||
func (st *ConfigState) GetCacheEmojiMemRatio() (v float64) {
|
||||
st.mutex.RLock()
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
@ -110,6 +111,36 @@ func (d *domainDB) GetDomainAllowByID(ctx context.Context, id string) (*gtsmodel
|
|||
return &allow, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) UpdateDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow, columns ...string) error {
|
||||
// Normalize the domain as punycode
|
||||
var err error
|
||||
allow.Domain, err = util.Punify(allow.Domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure updated_at is set.
|
||||
allow.UpdatedAt = time.Now()
|
||||
if len(columns) != 0 {
|
||||
columns = append(columns, "updated_at")
|
||||
}
|
||||
|
||||
// Attempt to update domain allow.
|
||||
if _, err := d.db.
|
||||
NewUpdate().
|
||||
Model(allow).
|
||||
Column(columns...).
|
||||
Where("? = ?", bun.Ident("domain_allow.id"), allow.ID).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the domain allow cache (for later reload)
|
||||
d.state.Caches.DB.DomainAllow.Clear()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *domainDB) DeleteDomainAllow(ctx context.Context, domain string) error {
|
||||
// Normalize the domain as punycode
|
||||
domain, err := util.Punify(domain)
|
||||
|
@ -206,6 +237,36 @@ func (d *domainDB) GetDomainBlockByID(ctx context.Context, id string) (*gtsmodel
|
|||
return &block, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) UpdateDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock, columns ...string) error {
|
||||
// Normalize the domain as punycode
|
||||
var err error
|
||||
block.Domain, err = util.Punify(block.Domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure updated_at is set.
|
||||
block.UpdatedAt = time.Now()
|
||||
if len(columns) != 0 {
|
||||
columns = append(columns, "updated_at")
|
||||
}
|
||||
|
||||
// Attempt to update domain block.
|
||||
if _, err := d.db.
|
||||
NewUpdate().
|
||||
Model(block).
|
||||
Column(columns...).
|
||||
Where("? = ?", bun.Ident("domain_block.id"), block.ID).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the domain block cache (for later reload)
|
||||
d.state.Caches.DB.DomainBlock.Clear()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *domainDB) DeleteDomainBlock(ctx context.Context, domain string) error {
|
||||
// Normalize the domain as punycode
|
||||
domain, err := util.Punify(domain)
|
||||
|
|
285
internal/db/bundb/domainpermissiondraft.go
Normal file
285
internal/db/bundb/domainpermissiondraft.go
Normal file
|
@ -0,0 +1,285 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bundb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func (d *domainDB) getDomainPermissionDraft(
|
||||
ctx context.Context,
|
||||
lookup string,
|
||||
dbQuery func(*gtsmodel.DomainPermissionDraft) error,
|
||||
keyParts ...any,
|
||||
) (*gtsmodel.DomainPermissionDraft, error) {
|
||||
// Fetch perm draft from database cache with loader callback.
|
||||
permDraft, err := d.state.Caches.DB.DomainPermissionDraft.LoadOne(
|
||||
lookup,
|
||||
// Only called if not cached.
|
||||
func() (*gtsmodel.DomainPermissionDraft, error) {
|
||||
var permDraft gtsmodel.DomainPermissionDraft
|
||||
if err := dbQuery(&permDraft); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permDraft, nil
|
||||
},
|
||||
keyParts...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gtscontext.Barebones(ctx) {
|
||||
// No need to fully populate.
|
||||
return permDraft, nil
|
||||
}
|
||||
|
||||
if permDraft.CreatedByAccount == nil {
|
||||
// Not set, fetch from database.
|
||||
permDraft.CreatedByAccount, err = d.state.DB.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
permDraft.CreatedByAccountID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error populating created by account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return permDraft, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) GetDomainPermissionDraftByID(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*gtsmodel.DomainPermissionDraft, error) {
|
||||
return d.getDomainPermissionDraft(
|
||||
ctx,
|
||||
"ID",
|
||||
func(permDraft *gtsmodel.DomainPermissionDraft) error {
|
||||
return d.db.
|
||||
NewSelect().
|
||||
Model(permDraft).
|
||||
Where("? = ?", bun.Ident("domain_permission_draft.id"), id).
|
||||
Scan(ctx)
|
||||
},
|
||||
id,
|
||||
)
|
||||
}
|
||||
|
||||
func (d *domainDB) GetDomainPermissionDrafts(
|
||||
ctx context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
permSubID string,
|
||||
domain string,
|
||||
page *paging.Page,
|
||||
) (
|
||||
[]*gtsmodel.DomainPermissionDraft,
|
||||
error,
|
||||
) {
|
||||
var (
|
||||
// Get paging params.
|
||||
minID = page.GetMin()
|
||||
maxID = page.GetMax()
|
||||
limit = page.GetLimit()
|
||||
order = page.GetOrder()
|
||||
|
||||
// Make educated guess for slice size
|
||||
permDraftIDs = make([]string, 0, limit)
|
||||
)
|
||||
|
||||
q := d.db.
|
||||
NewSelect().
|
||||
TableExpr(
|
||||
"? AS ?",
|
||||
bun.Ident("domain_permission_drafts"),
|
||||
bun.Ident("domain_permission_draft"),
|
||||
).
|
||||
// Select only IDs from table
|
||||
Column("domain_permission_draft.id")
|
||||
|
||||
// Return only items with id
|
||||
// lower than provided maxID.
|
||||
if maxID != "" {
|
||||
q = q.Where(
|
||||
"? < ?",
|
||||
bun.Ident("domain_permission_draft.id"),
|
||||
maxID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items with id
|
||||
// greater than provided minID.
|
||||
if minID != "" {
|
||||
q = q.Where(
|
||||
"? > ?",
|
||||
bun.Ident("domain_permission_draft.id"),
|
||||
minID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items with
|
||||
// given permission type.
|
||||
if permType != gtsmodel.DomainPermissionUnknown {
|
||||
q = q.Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_draft.permission_type"),
|
||||
permType,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items with
|
||||
// given subscription ID.
|
||||
if permSubID != "" {
|
||||
q = q.Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_draft.subscription_id"),
|
||||
permSubID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items
|
||||
// with given domain.
|
||||
if domain != "" {
|
||||
var err error
|
||||
|
||||
// Normalize domain as punycode.
|
||||
domain, err = util.Punify(domain)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error punifying domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
q = q.Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_draft.domain"),
|
||||
domain,
|
||||
)
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
// Limit amount of
|
||||
// items returned.
|
||||
q = q.Limit(limit)
|
||||
}
|
||||
|
||||
if order == paging.OrderAscending {
|
||||
// Page up.
|
||||
q = q.OrderExpr(
|
||||
"? ASC",
|
||||
bun.Ident("domain_permission_draft.id"),
|
||||
)
|
||||
} else {
|
||||
// Page down.
|
||||
q = q.OrderExpr(
|
||||
"? DESC",
|
||||
bun.Ident("domain_permission_draft.id"),
|
||||
)
|
||||
}
|
||||
|
||||
if err := q.Scan(ctx, &permDraftIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Catch case of no items early
|
||||
if len(permDraftIDs) == 0 {
|
||||
return nil, db.ErrNoEntries
|
||||
}
|
||||
|
||||
// If we're paging up, we still want items
|
||||
// to be sorted by ID desc, so reverse slice.
|
||||
if order == paging.OrderAscending {
|
||||
slices.Reverse(permDraftIDs)
|
||||
}
|
||||
|
||||
// Allocate return slice (will be at most len permDraftIDs)
|
||||
permDrafts := make([]*gtsmodel.DomainPermissionDraft, 0, len(permDraftIDs))
|
||||
for _, id := range permDraftIDs {
|
||||
permDraft, err := d.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting domain permission draft %q: %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Append to return slice
|
||||
permDrafts = append(permDrafts, permDraft)
|
||||
}
|
||||
|
||||
return permDrafts, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) PutDomainPermissionDraft(
|
||||
ctx context.Context,
|
||||
permDraft *gtsmodel.DomainPermissionDraft,
|
||||
) error {
|
||||
var err error
|
||||
|
||||
// Normalize the domain as punycode
|
||||
permDraft.Domain, err = util.Punify(permDraft.Domain)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error punifying domain %s: %w", permDraft.Domain, err)
|
||||
}
|
||||
|
||||
return d.state.Caches.DB.DomainPermissionDraft.Store(
|
||||
permDraft,
|
||||
func() error {
|
||||
_, err := d.db.
|
||||
NewInsert().
|
||||
Model(permDraft).
|
||||
Exec(ctx)
|
||||
return err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (d *domainDB) DeleteDomainPermissionDraft(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) error {
|
||||
// Delete the permDraft from DB.
|
||||
q := d.db.NewDelete().
|
||||
TableExpr(
|
||||
"? AS ?",
|
||||
bun.Ident("domain_permission_drafts"),
|
||||
bun.Ident("domain_permission_draft"),
|
||||
).
|
||||
Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_draft.id"),
|
||||
id,
|
||||
)
|
||||
|
||||
_, err := q.Exec(ctx)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Invalidate any cached model by ID.
|
||||
d.state.Caches.DB.DomainPermissionDraft.Invalidate("ID", id)
|
||||
|
||||
return nil
|
||||
}
|
275
internal/db/bundb/domainpermissionignore.go
Normal file
275
internal/db/bundb/domainpermissionignore.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bundb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func (d *domainDB) PutDomainPermissionIgnore(
|
||||
ctx context.Context,
|
||||
ignore *gtsmodel.DomainPermissionIgnore,
|
||||
) error {
|
||||
// Normalize the domain as punycode
|
||||
var err error
|
||||
ignore.Domain, err = util.Punify(ignore.Domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attempt to store domain perm ignore in DB
|
||||
if _, err := d.db.NewInsert().
|
||||
Model(ignore).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the domain perm ignore cache (for later reload)
|
||||
d.state.Caches.DB.DomainPermissionIgnore.Clear()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *domainDB) IsDomainPermissionIgnored(ctx context.Context, domain string) (bool, error) {
|
||||
// Normalize the domain as punycode
|
||||
domain, err := util.Punify(domain)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if our host and given domain are equal
|
||||
// or part of the same second-level domain; we
|
||||
// always ignore such perms as creating blocks
|
||||
// or allows in such cases may break things.
|
||||
if dns.CompareDomainName(domain, config.GetHost()) >= 2 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Func to scan list of all
|
||||
// ignored domain perms from DB.
|
||||
loadF := func() ([]string, error) {
|
||||
var domains []string
|
||||
|
||||
if err := d.db.
|
||||
NewSelect().
|
||||
Table("domain_ignores").
|
||||
Column("domain").
|
||||
Scan(ctx, &domains); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
// Check the cache for a domain perm ignore,
|
||||
// hydrating the cache with loadF if necessary.
|
||||
return d.state.Caches.DB.DomainPermissionIgnore.Matches(domain, loadF)
|
||||
}
|
||||
|
||||
func (d *domainDB) GetDomainPermissionIgnoreByID(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*gtsmodel.DomainPermissionIgnore, error) {
|
||||
ignore := new(gtsmodel.DomainPermissionIgnore)
|
||||
|
||||
q := d.db.
|
||||
NewSelect().
|
||||
Model(ignore).
|
||||
Where("? = ?", bun.Ident("domain_permission_ignore.id"), id)
|
||||
if err := q.Scan(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gtscontext.Barebones(ctx) {
|
||||
// No need to fully populate.
|
||||
return ignore, nil
|
||||
}
|
||||
|
||||
if ignore.CreatedByAccount == nil {
|
||||
// Not set, fetch from database.
|
||||
var err error
|
||||
ignore.CreatedByAccount, err = d.state.DB.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
ignore.CreatedByAccountID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error populating created by account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return ignore, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) GetDomainPermissionIgnores(
|
||||
ctx context.Context,
|
||||
domain string,
|
||||
page *paging.Page,
|
||||
) (
|
||||
[]*gtsmodel.DomainPermissionIgnore,
|
||||
error,
|
||||
) {
|
||||
var (
|
||||
// Get paging params.
|
||||
minID = page.GetMin()
|
||||
maxID = page.GetMax()
|
||||
limit = page.GetLimit()
|
||||
order = page.GetOrder()
|
||||
|
||||
// Make educated guess for slice size
|
||||
ignoreIDs = make([]string, 0, limit)
|
||||
)
|
||||
|
||||
q := d.db.
|
||||
NewSelect().
|
||||
TableExpr(
|
||||
"? AS ?",
|
||||
bun.Ident("domain_permission_ignores"),
|
||||
bun.Ident("domain_permission_ignore"),
|
||||
).
|
||||
// Select only IDs from table
|
||||
Column("domain_permission_ignore.id")
|
||||
|
||||
// Return only items with id
|
||||
// lower than provided maxID.
|
||||
if maxID != "" {
|
||||
q = q.Where(
|
||||
"? < ?",
|
||||
bun.Ident("domain_permission_ignore.id"),
|
||||
maxID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items with id
|
||||
// greater than provided minID.
|
||||
if minID != "" {
|
||||
q = q.Where(
|
||||
"? > ?",
|
||||
bun.Ident("domain_permission_ignore.id"),
|
||||
minID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items
|
||||
// with given domain.
|
||||
if domain != "" {
|
||||
var err error
|
||||
|
||||
// Normalize domain as punycode.
|
||||
domain, err = util.Punify(domain)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error punifying domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
q = q.Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_ignore.domain"),
|
||||
domain,
|
||||
)
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
// Limit amount of
|
||||
// items returned.
|
||||
q = q.Limit(limit)
|
||||
}
|
||||
|
||||
if order == paging.OrderAscending {
|
||||
// Page up.
|
||||
q = q.OrderExpr(
|
||||
"? ASC",
|
||||
bun.Ident("domain_permission_ignore.id"),
|
||||
)
|
||||
} else {
|
||||
// Page down.
|
||||
q = q.OrderExpr(
|
||||
"? DESC",
|
||||
bun.Ident("domain_permission_ignore.id"),
|
||||
)
|
||||
}
|
||||
|
||||
if err := q.Scan(ctx, &ignoreIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Catch case of no items early
|
||||
if len(ignoreIDs) == 0 {
|
||||
return nil, db.ErrNoEntries
|
||||
}
|
||||
|
||||
// If we're paging up, we still want items
|
||||
// to be sorted by ID desc, so reverse slice.
|
||||
if order == paging.OrderAscending {
|
||||
slices.Reverse(ignoreIDs)
|
||||
}
|
||||
|
||||
// Allocate return slice (will be at most len permSubIDs).
|
||||
ignores := make([]*gtsmodel.DomainPermissionIgnore, 0, len(ignoreIDs))
|
||||
for _, id := range ignoreIDs {
|
||||
ignore, err := d.GetDomainPermissionIgnoreByID(ctx, id)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting domain permission ignore %q: %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Append to return slice
|
||||
ignores = append(ignores, ignore)
|
||||
}
|
||||
|
||||
return ignores, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) DeleteDomainPermissionIgnore(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) error {
|
||||
// Delete the permSub from DB.
|
||||
q := d.db.NewDelete().
|
||||
TableExpr(
|
||||
"? AS ?",
|
||||
bun.Ident("domain_permission_ignores"),
|
||||
bun.Ident("domain_permission_ignore"),
|
||||
).
|
||||
Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_ignore.id"),
|
||||
id,
|
||||
)
|
||||
|
||||
_, err := q.Exec(ctx)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the domain perm ignore cache (for later reload)
|
||||
d.state.Caches.DB.DomainPermissionIgnore.Clear()
|
||||
|
||||
return nil
|
||||
}
|
246
internal/db/bundb/domainpermissionsubscription.go
Normal file
246
internal/db/bundb/domainpermissionsubscription.go
Normal file
|
@ -0,0 +1,246 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bundb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func (d *domainDB) getDomainPermissionSubscription(
|
||||
ctx context.Context,
|
||||
lookup string,
|
||||
dbQuery func(*gtsmodel.DomainPermissionSubscription) error,
|
||||
keyParts ...any,
|
||||
) (*gtsmodel.DomainPermissionSubscription, error) {
|
||||
// Fetch perm subscription from database cache with loader callback.
|
||||
permSub, err := d.state.Caches.DB.DomainPermissionSubscription.LoadOne(
|
||||
lookup,
|
||||
// Only called if not cached.
|
||||
func() (*gtsmodel.DomainPermissionSubscription, error) {
|
||||
var permSub gtsmodel.DomainPermissionSubscription
|
||||
if err := dbQuery(&permSub); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permSub, nil
|
||||
},
|
||||
keyParts...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gtscontext.Barebones(ctx) {
|
||||
// No need to fully populate.
|
||||
return permSub, nil
|
||||
}
|
||||
|
||||
if permSub.CreatedByAccount == nil {
|
||||
// Not set, fetch from database.
|
||||
permSub.CreatedByAccount, err = d.state.DB.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
permSub.CreatedByAccountID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error populating created by account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return permSub, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) GetDomainPermissionSubscriptionByID(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*gtsmodel.DomainPermissionSubscription, error) {
|
||||
return d.getDomainPermissionSubscription(
|
||||
ctx,
|
||||
"ID",
|
||||
func(permSub *gtsmodel.DomainPermissionSubscription) error {
|
||||
return d.db.
|
||||
NewSelect().
|
||||
Model(permSub).
|
||||
Where("? = ?", bun.Ident("domain_permission_subscription.id"), id).
|
||||
Scan(ctx)
|
||||
},
|
||||
id,
|
||||
)
|
||||
}
|
||||
|
||||
func (d *domainDB) GetDomainPermissionSubscriptions(
|
||||
ctx context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
page *paging.Page,
|
||||
) (
|
||||
[]*gtsmodel.DomainPermissionSubscription,
|
||||
error,
|
||||
) {
|
||||
var (
|
||||
// Get paging params.
|
||||
minID = page.GetMin()
|
||||
maxID = page.GetMax()
|
||||
limit = page.GetLimit()
|
||||
order = page.GetOrder()
|
||||
|
||||
// Make educated guess for slice size
|
||||
permSubIDs = make([]string, 0, limit)
|
||||
)
|
||||
|
||||
q := d.db.
|
||||
NewSelect().
|
||||
TableExpr(
|
||||
"? AS ?",
|
||||
bun.Ident("domain_permission_subscriptions"),
|
||||
bun.Ident("domain_permission_subscription"),
|
||||
).
|
||||
// Select only IDs from table
|
||||
Column("domain_permission_subscription.id")
|
||||
|
||||
// Return only items with id
|
||||
// lower than provided maxID.
|
||||
if maxID != "" {
|
||||
q = q.Where(
|
||||
"? < ?",
|
||||
bun.Ident("domain_permission_subscription.id"),
|
||||
maxID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items with id
|
||||
// greater than provided minID.
|
||||
if minID != "" {
|
||||
q = q.Where(
|
||||
"? > ?",
|
||||
bun.Ident("domain_permission_subscription.id"),
|
||||
minID,
|
||||
)
|
||||
}
|
||||
|
||||
// Return only items with
|
||||
// given permission type.
|
||||
if permType != gtsmodel.DomainPermissionUnknown {
|
||||
q = q.Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_subscription.permission_type"),
|
||||
permType,
|
||||
)
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
// Limit amount of
|
||||
// items returned.
|
||||
q = q.Limit(limit)
|
||||
}
|
||||
|
||||
if order == paging.OrderAscending {
|
||||
// Page up.
|
||||
q = q.OrderExpr(
|
||||
"? ASC",
|
||||
bun.Ident("domain_permission_subscription.id"),
|
||||
)
|
||||
} else {
|
||||
// Page down.
|
||||
q = q.OrderExpr(
|
||||
"? DESC",
|
||||
bun.Ident("domain_permission_subscription.id"),
|
||||
)
|
||||
}
|
||||
|
||||
if err := q.Scan(ctx, &permSubIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Catch case of no items early
|
||||
if len(permSubIDs) == 0 {
|
||||
return nil, db.ErrNoEntries
|
||||
}
|
||||
|
||||
// If we're paging up, we still want items
|
||||
// to be sorted by ID desc, so reverse slice.
|
||||
if order == paging.OrderAscending {
|
||||
slices.Reverse(permSubIDs)
|
||||
}
|
||||
|
||||
// Allocate return slice (will be at most len permSubIDs).
|
||||
permSubs := make([]*gtsmodel.DomainPermissionSubscription, 0, len(permSubIDs))
|
||||
for _, id := range permSubIDs {
|
||||
permSub, err := d.GetDomainPermissionSubscriptionByID(ctx, id)
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "error getting domain permission subscription %q: %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Append to return slice
|
||||
permSubs = append(permSubs, permSub)
|
||||
}
|
||||
|
||||
return permSubs, nil
|
||||
}
|
||||
|
||||
func (d *domainDB) PutDomainPermissionSubscription(
|
||||
ctx context.Context,
|
||||
permSubscription *gtsmodel.DomainPermissionSubscription,
|
||||
) error {
|
||||
return d.state.Caches.DB.DomainPermissionSubscription.Store(
|
||||
permSubscription,
|
||||
func() error {
|
||||
_, err := d.db.
|
||||
NewInsert().
|
||||
Model(permSubscription).
|
||||
Exec(ctx)
|
||||
return err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (d *domainDB) DeleteDomainPermissionSubscription(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) error {
|
||||
// Delete the permSub from DB.
|
||||
q := d.db.NewDelete().
|
||||
TableExpr(
|
||||
"? AS ?",
|
||||
bun.Ident("domain_permission_subscriptions"),
|
||||
bun.Ident("domain_permission_subscription"),
|
||||
).
|
||||
Where(
|
||||
"? = ?",
|
||||
bun.Ident("domain_permission_subscription.id"),
|
||||
id,
|
||||
)
|
||||
|
||||
_, err := q.Exec(ctx)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Invalidate any cached model by ID.
|
||||
d.state.Caches.DB.DomainPermissionSubscription.Invalidate("ID", id)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// 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_drafts`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionDraft)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create `domain_permission_subscriptions`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionSubscription)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create `domain_permission_ignores`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionIgnore)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes. Indices. Indie sexes.
|
||||
for table, indexes := range map[string]map[string][]string{
|
||||
"domain_permission_drafts": {
|
||||
"domain_permission_drafts_domain_idx": {"domain"},
|
||||
"domain_permission_drafts_subscription_id_idx": {"subscription_id"},
|
||||
},
|
||||
"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)
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"net/url"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
// Domain contains DB functions related to domains and domain blocks.
|
||||
|
@ -42,6 +43,9 @@ type Domain interface {
|
|||
// GetDomainAllows returns all instance-level domain allows currently enforced by this instance.
|
||||
GetDomainAllows(ctx context.Context) ([]*gtsmodel.DomainAllow, error)
|
||||
|
||||
// UpdateDomainAllow updates the given domain allow, setting the provided columns (empty for all).
|
||||
UpdateDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow, columns ...string) error
|
||||
|
||||
// DeleteDomainAllow deletes an instance-level domain allow with the given domain, if it exists.
|
||||
DeleteDomainAllow(ctx context.Context, domain string) error
|
||||
|
||||
|
@ -57,6 +61,9 @@ type Domain interface {
|
|||
// GetDomainBlocks returns all instance-level domain blocks currently enforced by this instance.
|
||||
GetDomainBlocks(ctx context.Context) ([]*gtsmodel.DomainBlock, error)
|
||||
|
||||
// UpdateDomainBlock updates the given domain block, setting the provided columns (empty for all).
|
||||
UpdateDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock, columns ...string) error
|
||||
|
||||
// DeleteDomainBlock deletes an instance-level domain block with the given domain, if it exists.
|
||||
DeleteDomainBlock(ctx context.Context, domain string) error
|
||||
|
||||
|
@ -78,4 +85,69 @@ type Domain interface {
|
|||
// AreURIsBlocked calls IsURIBlocked for each URI.
|
||||
// Will return true if even one of the given URIs is blocked.
|
||||
AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, error)
|
||||
|
||||
/*
|
||||
Domain permission draft stuff.
|
||||
*/
|
||||
|
||||
// GetDomainPermissionDraftByID gets one DomainPermissionDraft with the given ID.
|
||||
GetDomainPermissionDraftByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionDraft, error)
|
||||
|
||||
// GetDomainPermissionDrafts returns a page of
|
||||
// DomainPermissionDrafts using the given parameters.
|
||||
GetDomainPermissionDrafts(
|
||||
ctx context.Context,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
permSubID string,
|
||||
domain string,
|
||||
page *paging.Page,
|
||||
) ([]*gtsmodel.DomainPermissionDraft, error)
|
||||
|
||||
// PutDomainPermissionDraft stores one DomainPermissionDraft.
|
||||
PutDomainPermissionDraft(ctx context.Context, permDraft *gtsmodel.DomainPermissionDraft) error
|
||||
|
||||
// DeleteDomainPermissionDraft deletes one DomainPermissionDraft with the given id.
|
||||
DeleteDomainPermissionDraft(ctx context.Context, id string) error
|
||||
|
||||
/*
|
||||
Domain permission ignore stuff.
|
||||
*/
|
||||
|
||||
// GetDomainPermissionIgnoreByID gets one DomainPermissionIgnore with the given ID.
|
||||
GetDomainPermissionIgnoreByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionIgnore, error)
|
||||
|
||||
// GetDomainPermissionIgnores returns a page of
|
||||
// DomainPermissionIgnores using the given parameters.
|
||||
GetDomainPermissionIgnores(
|
||||
ctx context.Context,
|
||||
domain string,
|
||||
page *paging.Page,
|
||||
) ([]*gtsmodel.DomainPermissionIgnore, error)
|
||||
|
||||
// PutDomainPermissionIgnore stores one DomainPermissionIgnore.
|
||||
PutDomainPermissionIgnore(ctx context.Context, permIgnore *gtsmodel.DomainPermissionIgnore) error
|
||||
|
||||
// DeleteDomainPermissionIgnore deletes one DomainPermissionIgnore with the given id.
|
||||
DeleteDomainPermissionIgnore(ctx context.Context, id string) error
|
||||
|
||||
/*
|
||||
Domain permission subscription stuff.
|
||||
*/
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ func (d *DomainAllow) GetUpdatedAt() time.Time {
|
|||
return d.UpdatedAt
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetUpdatedAt(i time.Time) {
|
||||
d.UpdatedAt = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetDomain() string {
|
||||
return d.Domain
|
||||
}
|
||||
|
@ -53,26 +57,50 @@ func (d *DomainAllow) GetCreatedByAccountID() string {
|
|||
return d.CreatedByAccountID
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetCreatedByAccountID(i string) {
|
||||
d.CreatedByAccountID = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetCreatedByAccount() *Account {
|
||||
return d.CreatedByAccount
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetCreatedByAccount(i *Account) {
|
||||
d.CreatedByAccount = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetPrivateComment() string {
|
||||
return d.PrivateComment
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetPrivateComment(i string) {
|
||||
d.PrivateComment = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetPublicComment() string {
|
||||
return d.PublicComment
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetPublicComment(i string) {
|
||||
d.PublicComment = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetObfuscate() *bool {
|
||||
return d.Obfuscate
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetObfuscate(i *bool) {
|
||||
d.Obfuscate = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetSubscriptionID() string {
|
||||
return d.SubscriptionID
|
||||
}
|
||||
|
||||
func (d *DomainAllow) SetSubscriptionID(i string) {
|
||||
d.SubscriptionID = i
|
||||
}
|
||||
|
||||
func (d *DomainAllow) GetType() DomainPermissionType {
|
||||
return DomainPermissionAllow
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ func (d *DomainBlock) GetUpdatedAt() time.Time {
|
|||
return d.UpdatedAt
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetUpdatedAt(i time.Time) {
|
||||
d.UpdatedAt = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetDomain() string {
|
||||
return d.Domain
|
||||
}
|
||||
|
@ -53,26 +57,50 @@ func (d *DomainBlock) GetCreatedByAccountID() string {
|
|||
return d.CreatedByAccountID
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetCreatedByAccountID(i string) {
|
||||
d.CreatedByAccountID = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetCreatedByAccount() *Account {
|
||||
return d.CreatedByAccount
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetCreatedByAccount(i *Account) {
|
||||
d.CreatedByAccount = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetPrivateComment() string {
|
||||
return d.PrivateComment
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetPrivateComment(i string) {
|
||||
d.PrivateComment = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetPublicComment() string {
|
||||
return d.PublicComment
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetPublicComment(i string) {
|
||||
d.PublicComment = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetObfuscate() *bool {
|
||||
return d.Obfuscate
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetObfuscate(i *bool) {
|
||||
d.Obfuscate = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetSubscriptionID() string {
|
||||
return d.SubscriptionID
|
||||
}
|
||||
|
||||
func (d *DomainBlock) SetSubscriptionID(i string) {
|
||||
d.SubscriptionID = i
|
||||
}
|
||||
|
||||
func (d *DomainBlock) GetType() DomainPermissionType {
|
||||
return DomainPermissionBlock
|
||||
}
|
||||
|
|
|
@ -19,19 +19,26 @@
|
|||
|
||||
import "time"
|
||||
|
||||
// DomainPermission models a domain
|
||||
// permission entry (block/allow).
|
||||
// DomainPermission models a domain permission
|
||||
// entry -- block / allow / draft / ignore.
|
||||
type DomainPermission interface {
|
||||
GetID() string
|
||||
GetCreatedAt() time.Time
|
||||
GetUpdatedAt() time.Time
|
||||
SetUpdatedAt(i time.Time)
|
||||
GetDomain() string
|
||||
GetCreatedByAccountID() string
|
||||
SetCreatedByAccountID(i string)
|
||||
GetCreatedByAccount() *Account
|
||||
SetCreatedByAccount(i *Account)
|
||||
GetPrivateComment() string
|
||||
SetPrivateComment(i string)
|
||||
GetPublicComment() string
|
||||
SetPublicComment(i string)
|
||||
GetObfuscate() *bool
|
||||
SetObfuscate(i *bool)
|
||||
GetSubscriptionID() string
|
||||
SetSubscriptionID(i string)
|
||||
GetType() DomainPermissionType
|
||||
}
|
||||
|
||||
|
|
106
internal/gtsmodel/domainpermissiondraft.go
Normal file
106
internal/gtsmodel/domainpermissiondraft.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
// 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 DomainPermissionDraft 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.
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was last updated.
|
||||
PermissionType DomainPermissionType `bun:",notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // Permission type of the draft.
|
||||
Domain string `bun:",nullzero,notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // Domain to block or allow. Eg. 'whatever.com'.
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription.
|
||||
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
|
||||
PrivateComment string `bun:",nullzero"` // Private comment on this perm, viewable to admins.
|
||||
PublicComment string `bun:",nullzero"` // Public comment on this perm, viewable (optionally) by everyone.
|
||||
Obfuscate *bool `bun:",nullzero,notnull,default:false"` // Obfuscate domain name when displaying it publicly.
|
||||
SubscriptionID string `bun:"type:CHAR(26),unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // ID of the subscription that created this draft, if any.
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetID() string {
|
||||
return d.ID
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetCreatedAt() time.Time {
|
||||
return d.CreatedAt
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetUpdatedAt() time.Time {
|
||||
return d.UpdatedAt
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetUpdatedAt(i time.Time) {
|
||||
d.UpdatedAt = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetDomain() string {
|
||||
return d.Domain
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetCreatedByAccountID() string {
|
||||
return d.CreatedByAccountID
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetCreatedByAccountID(i string) {
|
||||
d.CreatedByAccountID = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetCreatedByAccount() *Account {
|
||||
return d.CreatedByAccount
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetCreatedByAccount(i *Account) {
|
||||
d.CreatedByAccount = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetPrivateComment() string {
|
||||
return d.PrivateComment
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetPrivateComment(i string) {
|
||||
d.PrivateComment = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetPublicComment() string {
|
||||
return d.PublicComment
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetPublicComment(i string) {
|
||||
d.PublicComment = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetObfuscate() *bool {
|
||||
return d.Obfuscate
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetObfuscate(i *bool) {
|
||||
d.Obfuscate = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetSubscriptionID() string {
|
||||
return d.SubscriptionID
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) SetSubscriptionID(i string) {
|
||||
d.SubscriptionID = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionDraft) GetType() DomainPermissionType {
|
||||
return d.PermissionType
|
||||
}
|
92
internal/gtsmodel/domainpermissionignore.go
Normal file
92
internal/gtsmodel/domainpermissionignore.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
// 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"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// DomainPermissionIgnore represents one domain that should be ignored
|
||||
// when domain permission (ignores) are created from subscriptions.
|
||||
type DomainPermissionIgnore 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.
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was last updated.
|
||||
Domain string `bun:",nullzero,notnull,unique"` // Domain to ignore. Eg. 'whatever.com'.
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this ignore.
|
||||
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
|
||||
PrivateComment string `bun:",nullzero"` // Private comment on this ignore, viewable to admins.
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetID() string {
|
||||
return d.ID
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetCreatedAt() time.Time {
|
||||
return d.CreatedAt
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetUpdatedAt() time.Time {
|
||||
return d.UpdatedAt
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) SetUpdatedAt(i time.Time) {
|
||||
d.UpdatedAt = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetDomain() string {
|
||||
return d.Domain
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetCreatedByAccountID() string {
|
||||
return d.CreatedByAccountID
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) SetCreatedByAccountID(i string) {
|
||||
d.CreatedByAccountID = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetCreatedByAccount() *Account {
|
||||
return d.CreatedByAccount
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) SetCreatedByAccount(i *Account) {
|
||||
d.CreatedByAccount = i
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) GetPrivateComment() string {
|
||||
return d.PrivateComment
|
||||
}
|
||||
|
||||
func (d *DomainPermissionIgnore) SetPrivateComment(i string) {
|
||||
d.PrivateComment = i
|
||||
}
|
||||
|
||||
/*
|
||||
Stubbed functions for interface purposes.
|
||||
*/
|
||||
|
||||
func (d *DomainPermissionIgnore) GetPublicComment() string { return "" }
|
||||
func (d *DomainPermissionIgnore) SetPublicComment(_ string) {}
|
||||
func (d *DomainPermissionIgnore) GetObfuscate() *bool { return util.Ptr(false) }
|
||||
func (d *DomainPermissionIgnore) SetObfuscate(_ *bool) {}
|
||||
func (d *DomainPermissionIgnore) GetSubscriptionID() string { return "" }
|
||||
func (d *DomainPermissionIgnore) SetSubscriptionID(_ string) {}
|
||||
func (d *DomainPermissionIgnore) GetType() DomainPermissionType { return DomainPermissionUnknown }
|
38
internal/gtsmodel/domainpermissionsubscription.go
Normal file
38
internal/gtsmodel/domainpermissionsubscription.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// 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"` // Moderator-set title for this list.
|
||||
PermissionType DomainPermissionType `bun:",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.
|
||||
ContentType string `bun:",nullzero,notnull"` // Content type to expect from the URI.
|
||||
URI string `bun:",unique,nullzero,notnull"` // URI of the domain permission list.
|
||||
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.
|
||||
IsError *bool `bun:",nullzero,notnull,default:false"` // True if last fetch attempt of URI resulted in an error.
|
||||
Error string `bun:",nullzero"` // If IsError=true, this field contains the error resulting from the attempted fetch.
|
||||
Count uint64 `bun:""` // Count of domain permission entries discovered at URI.
|
||||
}
|
|
@ -31,24 +31,6 @@
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
// apiDomainPerm is a cheeky shortcut for returning
|
||||
// the API version of the given domain permission
|
||||
// (*gtsmodel.DomainBlock or *gtsmodel.DomainAllow),
|
||||
// or an appropriate error if something goes wrong.
|
||||
func (p *Processor) apiDomainPerm(
|
||||
ctx context.Context,
|
||||
domainPermission gtsmodel.DomainPermission,
|
||||
export bool,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
apiDomainPerm, err := p.converter.DomainPermToAPIDomainPerm(ctx, domainPermission, export)
|
||||
if err != nil {
|
||||
err := gtserror.NewfAt(3, "error converting domain permission to api model: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return apiDomainPerm, nil
|
||||
}
|
||||
|
||||
// DomainPermissionCreate creates an instance-level permission
|
||||
// targeting the given domain, and then processes any side
|
||||
// effects of the permission creation.
|
||||
|
|
325
internal/processing/admin/domainpermissiondraft.go
Normal file
325
internal/processing/admin/domainpermissiondraft.go
Normal file
|
@ -0,0 +1,325 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// DomainPermissionDraftGet returns one
|
||||
// domain permission draft with the given id.
|
||||
func (p *Processor) DomainPermissionDraftGet(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permDraft == nil {
|
||||
err := fmt.Errorf("domain permission draft %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permDraft, false)
|
||||
}
|
||||
|
||||
// DomainPermissionDraftsGet returns a page of
|
||||
// DomainPermissionDrafts with the given parameters.
|
||||
func (p *Processor) DomainPermissionDraftsGet(
|
||||
ctx context.Context,
|
||||
subscriptionID string,
|
||||
domain string,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
page *paging.Page,
|
||||
) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
permDrafts, err := p.state.DB.GetDomainPermissionDrafts(
|
||||
ctx,
|
||||
permType,
|
||||
subscriptionID,
|
||||
domain,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
count := len(permDrafts)
|
||||
if count == 0 {
|
||||
return paging.EmptyResponse(), nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := permDrafts[count-1].ID
|
||||
hi := permDrafts[0].ID
|
||||
|
||||
// Convert each perm draft to API model.
|
||||
items := make([]any, len(permDrafts))
|
||||
for i, permDraft := range permDrafts {
|
||||
apiPermDraft, err := p.apiDomainPerm(ctx, permDraft, false)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
items[i] = apiPermDraft
|
||||
}
|
||||
|
||||
// Assemble next/prev page queries.
|
||||
query := make(url.Values, 3)
|
||||
if subscriptionID != "" {
|
||||
query.Set(apiutil.DomainPermissionSubscriptionIDKey, subscriptionID)
|
||||
}
|
||||
if domain != "" {
|
||||
query.Set(apiutil.DomainPermissionDomainKey, domain)
|
||||
}
|
||||
if permType != gtsmodel.DomainPermissionUnknown {
|
||||
query.Set(apiutil.DomainPermissionPermTypeKey, permType.String())
|
||||
}
|
||||
|
||||
return paging.PackageResponse(paging.ResponseParams{
|
||||
Items: items,
|
||||
Path: "/api/v1/admin/domain_permission_drafts",
|
||||
Next: page.Next(lo, hi),
|
||||
Prev: page.Prev(lo, hi),
|
||||
Query: query,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionDraftCreate(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
domain string,
|
||||
permType gtsmodel.DomainPermissionType,
|
||||
obfuscate bool,
|
||||
publicComment string,
|
||||
privateComment string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft := >smodel.DomainPermissionDraft{
|
||||
ID: id.NewULID(),
|
||||
PermissionType: permType,
|
||||
Domain: domain,
|
||||
CreatedByAccountID: acct.ID,
|
||||
CreatedByAccount: acct,
|
||||
PrivateComment: privateComment,
|
||||
PublicComment: publicComment,
|
||||
Obfuscate: &obfuscate,
|
||||
}
|
||||
|
||||
if err := p.state.DB.PutDomainPermissionDraft(ctx, permDraft); err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
const text = "a domain permission draft already exists with this permission type, domain, and subscription ID"
|
||||
err := fmt.Errorf("%w: %s", err, text)
|
||||
return nil, gtserror.NewErrorConflict(err, text)
|
||||
}
|
||||
|
||||
// Real error.
|
||||
err := gtserror.Newf("db error putting domain permission draft: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permDraft, false)
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionDraftAccept(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
id string,
|
||||
overwrite bool,
|
||||
) (*apimodel.DomainPermission, string, gtserror.WithCode) {
|
||||
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
|
||||
return nil, "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permDraft == nil {
|
||||
err := fmt.Errorf("domain permission draft %s not found", id)
|
||||
return nil, "", gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
var (
|
||||
// Existing permission
|
||||
// entry, if it exists.
|
||||
existing gtsmodel.DomainPermission
|
||||
)
|
||||
|
||||
// Try to get existing entry.
|
||||
switch permDraft.PermissionType {
|
||||
case gtsmodel.DomainPermissionBlock:
|
||||
existing, err = p.state.DB.GetDomainBlock(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
permDraft.Domain,
|
||||
)
|
||||
case gtsmodel.DomainPermissionAllow:
|
||||
existing, err = p.state.DB.GetDomainAllow(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
permDraft.Domain,
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission %s: %w", id, err)
|
||||
return nil, "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Check if we got existing entry.
|
||||
existed := !util.IsNil(existing)
|
||||
if existed && !overwrite {
|
||||
// Domain permission exists and we shouldn't
|
||||
// overwrite it, leave everything alone.
|
||||
const text = "a domain permission already exists with this permission type and domain"
|
||||
return nil, "", gtserror.NewErrorConflict(errors.New(text), text)
|
||||
}
|
||||
|
||||
// Function to clean up the accepted draft, only called if
|
||||
// creating or updating permission from draft is successful.
|
||||
deleteDraft := func() {
|
||||
if err := p.state.DB.DeleteDomainPermissionDraft(ctx, permDraft.ID); err != nil {
|
||||
log.Errorf(ctx, "db error deleting domain permission draft: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
// Easy case, we just need to create a new domain
|
||||
// permission from the draft, and then delete it.
|
||||
var (
|
||||
new *apimodel.DomainPermission
|
||||
actionID string
|
||||
errWithCode gtserror.WithCode
|
||||
)
|
||||
|
||||
if permDraft.PermissionType == gtsmodel.DomainPermissionBlock {
|
||||
new, actionID, errWithCode = p.createDomainBlock(
|
||||
ctx,
|
||||
acct,
|
||||
permDraft.Domain,
|
||||
*permDraft.Obfuscate,
|
||||
permDraft.PublicComment,
|
||||
permDraft.PrivateComment,
|
||||
permDraft.SubscriptionID,
|
||||
)
|
||||
}
|
||||
|
||||
if permDraft.PermissionType == gtsmodel.DomainPermissionAllow {
|
||||
new, actionID, errWithCode = p.createDomainAllow(
|
||||
ctx,
|
||||
acct,
|
||||
permDraft.Domain,
|
||||
*permDraft.Obfuscate,
|
||||
permDraft.PublicComment,
|
||||
permDraft.PrivateComment,
|
||||
permDraft.SubscriptionID,
|
||||
)
|
||||
}
|
||||
|
||||
// Clean up the draft
|
||||
// before returning.
|
||||
deleteDraft()
|
||||
|
||||
return new, actionID, errWithCode
|
||||
} else {
|
||||
// Domain permission exists but we should overwrite
|
||||
// it by just updating the existing domain permission.
|
||||
// Domain can't change, so no need to re-run side effects.
|
||||
existing.SetCreatedByAccountID(permDraft.CreatedByAccountID)
|
||||
existing.SetCreatedByAccount(permDraft.CreatedByAccount)
|
||||
existing.SetPrivateComment(permDraft.PrivateComment)
|
||||
existing.SetPublicComment(permDraft.PublicComment)
|
||||
existing.SetObfuscate(permDraft.Obfuscate)
|
||||
existing.SetSubscriptionID(permDraft.SubscriptionID)
|
||||
|
||||
var err error
|
||||
switch dp := existing.(type) {
|
||||
case *gtsmodel.DomainBlock:
|
||||
err = p.state.DB.UpdateDomainBlock(ctx, dp)
|
||||
|
||||
case *gtsmodel.DomainAllow:
|
||||
err = p.state.DB.UpdateDomainAllow(ctx, dp)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := gtserror.Newf("db error updating existing domain permission: %w", err)
|
||||
return nil, "", gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Clean up the draft
|
||||
// before returning.
|
||||
deleteDraft()
|
||||
|
||||
apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false)
|
||||
return apiPerm, "", errWithCode
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Processor) DomainPermissionDraftRemove(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
id string,
|
||||
ignoreTarget bool,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permDraft, err := p.state.DB.GetDomainPermissionDraftByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission draft %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permDraft == nil {
|
||||
err := fmt.Errorf("domain permission draft %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
// Delete the permission draft.
|
||||
if err := p.state.DB.DeleteDomainPermissionDraft(ctx, permDraft.ID); err != nil {
|
||||
err := gtserror.Newf("db error deleting domain permission draft: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if ignoreTarget {
|
||||
// Add a domain permission ignore
|
||||
// targeting the permDraft's domain.
|
||||
_, err = p.DomainPermissionIgnoreCreate(
|
||||
ctx,
|
||||
acct,
|
||||
permDraft.Domain,
|
||||
permDraft.PrivateComment,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrAlreadyExists) {
|
||||
err := gtserror.Newf("db error creating domain permission ignore: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permDraft, false)
|
||||
}
|
134
internal/processing/admin/domainpermissionignore.go
Normal file
134
internal/processing/admin/domainpermissionignore.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
)
|
||||
|
||||
func (p *Processor) DomainPermissionIgnoreCreate(
|
||||
ctx context.Context,
|
||||
acct *gtsmodel.Account,
|
||||
domain string,
|
||||
privateComment string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permIgnore := >smodel.DomainPermissionIgnore{
|
||||
ID: id.NewULID(),
|
||||
Domain: domain,
|
||||
CreatedByAccountID: acct.ID,
|
||||
CreatedByAccount: acct,
|
||||
PrivateComment: privateComment,
|
||||
}
|
||||
|
||||
if err := p.state.DB.PutDomainPermissionIgnore(ctx, permIgnore); err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
const text = "a domain permission ignore already exists with this permission type and domain"
|
||||
err := fmt.Errorf("%w: %s", err, text)
|
||||
return nil, gtserror.NewErrorConflict(err, text)
|
||||
}
|
||||
|
||||
// Real error.
|
||||
err := gtserror.Newf("db error putting domain permission ignore: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permIgnore, false)
|
||||
}
|
||||
|
||||
// DomainPermissionIgnoreGet returns one
|
||||
// domain permission ignore with the given id.
|
||||
func (p *Processor) DomainPermissionIgnoreGet(
|
||||
ctx context.Context,
|
||||
id string,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
permIgnore, err := p.state.DB.GetDomainPermissionIgnoreByID(ctx, id)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error getting domain permission ignore %s: %w", id, err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if permIgnore == nil {
|
||||
err := fmt.Errorf("domain permission ignore %s not found", id)
|
||||
return nil, gtserror.NewErrorNotFound(err, err.Error())
|
||||
}
|
||||
|
||||
return p.apiDomainPerm(ctx, permIgnore, false)
|
||||
}
|
||||
|
||||
// DomainPermissionIgnoresGet returns a page of
|
||||
// DomainPermissionIgnores with the given parameters.
|
||||
func (p *Processor) DomainPermissionIgnoresGet(
|
||||
ctx context.Context,
|
||||
domain string,
|
||||
page *paging.Page,
|
||||
) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
permIgnores, err := p.state.DB.GetDomainPermissionIgnores(
|
||||
ctx,
|
||||
domain,
|
||||
page,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err := gtserror.Newf("db error: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
count := len(permIgnores)
|
||||
if count == 0 {
|
||||
return paging.EmptyResponse(), nil
|
||||
}
|
||||
|
||||
// Get the lowest and highest
|
||||
// ID values, used for paging.
|
||||
lo := permIgnores[count-1].ID
|
||||
hi := permIgnores[0].ID
|
||||
|
||||
// Convert each perm ignore to API model.
|
||||
items := make([]any, len(permIgnores))
|
||||
for i, permIgnore := range permIgnores {
|
||||
apiPermIgnore, err := p.apiDomainPerm(ctx, permIgnore, false)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
items[i] = apiPermIgnore
|
||||
}
|
||||
|
||||
// Assemble next/prev page queries.
|
||||
query := make(url.Values, 1)
|
||||
if domain != "" {
|
||||
query.Set(apiutil.DomainPermissionDomainKey, domain)
|
||||
}
|
||||
|
||||
return paging.PackageResponse(paging.ResponseParams{
|
||||
Items: items,
|
||||
Path: "/api/v1/admin/domain_permission_ignores",
|
||||
Next: page.Next(lo, hi),
|
||||
Prev: page.Prev(lo, hi),
|
||||
Query: query,
|
||||
}), nil
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"errors"
|
||||
"time"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
@ -97,3 +98,20 @@ func (p *Processor) rangeDomainAccounts(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apiDomainPerm is a cheeky shortcut for returning
|
||||
// the API version of the given domain permission,
|
||||
// or an appropriate error if something goes wrong.
|
||||
func (p *Processor) apiDomainPerm(
|
||||
ctx context.Context,
|
||||
domainPermission gtsmodel.DomainPermission,
|
||||
export bool,
|
||||
) (*apimodel.DomainPermission, gtserror.WithCode) {
|
||||
apiDomainPerm, err := p.converter.DomainPermToAPIDomainPerm(ctx, domainPermission, export)
|
||||
if err != nil {
|
||||
err := gtserror.NewfAt(3, "error converting domain permission to api model: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return apiDomainPerm, nil
|
||||
}
|
||||
|
|
|
@ -1937,7 +1937,8 @@ func (c *Converter) ConversationToAPIConversation(
|
|||
return apiConversation, nil
|
||||
}
|
||||
|
||||
// DomainPermToAPIDomainPerm converts a gts model domin block or allow into an api domain permission.
|
||||
// DomainPermToAPIDomainPerm converts a gtsmodel domain block,
|
||||
// allow, draft, or ignore into an api domain permission.
|
||||
func (c *Converter) DomainPermToAPIDomainPerm(
|
||||
ctx context.Context,
|
||||
d gtsmodel.DomainPermission,
|
||||
|
@ -1970,6 +1971,11 @@ func (c *Converter) DomainPermToAPIDomainPerm(
|
|||
domainPerm.CreatedBy = d.GetCreatedByAccountID()
|
||||
domainPerm.CreatedAt = util.FormatISO8601(d.GetCreatedAt())
|
||||
|
||||
// If this is a draft, also add the permission type.
|
||||
if _, ok := d.(*gtsmodel.DomainPermissionDraft); ok {
|
||||
domainPerm.PermissionType = d.GetType().String()
|
||||
}
|
||||
|
||||
return domainPerm, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package util
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// EqualPtrs returns whether the values contained within two comparable ptr types are equal.
|
||||
func EqualPtrs[T comparable](t1, t2 *T) bool {
|
||||
switch {
|
||||
|
@ -59,3 +61,8 @@ func PtrOrValue[T any](t *T, value T) T {
|
|||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func IsNil(i interface{}) bool {
|
||||
type eface struct{ _, data unsafe.Pointer }
|
||||
return (*eface)(unsafe.Pointer(&i)).data == nil
|
||||
}
|
||||
|
|
|
@ -107,7 +107,11 @@ function Error({ error, reset }: ErrorProps) {
|
|||
{ reset &&
|
||||
<span
|
||||
className="dismiss"
|
||||
onClick={reset}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
reset();
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
|
|
@ -17,18 +17,107 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { AdminAccount } from "../lib/types/account";
|
||||
import { useLazyGetAccountQuery } from "../lib/query/admin";
|
||||
import Loading from "./loading";
|
||||
import { Error as ErrorC } from "./error";
|
||||
|
||||
interface UsernameProps {
|
||||
interface UsernameLozengeProps {
|
||||
/**
|
||||
* Either an account ID (for fetching) or an account.
|
||||
*/
|
||||
account?: string | AdminAccount;
|
||||
/**
|
||||
* Make the lozenge clickable and link to this location.
|
||||
*/
|
||||
linkTo?: string;
|
||||
/**
|
||||
* Location to set as backLocation after linking to linkTo.
|
||||
*/
|
||||
backLocation?: string;
|
||||
/**
|
||||
* Additional classnames to add to the lozenge.
|
||||
*/
|
||||
classNames?: string[];
|
||||
}
|
||||
|
||||
export default function UsernameLozenge({ account, linkTo, backLocation, classNames }: UsernameLozengeProps) {
|
||||
if (account === undefined) {
|
||||
return <>[unknown]</>;
|
||||
} else if (typeof account === "string") {
|
||||
return (
|
||||
<FetchUsernameLozenge
|
||||
accountID={account}
|
||||
linkTo={linkTo}
|
||||
backLocation={backLocation}
|
||||
classNames={classNames}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ReadyUsernameLozenge
|
||||
account={account}
|
||||
linkTo={linkTo}
|
||||
backLocation={backLocation}
|
||||
classNames={classNames}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface FetchUsernameLozengeProps {
|
||||
accountID: string;
|
||||
linkTo?: string;
|
||||
backLocation?: string;
|
||||
classNames?: string[];
|
||||
}
|
||||
|
||||
function FetchUsernameLozenge({ accountID, linkTo, backLocation, classNames }: FetchUsernameLozengeProps) {
|
||||
const [ trigger, result ] = useLazyGetAccountQuery();
|
||||
|
||||
// Call to get the account
|
||||
// using the provided ID.
|
||||
useEffect(() => {
|
||||
trigger(accountID, true);
|
||||
}, [trigger, accountID]);
|
||||
|
||||
const {
|
||||
data: account,
|
||||
isLoading,
|
||||
isFetching,
|
||||
isError,
|
||||
error,
|
||||
} = result;
|
||||
|
||||
// Wait for the account
|
||||
// model to be returned.
|
||||
if (isError) {
|
||||
return <ErrorC error={error} />;
|
||||
} else if (isLoading || isFetching || account === undefined) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ReadyUsernameLozenge
|
||||
account={account}
|
||||
linkTo={linkTo}
|
||||
backLocation={backLocation}
|
||||
classNames={classNames}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface ReadyUsernameLozengeProps {
|
||||
account: AdminAccount;
|
||||
linkTo?: string;
|
||||
backLocation?: string;
|
||||
classNames?: string[];
|
||||
}
|
||||
|
||||
export default function Username({ account, linkTo, backLocation, classNames }: UsernameProps) {
|
||||
function ReadyUsernameLozenge({ account, linkTo, backLocation, classNames }: ReadyUsernameLozengeProps) {
|
||||
const [ _location, setLocation ] = useLocation();
|
||||
|
||||
let className = "username-lozenge";
|
|
@ -110,12 +110,19 @@ export function MenuItem(props: PropsWithChildren<MenuItemProps>) {
|
|||
if (topLevel) {
|
||||
classNames.push("category", "top-level");
|
||||
} else {
|
||||
if (thisLevel === 1 && hasChildren) {
|
||||
classNames.push("category", "expanding");
|
||||
} else if (thisLevel === 1 && !hasChildren) {
|
||||
classNames.push("view", "expanding");
|
||||
} else if (thisLevel === 2) {
|
||||
classNames.push("view", "nested");
|
||||
switch (true) {
|
||||
case thisLevel === 1 && hasChildren:
|
||||
classNames.push("category", "expanding");
|
||||
break;
|
||||
case thisLevel === 1 && !hasChildren:
|
||||
classNames.push("view", "expanding");
|
||||
break;
|
||||
case thisLevel >= 2 && hasChildren:
|
||||
classNames.push("nested", "category");
|
||||
break;
|
||||
case thisLevel >= 2 && !hasChildren:
|
||||
classNames.push("nested", "view");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
173
web/source/settings/lib/query/admin/domain-permissions/drafts.ts
Normal file
173
web/source/settings/lib/query/admin/domain-permissions/drafts.ts
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
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 {
|
||||
DomainPerm,
|
||||
DomainPermDraftCreateParams,
|
||||
DomainPermDraftSearchParams,
|
||||
DomainPermDraftSearchResp,
|
||||
} from "../../../types/domain-permission";
|
||||
import parse from "parse-link-header";
|
||||
import { PermType } from "../../../types/perm";
|
||||
|
||||
const extended = gtsApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
searchDomainPermissionDrafts: build.query<DomainPermDraftSearchResp, DomainPermDraftSearchParams>({
|
||||
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_drafts${query}`
|
||||
};
|
||||
},
|
||||
// Headers required for paging.
|
||||
transformResponse: (apiResp: DomainPerm[], meta) => {
|
||||
const drafts = apiResp;
|
||||
const linksStr = meta?.response?.headers.get("Link");
|
||||
const links = parse(linksStr);
|
||||
return { drafts, links };
|
||||
},
|
||||
// Only provide TRANSFORMED tag id since this model is not the same
|
||||
// as getDomainPermissionDraft model (due to transformResponse).
|
||||
providesTags: [{ type: "DomainPermissionDraft", id: "TRANSFORMED" }]
|
||||
}),
|
||||
|
||||
getDomainPermissionDraft: build.query<DomainPerm, string>({
|
||||
query: (id) => ({
|
||||
url: `/api/v1/admin/domain_permission_drafts/${id}`
|
||||
}),
|
||||
providesTags: (_result, _error, id) => [
|
||||
{ type: 'DomainPermissionDraft', id }
|
||||
],
|
||||
}),
|
||||
|
||||
createDomainPermissionDraft: build.mutation<DomainPerm, DomainPermDraftCreateParams>({
|
||||
query: (formData) => ({
|
||||
method: "POST",
|
||||
url: `/api/v1/admin/domain_permission_drafts`,
|
||||
asForm: true,
|
||||
body: formData,
|
||||
discardEmpty: true
|
||||
}),
|
||||
invalidatesTags: [{ type: "DomainPermissionDraft", id: "TRANSFORMED" }],
|
||||
}),
|
||||
|
||||
acceptDomainPermissionDraft: build.mutation<DomainPerm, { id: string, overwrite?: boolean, permType: PermType }>({
|
||||
query: ({ id, overwrite }) => ({
|
||||
method: "POST",
|
||||
url: `/api/v1/admin/domain_permission_drafts/${id}/accept`,
|
||||
asForm: true,
|
||||
body: {
|
||||
overwrite: overwrite,
|
||||
},
|
||||
discardEmpty: true
|
||||
}),
|
||||
invalidatesTags: (res, _error, { id, permType }) => {
|
||||
const invalidated: any[] = [];
|
||||
|
||||
// If error, nothing to invalidate.
|
||||
if (!res) {
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
// Invalidate this draft by ID, and
|
||||
// the transformed list of all drafts.
|
||||
invalidated.push(
|
||||
{ type: 'DomainPermissionDraft', id: id },
|
||||
{ type: "DomainPermissionDraft", id: "TRANSFORMED" },
|
||||
);
|
||||
|
||||
// Invalidate cached blocks/allows depending
|
||||
// on the permType of the accepted draft.
|
||||
if (permType === "allow") {
|
||||
invalidated.push("domainAllows");
|
||||
} else {
|
||||
invalidated.push("domainBlocks");
|
||||
}
|
||||
|
||||
return invalidated;
|
||||
}
|
||||
}),
|
||||
|
||||
removeDomainPermissionDraft: build.mutation<DomainPerm, { id: string, ignore_target?: boolean }>({
|
||||
query: ({ id, ignore_target }) => ({
|
||||
method: "POST",
|
||||
url: `/api/v1/admin/domain_permission_drafts/${id}/remove`,
|
||||
asForm: true,
|
||||
body: {
|
||||
ignore_target: ignore_target,
|
||||
},
|
||||
discardEmpty: true
|
||||
}),
|
||||
invalidatesTags: (res, _error, { id }) =>
|
||||
res
|
||||
? [
|
||||
{ type: "DomainPermissionDraft", id },
|
||||
{ type: "DomainPermissionDraft", id: "TRANSFORMED" },
|
||||
]
|
||||
: [],
|
||||
})
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* View domain permission drafts.
|
||||
*/
|
||||
const useLazySearchDomainPermissionDraftsQuery = extended.useLazySearchDomainPermissionDraftsQuery;
|
||||
|
||||
/**
|
||||
* Get domain permission draft with the given ID.
|
||||
*/
|
||||
const useGetDomainPermissionDraftQuery = extended.useGetDomainPermissionDraftQuery;
|
||||
|
||||
/**
|
||||
* Create a domain permission draft with the given parameters.
|
||||
*/
|
||||
const useCreateDomainPermissionDraftMutation = extended.useCreateDomainPermissionDraftMutation;
|
||||
|
||||
/**
|
||||
* Accept a domain permission draft, turning it into an enforced domain permission.
|
||||
*/
|
||||
const useAcceptDomainPermissionDraftMutation = extended.useAcceptDomainPermissionDraftMutation;
|
||||
|
||||
/**
|
||||
* Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
|
||||
*/
|
||||
const useRemoveDomainPermissionDraftMutation = extended.useRemoveDomainPermissionDraftMutation;
|
||||
|
||||
export {
|
||||
useLazySearchDomainPermissionDraftsQuery,
|
||||
useGetDomainPermissionDraftQuery,
|
||||
useCreateDomainPermissionDraftMutation,
|
||||
useAcceptDomainPermissionDraftMutation,
|
||||
useRemoveDomainPermissionDraftMutation,
|
||||
};
|
|
@ -37,6 +37,12 @@ const extended = gtsApi.injectEndpoints({
|
|||
}),
|
||||
transformResponse: listToKeyedObject<DomainPerm>("domain"),
|
||||
}),
|
||||
|
||||
domainPermissionDrafts: build.query<any, void>({
|
||||
query: () => ({
|
||||
url: `/api/v1/admin/domain_permission_drafts`
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
type DomainPerm,
|
||||
type ImportDomainPermsParams,
|
||||
type MappedDomainPerms,
|
||||
isDomainPermInternalKey,
|
||||
stripOnImport,
|
||||
} from "../../../types/domain-permission";
|
||||
import { listToKeyedObject } from "../../transforms";
|
||||
|
||||
|
@ -83,7 +83,7 @@ function importEntriesProcessor(formData: ImportDomainPermsParams): (_entry: Dom
|
|||
// Unset all internal processing keys
|
||||
// and any undefined keys on this entry.
|
||||
Object.entries(entry).forEach(([key, val]: [keyof DomainPerm, any]) => {
|
||||
if (val == undefined || isDomainPermInternalKey(key)) {
|
||||
if (val == undefined || stripOnImport(key)) {
|
||||
delete entry[key];
|
||||
}
|
||||
});
|
||||
|
|
|
@ -169,6 +169,7 @@ export const gtsApi = createApi({
|
|||
"HTTPHeaderBlocks",
|
||||
"DefaultInteractionPolicies",
|
||||
"InteractionRequest",
|
||||
"DomainPermissionDraft",
|
||||
],
|
||||
endpoints: (build) => ({
|
||||
instanceV1: build.query<InstanceV1, void>({
|
||||
|
|
|
@ -19,11 +19,12 @@
|
|||
|
||||
import typia from "typia";
|
||||
import { PermType } from "./perm";
|
||||
import { Links } from "parse-link-header";
|
||||
|
||||
export const validateDomainPerms = typia.createValidate<DomainPerm[]>();
|
||||
|
||||
/**
|
||||
* A single domain permission entry (block or allow).
|
||||
* A single domain permission entry (block, allow, draft, ignore).
|
||||
*/
|
||||
export interface DomainPerm {
|
||||
id?: string;
|
||||
|
@ -32,11 +33,14 @@ export interface DomainPerm {
|
|||
private_comment?: string;
|
||||
public_comment?: string;
|
||||
created_at?: string;
|
||||
created_by?: string;
|
||||
subscription_id?: string;
|
||||
|
||||
// Internal processing keys; remove
|
||||
// before serdes of domain perm.
|
||||
// Keys that should be stripped before
|
||||
// sending the domain permission (if imported).
|
||||
|
||||
permission_type?: PermType;
|
||||
key?: string;
|
||||
permType?: PermType;
|
||||
suggest?: string;
|
||||
valid?: boolean;
|
||||
checked?: boolean;
|
||||
|
@ -53,9 +57,9 @@ export interface MappedDomainPerms {
|
|||
[key: string]: DomainPerm;
|
||||
}
|
||||
|
||||
const domainPermInternalKeys: Set<keyof DomainPerm> = new Set([
|
||||
const domainPermStripOnImport: Set<keyof DomainPerm> = new Set([
|
||||
"key",
|
||||
"permType",
|
||||
"permission_type",
|
||||
"suggest",
|
||||
"valid",
|
||||
"checked",
|
||||
|
@ -65,15 +69,14 @@ const domainPermInternalKeys: Set<keyof DomainPerm> = new Set([
|
|||
]);
|
||||
|
||||
/**
|
||||
* Returns true if provided DomainPerm Object key is
|
||||
* "internal"; ie., it's just for our use, and it shouldn't
|
||||
* be serialized to or deserialized from the GtS API.
|
||||
* Returns true if provided DomainPerm Object key is one
|
||||
* that should be stripped when importing a domain permission.
|
||||
*
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
export function isDomainPermInternalKey(key: keyof DomainPerm) {
|
||||
return domainPermInternalKeys.has(key);
|
||||
export function stripOnImport(key: keyof DomainPerm) {
|
||||
return domainPermStripOnImport.has(key);
|
||||
}
|
||||
|
||||
export interface ImportDomainPermsParams {
|
||||
|
@ -94,3 +97,102 @@ export interface ExportDomainPermsParams {
|
|||
action: "export" | "export-file";
|
||||
exportType: "json" | "csv" | "plain";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for GET to /api/v1/admin/domain_permission_drafts.
|
||||
*/
|
||||
export interface DomainPermDraftSearchParams {
|
||||
/**
|
||||
* Show only drafts created by the given subscription ID.
|
||||
*/
|
||||
subscription_id?: string;
|
||||
/**
|
||||
* Return only drafts that target the given domain.
|
||||
*/
|
||||
domain?: string;
|
||||
/**
|
||||
* Filter on "block" or "allow" type drafts.
|
||||
*/
|
||||
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_drafts/{id}/accept.
|
||||
*/
|
||||
export interface DomainPermDraftAcceptParams {
|
||||
/**
|
||||
* ID of the domain permission draft.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* If a domain permission already exists with the same domain and permission
|
||||
* type as the draft, overwrite the existing permission with fields from the draft.
|
||||
*/
|
||||
overwrite?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for POST to /api/v1/admin/domain_permission_drafts/{id}/accept.
|
||||
*/
|
||||
export interface DomainPermDraftRejectParams {
|
||||
/**
|
||||
* ID of the domain permission draft.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* When removing the domain permission draft, also create a domain ignore entry for
|
||||
* the target domain, so that drafts will not be created for this domain in the future.
|
||||
*/
|
||||
ignore_target?: boolean;
|
||||
}
|
||||
|
||||
export interface DomainPermDraftSearchResp {
|
||||
drafts: DomainPerm[];
|
||||
links: Links | null;
|
||||
}
|
||||
|
||||
export interface DomainPermDraftCreateParams {
|
||||
/**
|
||||
* Domain to create the permission draft for.
|
||||
*/
|
||||
domain: string;
|
||||
/**
|
||||
* Create a draft "allow" or a draft "block".
|
||||
*/
|
||||
permission_type: PermType;
|
||||
/**
|
||||
* Obfuscate the name of the domain when serving it publicly.
|
||||
* Eg., `example.org` becomes something like `ex***e.org`.
|
||||
*/
|
||||
obfuscate?: boolean;
|
||||
/**
|
||||
* Public comment about this domain permission. This will be displayed
|
||||
* alongside the domain permission if you choose to share permissions.
|
||||
*/
|
||||
public_comment?: string;
|
||||
/**
|
||||
* Private comment about this domain permission.
|
||||
* Will only be shown to other admins, so this is a useful way of
|
||||
* internally keeping track of why a certain domain ended up permissioned.
|
||||
*/
|
||||
private_comment?: string;
|
||||
}
|
||||
|
|
48
web/source/settings/lib/util/formvalidators.ts
Normal file
48
web/source/settings/lib/util/formvalidators.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
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 isValidDomain from "is-valid-domain";
|
||||
|
||||
/**
|
||||
* Validate the "domain" field of a form.
|
||||
* @param domain
|
||||
* @returns
|
||||
*/
|
||||
export function formDomainValidator(domain: string): string {
|
||||
if (domain.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (domain[domain.length-1] === ".") {
|
||||
return "invalid domain";
|
||||
}
|
||||
|
||||
const valid = isValidDomain(domain, {
|
||||
subdomain: true,
|
||||
wildcard: false,
|
||||
allowUnicode: true,
|
||||
topLevel: false,
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "invalid domain";
|
||||
}
|
|
@ -41,3 +41,16 @@ export function UseOurInstanceAccount(account: AdminAccount): boolean {
|
|||
|
||||
return !account.domain && account.username == ourDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uppercase first letter of given string.
|
||||
*/
|
||||
export function useCapitalize(i?: string): string {
|
||||
return useMemo(() => {
|
||||
if (i === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return i.charAt(0).toUpperCase() + i.slice(1);
|
||||
}, [i]);
|
||||
}
|
||||
|
|
|
@ -194,7 +194,8 @@ nav.menu-tree {
|
|||
}
|
||||
}
|
||||
|
||||
li.nested { /* any deeper nesting, just has indent */
|
||||
/* Deeper nesting. */
|
||||
li.nested {
|
||||
a.title {
|
||||
padding-left: 1rem;
|
||||
font-weight: normal;
|
||||
|
@ -210,11 +211,35 @@ nav.menu-tree {
|
|||
background: $settings-nav-bg-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.active > a.title {
|
||||
color: $fg-accent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.active {
|
||||
a.title {
|
||||
color: $fg-accent;
|
||||
font-weight: bold;
|
||||
&.category {
|
||||
& > a.title {
|
||||
&::after {
|
||||
content: "▶";
|
||||
left: 0.8rem;
|
||||
bottom: 0.1rem;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
& > a.title {
|
||||
&::after {
|
||||
content: "▼";
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
border-bottom: 0.1rem dotted $gray1;
|
||||
}
|
||||
}
|
||||
|
||||
li.nested > a.title {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1334,6 +1359,63 @@ button.tab-button {
|
|||
}
|
||||
}
|
||||
|
||||
.domain-permission-drafts-view {
|
||||
.domain-permission-draft {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
&.block {
|
||||
border-left: 0.3rem solid $error3;
|
||||
}
|
||||
|
||||
&.allow {
|
||||
border-left: 0.3rem solid $green1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $fg-accent;
|
||||
}
|
||||
|
||||
.info-list {
|
||||
border: none;
|
||||
|
||||
.info-list-entry {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
> .mutation-button
|
||||
> button {
|
||||
font-size: 1rem;
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.domain-permission-draft-details {
|
||||
.info-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.domain-permission-drafts-view,
|
||||
.domain-permission-draft-details {
|
||||
dd.permission-type {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.instance-rules {
|
||||
list-style-position: inside;
|
||||
margin: 0;
|
||||
|
|
|
@ -22,32 +22,11 @@ import { TextInput } from "../../../../components/form/inputs";
|
|||
import MutationButton from "../../../../components/form/mutation-button";
|
||||
import { useTextInput } from "../../../../lib/form";
|
||||
import { useInstanceKeysExpireMutation } from "../../../../lib/query/admin/actions";
|
||||
import isValidDomain from "is-valid-domain";
|
||||
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||
|
||||
export default function ExpireRemote({}) {
|
||||
const domainField = useTextInput("domain", {
|
||||
validator: (v: string) => {
|
||||
if (v.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (v[v.length-1] === ".") {
|
||||
return "invalid domain";
|
||||
}
|
||||
|
||||
const valid = isValidDomain(v, {
|
||||
subdomain: true,
|
||||
wildcard: false,
|
||||
allowUnicode: true,
|
||||
topLevel: false,
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "invalid domain";
|
||||
}
|
||||
validator: formDomainValidator,
|
||||
});
|
||||
|
||||
const [expire, expireResult] = useInstanceKeysExpireMutation();
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useLocation, useParams } from "wouter";
|
||||
import { PermType } from "../../../lib/types/perm";
|
||||
import { useDeleteHeaderAllowMutation, useDeleteHeaderBlockMutation, useGetHeaderAllowQuery, useGetHeaderBlockQuery } from "../../../lib/query/admin/http-header-permissions";
|
||||
|
@ -26,8 +26,7 @@ import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
|
|||
import { SerializedError } from "@reduxjs/toolkit";
|
||||
import Loading from "../../../components/loading";
|
||||
import { Error } from "../../../components/error";
|
||||
import { useLazyGetAccountQuery } from "../../../lib/query/admin";
|
||||
import Username from "../../../components/username";
|
||||
import UsernameLozenge from "../../../components/username-lozenge";
|
||||
import { useBaseUrl } from "../../../lib/navigation/util";
|
||||
import BackButton from "../../../components/back-button";
|
||||
import MutationButton from "../../../components/form/mutation-button";
|
||||
|
@ -92,58 +91,19 @@ interface PermDeetsProps {
|
|||
function PermDeets({
|
||||
permType,
|
||||
data: perm,
|
||||
isLoading: isLoadingPerm,
|
||||
isFetching: isFetchingPerm,
|
||||
isError: isErrorPerm,
|
||||
error: errorPerm,
|
||||
isLoading,
|
||||
isFetching,
|
||||
isError,
|
||||
error,
|
||||
}: PermDeetsProps) {
|
||||
const [ location ] = useLocation();
|
||||
const baseUrl = useBaseUrl();
|
||||
|
||||
// Once we've loaded the perm, trigger
|
||||
// getting the account that created it.
|
||||
const [ getAccount, getAccountRes ] = useLazyGetAccountQuery();
|
||||
useEffect(() => {
|
||||
if (!perm) {
|
||||
return;
|
||||
}
|
||||
getAccount(perm.created_by, true);
|
||||
}, [getAccount, perm]);
|
||||
|
||||
// Load the createdByAccount if possible,
|
||||
// returning a username lozenge with
|
||||
// a link to the account.
|
||||
const createdByAccount = useMemo(() => {
|
||||
const {
|
||||
data: account,
|
||||
isLoading: isLoadingAccount,
|
||||
isFetching: isFetchingAccount,
|
||||
isError: isErrorAccount,
|
||||
} = getAccountRes;
|
||||
|
||||
// Wait for query to finish, returning
|
||||
// loading spinner in the meantime.
|
||||
if (isLoadingAccount || isFetchingAccount || !perm) {
|
||||
return <Loading />;
|
||||
} else if (isErrorAccount || account === undefined) {
|
||||
// Fall back to account ID.
|
||||
return perm?.created_by;
|
||||
}
|
||||
|
||||
return (
|
||||
<Username
|
||||
account={account}
|
||||
linkTo={`~/settings/moderation/accounts/${account.id}`}
|
||||
backLocation={`~${baseUrl}${location}`}
|
||||
/>
|
||||
);
|
||||
}, [getAccountRes, perm, baseUrl, location]);
|
||||
|
||||
// Now wait til the perm itself is loaded.
|
||||
if (isLoadingPerm || isFetchingPerm) {
|
||||
// Wait til the perm itself is loaded.
|
||||
if (isLoading || isFetching) {
|
||||
return <Loading />;
|
||||
} else if (isErrorPerm) {
|
||||
return <Error error={errorPerm} />;
|
||||
} else if (isError) {
|
||||
return <Error error={error} />;
|
||||
} else if (perm === undefined) {
|
||||
throw "perm undefined";
|
||||
}
|
||||
|
@ -172,7 +132,13 @@ function PermDeets({
|
|||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Created By</dt>
|
||||
<dd>{createdByAccount}</dd>
|
||||
<dd>
|
||||
<UsernameLozenge
|
||||
account={perm.created_by}
|
||||
linkTo={`~/settings/moderation/accounts/${perm.created_by}`}
|
||||
backLocation={`~${baseUrl}${location}`}
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Header Name</dt>
|
||||
|
|
|
@ -27,6 +27,7 @@ import { PermType } from "../../../lib/types/perm";
|
|||
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
|
||||
import { SerializedError } from "@reduxjs/toolkit";
|
||||
import HeaderPermCreateForm from "./create";
|
||||
import { useCapitalize } from "../../../lib/util";
|
||||
|
||||
export default function HeaderPermsOverview() {
|
||||
const [ location, setLocation ] = useLocation();
|
||||
|
@ -41,9 +42,7 @@ export default function HeaderPermsOverview() {
|
|||
}, [params]);
|
||||
|
||||
// Uppercase first letter of given permType.
|
||||
const permTypeUpper = useMemo(() => {
|
||||
return permType.charAt(0).toUpperCase() + permType.slice(1);
|
||||
}, [permType]);
|
||||
const permTypeUpper = useCapitalize(permType);
|
||||
|
||||
// Fetch desired perms, skipping
|
||||
// the ones we don't want.
|
||||
|
|
|
@ -21,7 +21,7 @@ import React, { ReactNode } from "react";
|
|||
import { useSearchAccountsQuery } from "../../../../lib/query/admin";
|
||||
import { PageableList } from "../../../../components/pageable-list";
|
||||
import { useLocation } from "wouter";
|
||||
import Username from "../../../../components/username";
|
||||
import UsernameLozenge from "../../../../components/username-lozenge";
|
||||
import { AdminAccount } from "../../../../lib/types/account";
|
||||
|
||||
export default function AccountsPending() {
|
||||
|
@ -32,7 +32,7 @@ export default function AccountsPending() {
|
|||
function itemToEntry(account: AdminAccount): ReactNode {
|
||||
const acc = account.account;
|
||||
return (
|
||||
<Username
|
||||
<UsernameLozenge
|
||||
key={acc.acct}
|
||||
account={account}
|
||||
linkTo={`/${account.id}`}
|
||||
|
|
|
@ -26,8 +26,8 @@ import { Select, TextInput } from "../../../../components/form/inputs";
|
|||
import MutationButton from "../../../../components/form/mutation-button";
|
||||
import { useLocation, useSearch } from "wouter";
|
||||
import { AdminAccount } from "../../../../lib/types/account";
|
||||
import Username from "../../../../components/username";
|
||||
import isValidDomain from "is-valid-domain";
|
||||
import UsernameLozenge from "../../../../components/username-lozenge";
|
||||
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||
|
||||
export function AccountSearchForm() {
|
||||
const [ location, setLocation ] = useLocation();
|
||||
|
@ -45,28 +45,7 @@ export function AccountSearchForm() {
|
|||
display_name: useTextInput("display_name", { defaultValue: urlQueryParams.get("display_name") ?? ""}),
|
||||
by_domain: useTextInput("by_domain", {
|
||||
defaultValue: urlQueryParams.get("by_domain") ?? "",
|
||||
validator: (v: string) => {
|
||||
if (v.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (v[v.length-1] === ".") {
|
||||
return "invalid domain";
|
||||
}
|
||||
|
||||
const valid = isValidDomain(v, {
|
||||
subdomain: true,
|
||||
wildcard: false,
|
||||
allowUnicode: true,
|
||||
topLevel: false,
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "invalid domain";
|
||||
}
|
||||
validator: formDomainValidator,
|
||||
}),
|
||||
email: useTextInput("email", { defaultValue: urlQueryParams.get("email") ?? ""}),
|
||||
ip: useTextInput("ip", { defaultValue: urlQueryParams.get("ip") ?? ""}),
|
||||
|
@ -114,7 +93,7 @@ export function AccountSearchForm() {
|
|||
function itemToEntry(account: AdminAccount): ReactNode {
|
||||
const acc = account.account;
|
||||
return (
|
||||
<Username
|
||||
<UsernameLozenge
|
||||
key={acc.acct}
|
||||
account={account}
|
||||
linkTo={`/${account.id}`}
|
||||
|
|
|
@ -39,37 +39,47 @@ import { NoArg } from "../../../lib/types/query";
|
|||
import { Error } from "../../../components/error";
|
||||
import { useBaseUrl } from "../../../lib/navigation/util";
|
||||
import { PermType } from "../../../lib/types/perm";
|
||||
import isValidDomain from "is-valid-domain";
|
||||
import { useCapitalize } from "../../../lib/util";
|
||||
import { formDomainValidator } from "../../../lib/util/formvalidators";
|
||||
|
||||
export default function DomainPermDetail() {
|
||||
const baseUrl = useBaseUrl();
|
||||
|
||||
// Parse perm type from routing params.
|
||||
let params = useParams();
|
||||
if (params.permType !== "blocks" && params.permType !== "allows") {
|
||||
const search = useSearch();
|
||||
|
||||
// Parse perm type from routing params, converting
|
||||
// "blocks" => "block" and "allows" => "allow".
|
||||
const params = useParams();
|
||||
const permTypeRaw = params.permType;
|
||||
if (permTypeRaw !== "blocks" && permTypeRaw !== "allows") {
|
||||
throw "unrecognized perm type " + params.permType;
|
||||
}
|
||||
const permType = params.permType.slice(0, -1) as PermType;
|
||||
const permType = useMemo(() => {
|
||||
return permTypeRaw.slice(0, -1) as PermType;
|
||||
}, [permTypeRaw]);
|
||||
|
||||
const { data: domainBlocks = {}, isLoading: isLoadingDomainBlocks } = useDomainBlocksQuery(NoArg, { skip: permType !== "block" });
|
||||
const { data: domainAllows = {}, isLoading: isLoadingDomainAllows } = useDomainAllowsQuery(NoArg, { skip: permType !== "allow" });
|
||||
// Conditionally fetch either domain blocks or domain
|
||||
// allows depending on which perm type we're looking at.
|
||||
const {
|
||||
data: blocks = {},
|
||||
isLoading: loadingBlocks,
|
||||
isFetching: fetchingBlocks,
|
||||
} = useDomainBlocksQuery(NoArg, { skip: permType !== "block" });
|
||||
const {
|
||||
data: allows = {},
|
||||
isLoading: loadingAllows,
|
||||
isFetching: fetchingAllows,
|
||||
} = useDomainAllowsQuery(NoArg, { skip: permType !== "allow" });
|
||||
|
||||
let isLoading;
|
||||
switch (permType) {
|
||||
case "block":
|
||||
isLoading = isLoadingDomainBlocks;
|
||||
break;
|
||||
case "allow":
|
||||
isLoading = isLoadingDomainAllows;
|
||||
break;
|
||||
default:
|
||||
throw "perm type unknown";
|
||||
// Wait until we're done loading.
|
||||
const loading = permType === "block"
|
||||
? loadingBlocks || fetchingBlocks
|
||||
: loadingAllows || fetchingAllows;
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
// Parse domain from routing params.
|
||||
let domain = params.domain ?? "unknown";
|
||||
|
||||
const search = useSearch();
|
||||
if (domain === "view") {
|
||||
// Retrieve domain from form field submission.
|
||||
const searchParams = new URLSearchParams(search);
|
||||
|
@ -81,36 +91,41 @@ export default function DomainPermDetail() {
|
|||
domain = searchDomain;
|
||||
}
|
||||
|
||||
// Normalize / decode domain (it may be URL-encoded).
|
||||
// Normalize / decode domain
|
||||
// (it may be URL-encoded).
|
||||
domain = decodeURIComponent(domain);
|
||||
|
||||
// Check if we already have a perm of the desired type for this domain.
|
||||
const existingPerm: DomainPerm | undefined = useMemo(() => {
|
||||
if (permType == "block") {
|
||||
return domainBlocks[domain];
|
||||
} else {
|
||||
return domainAllows[domain];
|
||||
}
|
||||
}, [domainBlocks, domainAllows, domain, permType]);
|
||||
|
||||
// Check if we already have a perm
|
||||
// of the desired type for this domain.
|
||||
const existingPerm = permType === "block"
|
||||
? blocks[domain]
|
||||
: allows[domain];
|
||||
|
||||
// Render different into content depending on
|
||||
// if we have a perm already for this domain.
|
||||
let infoContent: React.JSX.Element;
|
||||
|
||||
if (isLoading) {
|
||||
infoContent = <Loading />;
|
||||
} else if (existingPerm == undefined) {
|
||||
infoContent = <span>No stored {permType} yet, you can add one below:</span>;
|
||||
if (existingPerm === undefined) {
|
||||
infoContent = (
|
||||
<span>
|
||||
No stored {permType} yet, you can add one below:
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
infoContent = (
|
||||
<div className="info">
|
||||
<i className="fa fa-fw fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
<b>Editing domain permissions isn't implemented yet, <a href="https://github.com/superseriousbusiness/gotosocial/issues/1198" target="_blank" rel="noopener noreferrer">check here for progress</a></b>
|
||||
<b>Editing existing domain {permTypeRaw} isn't implemented yet, <a href="https://github.com/superseriousbusiness/gotosocial/issues/1198" target="_blank" rel="noopener noreferrer">check here for progress</a></b>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-cutoff"><BackButton to={`~${baseUrl}/${permType}s`}/> Domain {permType} for: <span title={domain}>{domain}</span></h1>
|
||||
<h1 className="text-cutoff">
|
||||
<BackButton to={`~${baseUrl}/${permTypeRaw}`} />
|
||||
{" "}
|
||||
Domain {permType} for {domain}
|
||||
</h1>
|
||||
{infoContent}
|
||||
<DomainPermForm
|
||||
defaultDomain={domain}
|
||||
|
@ -143,28 +158,7 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
|
|||
domain: useTextInput("domain", {
|
||||
source: perm,
|
||||
defaultValue: defaultDomain,
|
||||
validator: (v: string) => {
|
||||
if (v.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (v[v.length-1] === ".") {
|
||||
return "invalid domain";
|
||||
}
|
||||
|
||||
const valid = isValidDomain(v, {
|
||||
subdomain: true,
|
||||
wildcard: false,
|
||||
allowUnicode: true,
|
||||
topLevel: false,
|
||||
});
|
||||
|
||||
if (valid) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "invalid domain";
|
||||
}
|
||||
validator: formDomainValidator,
|
||||
}),
|
||||
obfuscate: useBoolInput("obfuscate", { source: perm }),
|
||||
commentPrivate: useTextInput("private_comment", { source: perm }),
|
||||
|
@ -209,9 +203,7 @@ function DomainPermForm({ defaultDomain, perm, permType }: DomainPermFormProps)
|
|||
const [submitForm, submitFormResult] = useFormSubmit(form, [addTrigger, addResult], { changedOnly: false });
|
||||
|
||||
// Uppercase first letter of given permType.
|
||||
const permTypeUpper = useMemo(() => {
|
||||
return permType.charAt(0).toUpperCase() + permType.slice(1);
|
||||
}, [permType]);
|
||||
const permTypeUpper = useCapitalize(permType);
|
||||
|
||||
const [location, setLocation] = useLocation();
|
||||
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
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 { useLocation, useParams } from "wouter";
|
||||
import Loading from "../../../../components/loading";
|
||||
import { useBaseUrl } from "../../../../lib/navigation/util";
|
||||
import BackButton from "../../../../components/back-button";
|
||||
import {
|
||||
useAcceptDomainPermissionDraftMutation,
|
||||
useGetDomainPermissionDraftQuery,
|
||||
useRemoveDomainPermissionDraftMutation
|
||||
} from "../../../../lib/query/admin/domain-permissions/drafts";
|
||||
import { Error as ErrorC } from "../../../../components/error";
|
||||
import UsernameLozenge from "../../../../components/username-lozenge";
|
||||
import MutationButton from "../../../../components/form/mutation-button";
|
||||
import { useBoolInput, useTextInput } from "../../../../lib/form";
|
||||
import { Checkbox, Select } from "../../../../components/form/inputs";
|
||||
import { PermType } from "../../../../lib/types/perm";
|
||||
|
||||
export default function DomainPermissionDraftDetail() {
|
||||
const baseUrl = useBaseUrl();
|
||||
const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
|
||||
const params = useParams();
|
||||
|
||||
let id = params.permDraftId as string | undefined;
|
||||
if (!id) {
|
||||
throw "no perm ID";
|
||||
}
|
||||
|
||||
const {
|
||||
data: permDraft,
|
||||
isLoading,
|
||||
isFetching,
|
||||
isError,
|
||||
error,
|
||||
} = useGetDomainPermissionDraftQuery(id);
|
||||
|
||||
if (isLoading || isFetching) {
|
||||
return <Loading />;
|
||||
} else if (isError) {
|
||||
return <ErrorC error={error} />;
|
||||
} else if (permDraft === undefined) {
|
||||
return <ErrorC error={new Error("permission draft was undefined")} />;
|
||||
}
|
||||
|
||||
const created = permDraft.created_at ? new Date(permDraft.created_at).toDateString(): "unknown";
|
||||
const domain = permDraft.domain;
|
||||
const permType = permDraft.permission_type;
|
||||
if (!permType) {
|
||||
return <ErrorC error={new Error("permission_type was undefined")} />;
|
||||
}
|
||||
const publicComment = permDraft.public_comment ?? "[none]";
|
||||
const privateComment = permDraft.private_comment ?? "[none]";
|
||||
const subscriptionID = permDraft.subscription_id ?? "[none]";
|
||||
|
||||
return (
|
||||
<div className="domain-permission-draft-details">
|
||||
<h1><BackButton to={backLocation} /> Domain Permission Draft Detail</h1>
|
||||
<dl className="info-list">
|
||||
<div className="info-list-entry">
|
||||
<dt>Created</dt>
|
||||
<dd><time dateTime={permDraft.created_at}>{created}</time></dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Created By</dt>
|
||||
<dd>
|
||||
<UsernameLozenge
|
||||
account={permDraft.created_by}
|
||||
linkTo={`~/settings/moderation/accounts/${permDraft.created_by}`}
|
||||
backLocation={`~${location}`}
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Domain</dt>
|
||||
<dd>{domain}</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>
|
||||
<div className="info-list-entry">
|
||||
<dt>Private comment</dt>
|
||||
<dd>{privateComment}</dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Public comment</dt>
|
||||
<dd>{publicComment}</dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Subscription ID</dt>
|
||||
<dd>{subscriptionID}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<HandleDraft
|
||||
id={id}
|
||||
permType={permType}
|
||||
backLocation={backLocation}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HandleDraft({ id, permType, backLocation }: { id: string, permType: PermType, backLocation: string }) {
|
||||
const [ accept, acceptResult ] = useAcceptDomainPermissionDraftMutation();
|
||||
const [ remove, removeResult ] = useRemoveDomainPermissionDraftMutation();
|
||||
const [_location, setLocation] = useLocation();
|
||||
const form = {
|
||||
acceptOrRemove: useTextInput("accept_or_remove", { defaultValue: "accept" }),
|
||||
overwrite: useBoolInput("overwrite"),
|
||||
ignore_target: useBoolInput("ignore_target"),
|
||||
};
|
||||
|
||||
const onClick = (e) => {
|
||||
e.preventDefault();
|
||||
if (form.acceptOrRemove.value === "accept") {
|
||||
const overwrite = form.overwrite.value;
|
||||
accept({id, overwrite, permType}).then(res => {
|
||||
if ("data" in res) {
|
||||
setLocation(backLocation);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const ignore_target = form.ignore_target.value;
|
||||
remove({id, ignore_target}).then(res => {
|
||||
if ("data" in res) {
|
||||
setLocation(backLocation);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form>
|
||||
<Select
|
||||
field={form.acceptOrRemove}
|
||||
label="Accept or remove draft"
|
||||
options={
|
||||
<>
|
||||
<option value="accept">Accept</option>
|
||||
<option value="remove">Remove</option>
|
||||
</>
|
||||
}
|
||||
></Select>
|
||||
|
||||
{ form.acceptOrRemove.value === "accept" &&
|
||||
<>
|
||||
<Checkbox
|
||||
field={form.overwrite}
|
||||
label={`Overwrite any existing ${permType} for this domain`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
{ form.acceptOrRemove.value === "remove" &&
|
||||
<>
|
||||
<Checkbox
|
||||
field={form.ignore_target}
|
||||
label={`Add a domain permission ignore for this domain`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
<MutationButton
|
||||
label={
|
||||
form.acceptOrRemove.value === "accept"
|
||||
? `Accept ${permType}`
|
||||
: "Remove draft"
|
||||
}
|
||||
type="button"
|
||||
className={
|
||||
form.acceptOrRemove.value === "accept"
|
||||
? "button"
|
||||
: "button danger"
|
||||
}
|
||||
onClick={onClick}
|
||||
disabled={false}
|
||||
showError={true}
|
||||
result={
|
||||
form.acceptOrRemove.value === "accept"
|
||||
? acceptResult
|
||||
: removeResult
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
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 { useAcceptDomainPermissionDraftMutation, useLazySearchDomainPermissionDraftsQuery, useRemoveDomainPermissionDraftMutation } from "../../../../lib/query/admin/domain-permissions/drafts";
|
||||
import { DomainPerm } 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 { useCapitalize } from "../../../../lib/util";
|
||||
|
||||
export default function DomainPermissionDraftsSearch() {
|
||||
return (
|
||||
<div className="domain-permission-drafts-view">
|
||||
<div className="form-section-docs">
|
||||
<h1>Domain Permission Drafts</h1>
|
||||
<p>
|
||||
You can use the form below to search through domain permission drafts.
|
||||
<br/>
|
||||
Domain permission drafts are domain block or domain allow entries that are not yet in force.
|
||||
<br/>
|
||||
You can choose to accept or remove a draft.
|
||||
</p>
|
||||
</div>
|
||||
<DomainPermissionDraftsSearchForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DomainPermissionDraftsSearchForm() {
|
||||
const [ location, setLocation ] = useLocation();
|
||||
const search = useSearch();
|
||||
const urlQueryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const hasParams = urlQueryParams.size != 0;
|
||||
const [ searchDrafts, searchRes ] = useLazySearchDomainPermissionDraftsQuery();
|
||||
|
||||
const form = {
|
||||
subscription_id: useTextInput("subscription_id", { defaultValue: urlQueryParams.get("subscription_id") ?? "" }),
|
||||
domain: useTextInput("domain", {
|
||||
defaultValue: urlQueryParams.get("domain") ?? "",
|
||||
validator: formDomainValidator,
|
||||
}),
|
||||
permission_type: useTextInput("permission_type", { defaultValue: urlQueryParams.get("permission_type") ?? "" }),
|
||||
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) {
|
||||
searchDrafts(Object.fromEntries(urlQueryParams));
|
||||
} else {
|
||||
setLocation(location + "?limit=20");
|
||||
}
|
||||
}, [
|
||||
urlQueryParams,
|
||||
hasParams,
|
||||
searchDrafts,
|
||||
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(draft: DomainPerm): ReactNode {
|
||||
return (
|
||||
<DraftListEntry
|
||||
key={draft.id}
|
||||
permDraft={draft}
|
||||
linkTo={`/drafts/${draft.id}`}
|
||||
backLocation={backLocation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
onSubmit={submitQuery}
|
||||
// Prevent password managers
|
||||
// trying to fill in fields.
|
||||
autoComplete="off"
|
||||
>
|
||||
<Select
|
||||
field={form.permission_type}
|
||||
label="Permission type"
|
||||
options={
|
||||
<>
|
||||
<option value="">Any</option>
|
||||
<option value="block">Block</option>
|
||||
<option value="allow">Allow</option>
|
||||
</>
|
||||
}
|
||||
></Select>
|
||||
<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?.drafts}
|
||||
itemToEntry={itemToEntry}
|
||||
isError={searchRes.isError}
|
||||
error={searchRes.error}
|
||||
emptyMessage={<b>No drafts found that match your query.</b>}
|
||||
prevNextLinks={searchRes.data?.links}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface DraftEntryProps {
|
||||
permDraft: DomainPerm;
|
||||
linkTo: string;
|
||||
backLocation: string;
|
||||
}
|
||||
|
||||
function DraftListEntry({ permDraft, linkTo, backLocation }: DraftEntryProps) {
|
||||
const [ _location, setLocation ] = useLocation();
|
||||
const [ accept, acceptResult ] = useAcceptDomainPermissionDraftMutation();
|
||||
const [ remove, removeResult ] = useRemoveDomainPermissionDraftMutation();
|
||||
|
||||
const domain = permDraft.domain;
|
||||
const permType = permDraft.permission_type;
|
||||
const permTypeUpper = useCapitalize(permType);
|
||||
if (!permType) {
|
||||
return <ErrorC error={new Error("permission_type was undefined")} />;
|
||||
}
|
||||
|
||||
const publicComment = permDraft.public_comment ?? "[none]";
|
||||
const privateComment = permDraft.private_comment ?? "[none]";
|
||||
const subscriptionID = permDraft.subscription_id ?? "[none]";
|
||||
const id = permDraft.id;
|
||||
if (!id) {
|
||||
return <ErrorC error={new Error("id was undefined")} />;
|
||||
}
|
||||
|
||||
const title = `${permTypeUpper} ${domain}`;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`pseudolink domain-permission-draft entry ${permType}`}
|
||||
aria-label={title}
|
||||
title={title}
|
||||
onClick={() => {
|
||||
// When clicking on a draft, direct
|
||||
// to the detail view for that draft.
|
||||
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}
|
||||
>
|
||||
<h3>{title}</h3>
|
||||
<dl className="info-list">
|
||||
<div className="info-list-entry">
|
||||
<dt>Domain:</dt>
|
||||
<dd className="text-cutoff">{domain}</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>
|
||||
<div className="info-list-entry">
|
||||
<dt>Private comment:</dt>
|
||||
<dd className="text-cutoff">{privateComment}</dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Public comment:</dt>
|
||||
<dd>{publicComment}</dd>
|
||||
</div>
|
||||
<div className="info-list-entry">
|
||||
<dt>Subscription:</dt>
|
||||
<dd className="text-cutoff">{subscriptionID}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div className="action-buttons">
|
||||
<MutationButton
|
||||
label={`Accept ${permType}`}
|
||||
title={`Accept ${permType}`}
|
||||
type="button"
|
||||
className="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
accept({ id, permType });
|
||||
}}
|
||||
disabled={false}
|
||||
showError={true}
|
||||
result={acceptResult}
|
||||
/>
|
||||
<MutationButton
|
||||
label={`Remove draft`}
|
||||
title={`Remove draft`}
|
||||
type="button"
|
||||
className="button danger"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
remove({ id });
|
||||
}}
|
||||
disabled={false}
|
||||
showError={true}
|
||||
result={removeResult}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import useFormSubmit from "../../../../lib/form/submit";
|
||||
import { useCreateDomainPermissionDraftMutation } from "../../../../lib/query/admin/domain-permissions/drafts";
|
||||
import { useBoolInput, useRadioInput, useTextInput } from "../../../../lib/form";
|
||||
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||
import MutationButton from "../../../../components/form/mutation-button";
|
||||
import { Checkbox, RadioGroup, TextArea, TextInput } from "../../../../components/form/inputs";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
export default function DomainPermissionDraftNew() {
|
||||
const [ _location, setLocation ] = useLocation();
|
||||
|
||||
const form = {
|
||||
domain: useTextInput("domain", {
|
||||
validator: formDomainValidator,
|
||||
}),
|
||||
permission_type: useRadioInput("permission_type", {
|
||||
options: {
|
||||
block: "Block domain",
|
||||
allow: "Allow domain",
|
||||
}
|
||||
}),
|
||||
obfuscate: useBoolInput("obfuscate"),
|
||||
public_comment: useTextInput("public_comment"),
|
||||
private_comment: useTextInput("private_comment"),
|
||||
};
|
||||
|
||||
const [formSubmit, result] = useFormSubmit(
|
||||
form,
|
||||
useCreateDomainPermissionDraftMutation(),
|
||||
{
|
||||
changedOnly: false,
|
||||
onFinish: (res) => {
|
||||
if (res.data) {
|
||||
// Creation successful,
|
||||
// redirect to drafts overview.
|
||||
setLocation(`/drafts/search`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={formSubmit}
|
||||
// Prevent password managers
|
||||
// trying to fill in fields.
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="form-section-docs">
|
||||
<h2>New Domain Permission Draft</h2>
|
||||
<p>
|
||||
You can use the form below to create a new domain permission draft.
|
||||
<br/>
|
||||
Domain permission drafts are domain block or domain allow entries that are not yet in force.
|
||||
<br/>
|
||||
You can choose to accept or remove a draft.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<RadioGroup
|
||||
field={form.permission_type}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
field={form.domain}
|
||||
label={`Domain (without "https://" prefix)`}
|
||||
placeholder="example.org"
|
||||
autoCapitalize="none"
|
||||
spellCheck="false"
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
field={form.private_comment}
|
||||
label={"Private comment"}
|
||||
placeholder="This domain is like unto a clown car full of clowns, I suggest we block it forthwith."
|
||||
autoCapitalize="sentences"
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
field={form.public_comment}
|
||||
label={"Public comment"}
|
||||
placeholder="Bad posters"
|
||||
autoCapitalize="sentences"
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
field={form.obfuscate}
|
||||
label="Obfuscate domain in public lists"
|
||||
/>
|
||||
|
||||
<MutationButton
|
||||
label="Save"
|
||||
result={result}
|
||||
disabled={
|
||||
!form.domain.value ||
|
||||
!form.domain.valid ||
|
||||
!form.permission_type.value
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -30,6 +30,7 @@ import type { MappedDomainPerms } from "../../../lib/types/domain-permission";
|
|||
import { NoArg } from "../../../lib/types/query";
|
||||
import { PermType } from "../../../lib/types/perm";
|
||||
import { useBaseUrl } from "../../../lib/navigation/util";
|
||||
import { useCapitalize } from "../../../lib/util";
|
||||
|
||||
export default function DomainPermissionsOverview() {
|
||||
const baseUrl = useBaseUrl();
|
||||
|
@ -42,9 +43,7 @@ export default function DomainPermissionsOverview() {
|
|||
const permType = params.permType.slice(0, -1) as PermType;
|
||||
|
||||
// Uppercase first letter of given permType.
|
||||
const permTypeUpper = useMemo(() => {
|
||||
return permType.charAt(0).toUpperCase() + permType.slice(1);
|
||||
}, [permType]);
|
||||
const permTypeUpper = useCapitalize(permType);
|
||||
|
||||
// Fetch / wait for desired perms to load.
|
||||
const { data: blocks, isLoading: isLoadingBlocks } = useDomainBlocksQuery(NoArg, { skip: permType !== "block" });
|
||||
|
|
|
@ -116,6 +116,23 @@ function ModerationDomainPermsMenu() {
|
|||
itemUrl="import-export"
|
||||
icon="fa-floppy-o"
|
||||
/>
|
||||
<MenuItem
|
||||
name="Drafts"
|
||||
itemUrl="drafts"
|
||||
defaultChild="search"
|
||||
icon="fa-pencil"
|
||||
>
|
||||
<MenuItem
|
||||
name="Search"
|
||||
itemUrl="search"
|
||||
icon="fa-list"
|
||||
/>
|
||||
<MenuItem
|
||||
name="New draft"
|
||||
itemUrl="new"
|
||||
icon="fa-plus"
|
||||
/>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import { useValue, useTextInput } from "../../../lib/form";
|
|||
import useFormSubmit from "../../../lib/form/submit";
|
||||
import { TextArea } from "../../../components/form/inputs";
|
||||
import MutationButton from "../../../components/form/mutation-button";
|
||||
import Username from "../../../components/username";
|
||||
import UsernameLozenge from "../../../components/username-lozenge";
|
||||
import { useGetReportQuery, useResolveReportMutation } from "../../../lib/query/admin/reports";
|
||||
import { useBaseUrl } from "../../../lib/navigation/util";
|
||||
import { AdminReport } from "../../../lib/types/report";
|
||||
|
@ -99,7 +99,7 @@ function ReportBasicInfo({ report, baseUrl, location }: ReportSectionProps) {
|
|||
<div className="info-list-entry">
|
||||
<dt>Reported account</dt>
|
||||
<dd>
|
||||
<Username
|
||||
<UsernameLozenge
|
||||
account={target}
|
||||
linkTo={`~/settings/moderation/accounts/${target.id}`}
|
||||
backLocation={`~${baseUrl}${location}`}
|
||||
|
@ -110,7 +110,7 @@ function ReportBasicInfo({ report, baseUrl, location }: ReportSectionProps) {
|
|||
<div className="info-list-entry">
|
||||
<dt>Reported by</dt>
|
||||
<dd>
|
||||
<Username
|
||||
<UsernameLozenge
|
||||
account={from}
|
||||
linkTo={`~/settings/moderation/accounts/${from.id}`}
|
||||
backLocation={`~${baseUrl}${location}`}
|
||||
|
@ -173,7 +173,7 @@ function ReportHistory({ report, baseUrl, location }: ReportSectionProps) {
|
|||
<div className="info-list-entry">
|
||||
<dt>Handled by</dt>
|
||||
<dd>
|
||||
<Username
|
||||
<UsernameLozenge
|
||||
account={handled_by}
|
||||
linkTo={`~/settings/moderation/accounts/${handled_by.id}`}
|
||||
backLocation={`~${baseUrl}${location}`}
|
||||
|
|
|
@ -25,7 +25,7 @@ import { PageableList } from "../../../components/pageable-list";
|
|||
import { Select } from "../../../components/form/inputs";
|
||||
import MutationButton from "../../../components/form/mutation-button";
|
||||
import { useLocation, useSearch } from "wouter";
|
||||
import Username from "../../../components/username";
|
||||
import UsernameLozenge from "../../../components/username-lozenge";
|
||||
import { AdminReport } from "../../../lib/types/report";
|
||||
|
||||
export default function ReportsSearch() {
|
||||
|
@ -206,7 +206,7 @@ function ReportListEntry({ report, linkTo, backLocation }: ReportEntryProps) {
|
|||
<div className="info-list-entry">
|
||||
<dt>Reported account:</dt>
|
||||
<dd className="text-cutoff">
|
||||
<Username
|
||||
<UsernameLozenge
|
||||
account={target}
|
||||
classNames={["text-cutoff report-byline"]}
|
||||
/>
|
||||
|
@ -216,7 +216,7 @@ function ReportListEntry({ report, linkTo, backLocation }: ReportEntryProps) {
|
|||
<div className="info-list-entry">
|
||||
<dt>Reported by:</dt>
|
||||
<dd className="text-cutoff reported-by">
|
||||
<Username account={from} />
|
||||
<UsernameLozenge account={from} />
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ import DomainPermDetail from "./domain-permissions/detail";
|
|||
import AccountsSearch from "./accounts";
|
||||
import AccountsPending from "./accounts/pending";
|
||||
import AccountDetail from "./accounts/detail";
|
||||
import DomainPermissionDraftsSearch from "./domain-permissions/drafts";
|
||||
import DomainPermissionDraftNew from "./domain-permissions/drafts/new";
|
||||
import DomainPermissionDraftDetail from "./domain-permissions/drafts/detail";
|
||||
|
||||
/*
|
||||
EXPORTED COMPONENTS
|
||||
|
@ -139,6 +142,9 @@ function ModerationDomainPermsRouter() {
|
|||
<Switch>
|
||||
<Route path="/import-export" component={ImportExport} />
|
||||
<Route path="/process" component={ImportExport} />
|
||||
<Route path="/drafts/search" component={DomainPermissionDraftsSearch} />
|
||||
<Route path="/drafts/new" component={DomainPermissionDraftNew} />
|
||||
<Route path="/drafts/:permDraftId" component={DomainPermissionDraftDetail} />
|
||||
<Route path="/:permType" component={DomainPermissionsOverview} />
|
||||
<Route path="/:permType/:domain" component={DomainPermDetail} />
|
||||
<Route><Redirect to="/blocks"/></Route>
|
||||
|
|
Loading…
Reference in a new issue