mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-25 13:16:40 +00:00
Compare commits
6 commits
89d6acd35e
...
d33c738fef
Author | SHA1 | Date | |
---|---|---|---|
d33c738fef | |||
301543616b | |||
c2029df9bc | |||
daf55ba6a5 | |||
9ace025da1 | |||
ffa67ac1ae |
|
@ -17,7 +17,7 @@ Documentation is at [docs.gotosocial.org](https://docs.gotosocial.org). You can
|
||||||
|
|
||||||
To build from source, check the [CONTRIBUTING.md](https://github.com/superseriousbusiness/gotosocial/blob/main/CONTRIBUTING.md) file.
|
To build from source, check the [CONTRIBUTING.md](https://github.com/superseriousbusiness/gotosocial/blob/main/CONTRIBUTING.md) file.
|
||||||
|
|
||||||
Here's a screenshot of the instance landing page!
|
Here's a screenshot of the instance landing page! Check out the project's [official account](https://gts.superseriousbusiness.org/@gotosocial) running on GoToSocial.
|
||||||
|
|
||||||
![Screenshot of the landing page for the GoToSocial instance goblin.technology. It shows basic information about the instance; number of users and posts etc.](https://raw.githubusercontent.com/superseriousbusiness/gotosocial/main/docs/overrides/public/instancesplash.png)
|
![Screenshot of the landing page for the GoToSocial instance goblin.technology. It shows basic information about the instance; number of users and posts etc.](https://raw.githubusercontent.com/superseriousbusiness/gotosocial/main/docs/overrides/public/instancesplash.png)
|
||||||
<!--overview-end-->
|
<!--overview-end-->
|
||||||
|
|
|
@ -1138,12 +1138,12 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
x-go-name: AsDraft
|
x-go-name: AsDraft
|
||||||
content_type:
|
content_type:
|
||||||
description: MIME content type to expect at URI.
|
description: MIME content type to use when parsing the permissions list.
|
||||||
example: text/csv
|
example: text/csv
|
||||||
type: string
|
type: string
|
||||||
x-go-name: ContentType
|
x-go-name: ContentType
|
||||||
count:
|
count:
|
||||||
description: Count of domain permission entries discovered at URI.
|
description: Count of domain permission entries discovered at URI on last (successful) fetch.
|
||||||
example: 53
|
example: 53
|
||||||
format: uint64
|
format: uint64
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -1188,6 +1188,11 @@ definitions:
|
||||||
example: block
|
example: block
|
||||||
type: string
|
type: string
|
||||||
x-go-name: PermissionType
|
x-go-name: PermissionType
|
||||||
|
title:
|
||||||
|
description: Title of this list, as set by admin who created or updated it.f
|
||||||
|
example: really cool list of neato pals
|
||||||
|
type: string
|
||||||
|
x-go-name: Title
|
||||||
uri:
|
uri:
|
||||||
description: URI to call in order to fetch the permissions list.
|
description: URI to call in order to fetch the permissions list.
|
||||||
example: https://www.example.org/blocklists/list1.csv
|
example: https://www.example.org/blocklists/list1.csv
|
||||||
|
@ -4993,7 +4998,7 @@ paths:
|
||||||
- description: The code to use for the emoji, which will be used by instance denizens to select it. This must be unique on the instance.
|
- description: The code to use for the emoji, which will be used by instance denizens to select it. This must be unique on the instance.
|
||||||
in: formData
|
in: formData
|
||||||
name: shortcode
|
name: shortcode
|
||||||
pattern: \w{2,30}
|
pattern: \w{1,30}
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- description: A png or gif image of the emoji. Animated pngs work too! To ensure compatibility with other fedi implementations, emoji size limit is 50kb by default.
|
- description: A png or gif image of the emoji. Animated pngs work too! To ensure compatibility with other fedi implementations, emoji size limit is 50kb by default.
|
||||||
|
@ -5139,7 +5144,7 @@ paths:
|
||||||
- description: The code to use for the emoji, which will be used by instance denizens to select it. This must be unique on the instance. Works for the `copy` action type only.
|
- description: The code to use for the emoji, which will be used by instance denizens to select it. This must be unique on the instance. Works for the `copy` action type only.
|
||||||
in: formData
|
in: formData
|
||||||
name: shortcode
|
name: shortcode
|
||||||
pattern: \w{2,30}
|
pattern: \w{1,30}
|
||||||
type: string
|
type: string
|
||||||
- description: A new png or gif image to use for the emoji. Animated pngs work too! To ensure compatibility with other fedi implementations, emoji size limit is 50kb by default. Works for LOCAL emojis only.
|
- description: A new png or gif image to use for the emoji. Animated pngs work too! To ensure compatibility with other fedi implementations, emoji size limit is 50kb by default. Works for LOCAL emojis only.
|
||||||
in: formData
|
in: formData
|
||||||
|
@ -5859,9 +5864,9 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- default: false
|
- 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.
|
description: When removing the domain permission draft, also create a domain exclude entry for the target domain, so that drafts will not be created for this domain in the future.
|
||||||
in: formData
|
in: formData
|
||||||
name: ignore_target
|
name: exclude_target
|
||||||
type: boolean
|
type: boolean
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
|
@ -5888,6 +5893,182 @@ paths:
|
||||||
summary: Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
|
summary: Remove a domain permission draft, optionally ignoring all future drafts targeting the given domain.
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/api/v1/admin/domain_permission_excludes:
|
||||||
|
get:
|
||||||
|
description: |-
|
||||||
|
The excludes 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_excludes?limit=20&max_id=01FC0SKA48HNSVR6YKZCQGS2V8>; rel="next", <https://example.org/api/v1/admin/domain_permission_excludes?limit=20&min_id=01FC0SKW5JK2Q4EVAV2B462YY0>; rel="prev"
|
||||||
|
````
|
||||||
|
operationId: domainPermissionExcludesGet
|
||||||
|
parameters:
|
||||||
|
- description: Return only excludes that target the given domain.
|
||||||
|
in: query
|
||||||
|
name: domain
|
||||||
|
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 excludes.
|
||||||
|
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 excludes.
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- multipart/form-data
|
||||||
|
- application/json
|
||||||
|
description: |-
|
||||||
|
Excluded domains (and their subdomains) will not be automatically blocked or allowed when a list of domain permissions is imported or subscribed to.
|
||||||
|
|
||||||
|
You can still manually create domain blocks or domain allows for excluded domains, and any new or existing domain blocks or domain allows for an excluded domain will still be enforced.
|
||||||
|
operationId: domainPermissionExcludeCreate
|
||||||
|
parameters:
|
||||||
|
- description: Domain to create the permission exclude for.
|
||||||
|
in: formData
|
||||||
|
name: domain
|
||||||
|
type: string
|
||||||
|
- description: Private comment about this domain exclude.
|
||||||
|
in: formData
|
||||||
|
name: private_comment
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The newly created domain permission exclude.
|
||||||
|
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 exclude with the given parameters.
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
/api/v1/admin/domain_permission_excludes/{id}:
|
||||||
|
delete:
|
||||||
|
operationId: domainPermissionExcludeDelete
|
||||||
|
parameters:
|
||||||
|
- description: ID of the domain permission exclude.
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The removed domain permission exclude.
|
||||||
|
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 exclude.
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
get:
|
||||||
|
operationId: domainPermissionExcludeGet
|
||||||
|
parameters:
|
||||||
|
- description: ID of the domain permission exclude.
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Domain permission exclude.
|
||||||
|
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 exclude with the given ID.
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/api/v1/admin/email/test:
|
/api/v1/admin/email/test:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
// The code to use for the emoji, which will be used by instance denizens to select it.
|
// The code to use for the emoji, which will be used by instance denizens to select it.
|
||||||
// This must be unique on the instance.
|
// This must be unique on the instance.
|
||||||
// type: string
|
// type: string
|
||||||
// pattern: \w{2,30}
|
// pattern: \w{1,30}
|
||||||
// required: true
|
// required: true
|
||||||
// -
|
// -
|
||||||
// name: image
|
// name: image
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
// The code to use for the emoji, which will be used by instance denizens to select it.
|
// The code to use for the emoji, which will be used by instance denizens to select it.
|
||||||
// This must be unique on the instance. Works for the `copy` action type only.
|
// This must be unique on the instance. Works for the `copy` action type only.
|
||||||
// type: string
|
// type: string
|
||||||
// pattern: \w{2,30}
|
// pattern: \w{1,30}
|
||||||
// -
|
// -
|
||||||
// name: image
|
// name: image
|
||||||
// in: formData
|
// in: formData
|
||||||
|
|
|
@ -560,7 +560,7 @@ func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyEmptyShortcode() {
|
||||||
b, err := io.ReadAll(result.Body)
|
b, err := io.ReadAll(result.Body)
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
|
||||||
suite.Equal(`{"error":"Bad Request: shortcode did not pass validation, must be between 2 and 30 characters, letters, numbers, and underscores only"}`, string(b))
|
suite.Equal(`{"error":"Bad Request: shortcode did not pass validation, must be between 1 and 30 characters, letters, numbers, and underscores only"}`, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyNoShortcode() {
|
func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyNoShortcode() {
|
||||||
|
|
|
@ -155,7 +155,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
||||||
|
@ -296,7 +296,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
||||||
|
@ -437,7 +437,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
||||||
|
@ -629,7 +629,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
||||||
|
@ -792,7 +792,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/original/`+instanceAccount.AvatarMediaAttachment.ID+`.gif",`+`
|
"thumbnail": "http://localhost:8080/fileserver/01AY6P665V14JJR0AFVRT7311Y/attachment/original/`+instanceAccount.AvatarMediaAttachment.ID+`.gif",`+`
|
||||||
|
@ -974,7 +974,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
||||||
|
|
|
@ -66,50 +66,6 @@ type DomainPermission struct {
|
||||||
PermissionType string `json:"permission_type,omitempty"`
|
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).
|
// DomainPermissionRequest is the form submitted as a POST to create a new domain permission entry (allow/block).
|
||||||
//
|
//
|
||||||
// swagger:ignore
|
// swagger:ignore
|
||||||
|
@ -143,3 +99,77 @@ type DomainKeysExpireRequest struct {
|
||||||
// hostname/domain to expire keys for.
|
// hostname/domain to expire keys for.
|
||||||
Domain string `form:"domain" json:"domain"`
|
Domain string `form:"domain" json:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
|
||||||
|
//
|
||||||
|
// swagger:model domainPermissionSubscription
|
||||||
|
type DomainPermissionSubscription struct {
|
||||||
|
// The ID of the domain permission subscription.
|
||||||
|
// example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||||
|
// readonly: true
|
||||||
|
ID string `json:"id"`
|
||||||
|
// Title of this list, as set by admin who created or updated it.f
|
||||||
|
// example: really cool list of neato pals
|
||||||
|
Title string `json:"title"`
|
||||||
|
// The type of domain permission subscription (allow, block).
|
||||||
|
// example: block
|
||||||
|
PermissionType string `json:"permission_type"`
|
||||||
|
// If true, domain permissions arising from this subscription will be created as drafts that must be approved by a moderator to take effect. If false, domain permissions from this subscription will come into force immediately.
|
||||||
|
// example: true
|
||||||
|
AsDraft bool `json:"as_draft"`
|
||||||
|
// ID of the account that created this subscription.
|
||||||
|
// example: 01FBW21XJA09XYX51KV5JVBW0F
|
||||||
|
// readonly: true
|
||||||
|
CreatedByAccountID string `json:"created_by_account_id"`
|
||||||
|
// URI to call in order to fetch the permissions list.
|
||||||
|
// example: https://www.example.org/blocklists/list1.csv
|
||||||
|
URI string `json:"uri"`
|
||||||
|
// MIME content type to use when parsing the permissions list.
|
||||||
|
// example: text/csv
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
// (Optional) username to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchUsername string `json:"fetch_username,omitempty"`
|
||||||
|
// (Optional) password to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchPassword string `json:"fetch_password,omitempty"`
|
||||||
|
// Time 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,omitempty"`
|
||||||
|
// If most recent fetch attempt failed, this field will contain an error message related to the fetch attempt.
|
||||||
|
// example: Oopsie doopsie, we made a fucky wucky.
|
||||||
|
// readonly: true
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
// Count of domain permission entries discovered at URI on last (successful) fetch.
|
||||||
|
// example: 53
|
||||||
|
// readonly: true
|
||||||
|
Count uint64 `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainPermissionSubscriptionRequest represents a request to create or update a domain permission subscription..
|
||||||
|
//
|
||||||
|
// swagger:ignore
|
||||||
|
type DomainPermissionSubscriptionRequest struct {
|
||||||
|
// Title of this list, as set by admin who created or updated it.f
|
||||||
|
// example: really cool list of neato pals
|
||||||
|
Title string `form:"title" json:"title"`
|
||||||
|
// The type of domain permission subscription (allow, block).
|
||||||
|
// example: block
|
||||||
|
PermissionType string `form:"permission_type" json:"permission_type"`
|
||||||
|
// If true, domain permissions arising from this subscription will be created as drafts that must be approved by a moderator to take effect. If false, domain permissions from this subscription will come into force immediately.
|
||||||
|
// example: true
|
||||||
|
AsDraft bool `form:"as_draft" json:"as_draft"`
|
||||||
|
// URI to call in order to fetch the permissions list.
|
||||||
|
// example: https://www.example.org/blocklists/list1.csv
|
||||||
|
URI string `form:"uri" json:"uri"`
|
||||||
|
// MIME content type to use when parsing the permissions list.
|
||||||
|
// example: text/csv
|
||||||
|
ContentType string `form:"content_type" json:"content_type"`
|
||||||
|
// (Optional) username to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchUsername string `form:"fetch_username" json:"fetch_username"`
|
||||||
|
// (Optional) password to set for basic auth when doing a fetch of URI.
|
||||||
|
// example: admin123
|
||||||
|
FetchPassword string `form:"fetch_password" json:"fetch_password"`
|
||||||
|
}
|
||||||
|
|
120
internal/db/bundb/domainpermissiondraft_test.go
Normal file
120
internal/db/bundb/domainpermissiondraft_test.go
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bundb_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DomainPermissionDraftTestSuite struct {
|
||||||
|
BunDBStandardTestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DomainPermissionDraftTestSuite) TestPermDraftCreateGetDelete() {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
draft = >smodel.DomainPermissionDraft{
|
||||||
|
ID: "01JCZN614XG85GCGAMSV9ZZAEJ",
|
||||||
|
PermissionType: gtsmodel.DomainPermissionBlock,
|
||||||
|
Domain: "exämple.org",
|
||||||
|
CreatedByAccountID: suite.testAccounts["admin_account"].ID,
|
||||||
|
PrivateComment: "this domain is poo",
|
||||||
|
PublicComment: "this domain is poo, but phrased in a more outward-facing way",
|
||||||
|
Obfuscate: util.Ptr(false),
|
||||||
|
SubscriptionID: "01JCZN8PG55KKEVTDAY52D0T3P",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Whack the draft in.
|
||||||
|
if err := suite.state.DB.PutDomainPermissionDraft(ctx, draft); err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the draft again.
|
||||||
|
dbDraft, err := suite.state.DB.GetDomainPermissionDraftByID(ctx, draft.ID)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain should have been stored punycoded.
|
||||||
|
suite.Equal("xn--exmple-cua.org", dbDraft.Domain)
|
||||||
|
|
||||||
|
// Search for domain using both
|
||||||
|
// punycode and unicode variants.
|
||||||
|
search1, err := suite.state.DB.GetDomainPermissionDrafts(
|
||||||
|
ctx,
|
||||||
|
gtsmodel.DomainPermissionUnknown,
|
||||||
|
"",
|
||||||
|
"exämple.org",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
if len(search1) != 1 {
|
||||||
|
suite.FailNow("couldn't get domain perm draft exämple.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
search2, err := suite.state.DB.GetDomainPermissionDrafts(
|
||||||
|
ctx,
|
||||||
|
gtsmodel.DomainPermissionUnknown,
|
||||||
|
"",
|
||||||
|
"xn--exmple-cua.org",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
if len(search2) != 1 {
|
||||||
|
suite.FailNow("couldn't get domain perm draft example.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change ID + try to put the same draft again.
|
||||||
|
draft.ID = "01JCZNVYSDT3JE385FABMJ7ADQ"
|
||||||
|
err = suite.state.DB.PutDomainPermissionDraft(ctx, draft)
|
||||||
|
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||||
|
suite.FailNow("was able to insert same domain perm draft twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put same draft but change permission type, should work.
|
||||||
|
draft.PermissionType = gtsmodel.DomainPermissionAllow
|
||||||
|
if err := suite.state.DB.PutDomainPermissionDraft(ctx, draft); err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete both drafts.
|
||||||
|
for _, id := range []string{
|
||||||
|
"01JCZN614XG85GCGAMSV9ZZAEJ",
|
||||||
|
"01JCZNVYSDT3JE385FABMJ7ADQ",
|
||||||
|
} {
|
||||||
|
if err := suite.state.DB.DeleteDomainPermissionDraft(ctx, id); err != nil {
|
||||||
|
suite.FailNow("error deleting domain permission draft")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDomainPermissionDraftTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(DomainPermissionDraftTestSuite))
|
||||||
|
}
|
|
@ -22,7 +22,6 @@
|
||||||
"errors"
|
"errors"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||||
|
@ -65,14 +64,6 @@ func (d *domainDB) IsDomainPermissionExcluded(ctx context.Context, domain string
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if our host and given domain are equal
|
|
||||||
// or part of the same second-level domain; we
|
|
||||||
// always exclude 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
|
// Func to scan list of all
|
||||||
// excluded domain perms from DB.
|
// excluded domain perms from DB.
|
||||||
loadF := func() ([]string, error) {
|
loadF := func() ([]string, error) {
|
||||||
|
@ -80,12 +71,16 @@ func (d *domainDB) IsDomainPermissionExcluded(ctx context.Context, domain string
|
||||||
|
|
||||||
if err := d.db.
|
if err := d.db.
|
||||||
NewSelect().
|
NewSelect().
|
||||||
Table("domain_excludes").
|
Table("domain_permission_excludes").
|
||||||
Column("domain").
|
Column("domain").
|
||||||
Scan(ctx, &domains); err != nil {
|
Scan(ctx, &domains); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude our own domain as creating blocks
|
||||||
|
// or allows for self will likely break things.
|
||||||
|
domains = append(domains, config.GetHost())
|
||||||
|
|
||||||
return domains, nil
|
return domains, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
185
internal/db/bundb/domainpermissionexclude_test.go
Normal file
185
internal/db/bundb/domainpermissionexclude_test.go
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
// 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DomainPermissionExcludeTestSuite struct {
|
||||||
|
BunDBStandardTestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DomainPermissionExcludeTestSuite) TestPermExcludeCreateGetDelete() {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
exclude = >smodel.DomainPermissionExclude{
|
||||||
|
ID: "01JCZN614XG85GCGAMSV9ZZAEJ",
|
||||||
|
Domain: "exämple.org",
|
||||||
|
CreatedByAccountID: suite.testAccounts["admin_account"].ID,
|
||||||
|
PrivateComment: "this domain is poo",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Whack the exclude in.
|
||||||
|
if err := suite.state.DB.PutDomainPermissionExclude(ctx, exclude); err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the exclude again.
|
||||||
|
dbExclude, err := suite.state.DB.GetDomainPermissionExcludeByID(ctx, exclude.ID)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain should have been stored punycoded.
|
||||||
|
suite.Equal("xn--exmple-cua.org", dbExclude.Domain)
|
||||||
|
|
||||||
|
// Search for domain using both
|
||||||
|
// punycode and unicode variants.
|
||||||
|
search1, err := suite.state.DB.GetDomainPermissionExcludes(
|
||||||
|
ctx,
|
||||||
|
"exämple.org",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
if len(search1) != 1 {
|
||||||
|
suite.FailNow("couldn't get domain perm exclude exämple.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
search2, err := suite.state.DB.GetDomainPermissionExcludes(
|
||||||
|
ctx,
|
||||||
|
"xn--exmple-cua.org",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
if len(search2) != 1 {
|
||||||
|
suite.FailNow("couldn't get domain perm exclude example.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change ID + try to put the same exclude again.
|
||||||
|
exclude.ID = "01JCZNVYSDT3JE385FABMJ7ADQ"
|
||||||
|
err = suite.state.DB.PutDomainPermissionExclude(ctx, exclude)
|
||||||
|
if !errors.Is(err, db.ErrAlreadyExists) {
|
||||||
|
suite.FailNow("was able to insert same domain perm exclude twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete both excludes.
|
||||||
|
for _, id := range []string{
|
||||||
|
"01JCZN614XG85GCGAMSV9ZZAEJ",
|
||||||
|
"01JCZNVYSDT3JE385FABMJ7ADQ",
|
||||||
|
} {
|
||||||
|
if err := suite.state.DB.DeleteDomainPermissionExclude(ctx, id); err != nil {
|
||||||
|
suite.FailNow("error deleting domain permission exclude")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DomainPermissionExcludeTestSuite) TestExcluded() {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
createdByAccountID = suite.testAccounts["admin_account"].ID
|
||||||
|
)
|
||||||
|
|
||||||
|
// Insert some excludes into the db.
|
||||||
|
for _, exclude := range []*gtsmodel.DomainPermissionExclude{
|
||||||
|
{
|
||||||
|
ID: "01JD7AFFBBZSPY8R2M0JCGQGPW",
|
||||||
|
Domain: "example.org",
|
||||||
|
CreatedByAccountID: createdByAccountID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "01JD7AMK98E2QX78KXEZJ1RF5Z",
|
||||||
|
Domain: "boobs.com",
|
||||||
|
CreatedByAccountID: createdByAccountID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "01JD7AMXW3R3W98E91R62ACDA0",
|
||||||
|
Domain: "rad.boobs.com",
|
||||||
|
CreatedByAccountID: createdByAccountID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "01JD7AYYN5TXQVASB30PT08CE1",
|
||||||
|
Domain: "honkers.org",
|
||||||
|
CreatedByAccountID: createdByAccountID,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if err := suite.state.DB.PutDomainPermissionExclude(ctx, exclude); err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
domain string
|
||||||
|
excluded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range []testCase{
|
||||||
|
{
|
||||||
|
domain: config.GetHost(),
|
||||||
|
excluded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: "test.example.org",
|
||||||
|
excluded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: "example.org",
|
||||||
|
excluded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: "boobs.com",
|
||||||
|
excluded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: "rad.boobs.com",
|
||||||
|
excluded: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: "sir.not.appearing.in.this.list",
|
||||||
|
excluded: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
excluded, err := suite.state.DB.IsDomainPermissionExcluded(ctx, testCase.domain)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if excluded != testCase.excluded {
|
||||||
|
suite.Failf("",
|
||||||
|
"test %d: %s excluded should be %t",
|
||||||
|
i, testCase.domain, testCase.excluded,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDomainPermissionExcludeTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(DomainPermissionExcludeTestSuite))
|
||||||
|
}
|
|
@ -103,6 +103,9 @@ func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) (
|
||||||
// Ignore statuses that are currently pending approval.
|
// Ignore statuses that are currently pending approval.
|
||||||
q = q.Where("NOT ? = ?", bun.Ident("status.pending_approval"), true)
|
q = q.Where("NOT ? = ?", bun.Ident("status.pending_approval"), true)
|
||||||
|
|
||||||
|
// Ignore statuses that are direct messages.
|
||||||
|
q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), "direct")
|
||||||
|
|
||||||
count, err := q.Count(ctx)
|
count, err := q.Count(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (suite *InstanceTestSuite) TestCountInstanceUsersRemote() {
|
||||||
func (suite *InstanceTestSuite) TestCountInstanceStatuses() {
|
func (suite *InstanceTestSuite) TestCountInstanceStatuses() {
|
||||||
count, err := suite.db.CountInstanceStatuses(context.Background(), config.GetHost())
|
count, err := suite.db.CountInstanceStatuses(context.Background(), config.GetHost())
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
suite.Equal(20, count)
|
suite.Equal(19, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() {
|
func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() {
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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_ignores`.
|
||||||
|
if _, err := tx.
|
||||||
|
NewCreateTable().
|
||||||
|
Model((*gtsmodel.DomainPermissionExclude)(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"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,16 +27,6 @@
|
||||||
func init() {
|
func init() {
|
||||||
up := func(ctx context.Context, db *bun.DB) error {
|
up := func(ctx context.Context, db *bun.DB) error {
|
||||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) 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`.
|
// Create `domain_permission_subscriptions`.
|
||||||
if _, err := tx.
|
if _, err := tx.
|
||||||
NewCreateTable().
|
NewCreateTable().
|
||||||
|
@ -46,21 +36,8 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create `domain_permission_ignores`.
|
|
||||||
if _, err := tx.
|
|
||||||
NewCreateTable().
|
|
||||||
Model((*gtsmodel.DomainPermissionExclude)(nil)).
|
|
||||||
IfNotExists().
|
|
||||||
Exec(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create indexes. Indices. Indie sexes.
|
// Create indexes. Indices. Indie sexes.
|
||||||
for table, indexes := range map[string]map[string][]string{
|
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": {
|
||||||
"domain_permission_subscriptions_permission_type_idx": {"permission_type"},
|
"domain_permission_subscriptions_permission_type_idx": {"permission_type"},
|
||||||
},
|
},
|
||||||
|
|
|
@ -130,6 +130,9 @@ type Domain interface {
|
||||||
// DeleteDomainPermissionExclude deletes one DomainPermissionExclude with the given id.
|
// DeleteDomainPermissionExclude deletes one DomainPermissionExclude with the given id.
|
||||||
DeleteDomainPermissionExclude(ctx context.Context, id string) error
|
DeleteDomainPermissionExclude(ctx context.Context, id string) error
|
||||||
|
|
||||||
|
// IsDomainPermissionExcluded returns true if the given domain matches in the list of excluded domains.
|
||||||
|
IsDomainPermissionExcluded(ctx context.Context, domain string) (bool, error)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Domain permission subscription stuff.
|
Domain permission subscription stuff.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,12 +23,12 @@ type DomainPermissionSubscription struct {
|
||||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
|
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
|
||||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
|
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created.
|
||||||
Title string `bun:",nullzero"` // Moderator-set title for this list.
|
Title string `bun:",nullzero"` // Moderator-set title for this list.
|
||||||
PermissionType DomainPermissionType `bun:",notnull"` // Permission type of the subscription.
|
PermissionType DomainPermissionType `bun:",nullzero,notnull"` // Permission type of the subscription.
|
||||||
AsDraft *bool `bun:",nullzero,notnull,default:true"` // Create domain permission entries resulting from this subscription as drafts.
|
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.
|
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription.
|
||||||
CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
|
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.
|
URI string `bun:",unique,nullzero,notnull"` // URI of the domain permission list.
|
||||||
|
ContentType DomainPermSubContentType `bun:",nullzero,notnull"` // Content type to expect from the URI.
|
||||||
FetchUsername string `bun:",nullzero"` // Username to send when doing a GET of URI using basic auth.
|
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.
|
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.
|
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // Time when fetch of URI was last attempted.
|
||||||
|
@ -36,3 +36,38 @@ type DomainPermissionSubscription struct {
|
||||||
Error string `bun:",nullzero"` // If IsError=true, this field contains the error resulting from the attempted fetch.
|
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.
|
Count uint64 `bun:""` // Count of domain permission entries discovered at URI.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DomainPermSubContentType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
DomainPermSubContentTypeUnknown DomainPermSubContentType = iota
|
||||||
|
DomainPermSubContentTypeCSV // text/csv
|
||||||
|
DomainPermSubContentTypeJSON // application/json
|
||||||
|
DomainPermSubContentTypePlain // text/plain
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p DomainPermSubContentType) String() string {
|
||||||
|
switch p {
|
||||||
|
case DomainPermSubContentTypeCSV:
|
||||||
|
return "text/csv"
|
||||||
|
case DomainPermSubContentTypeJSON:
|
||||||
|
return "application/json"
|
||||||
|
case DomainPermSubContentTypePlain:
|
||||||
|
return "text/plain"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDomainPermSubContentType(in string) DomainPermSubContentType {
|
||||||
|
switch in {
|
||||||
|
case "text/csv":
|
||||||
|
return DomainPermSubContentTypeCSV
|
||||||
|
case "application/json":
|
||||||
|
return DomainPermSubContentTypeCSV
|
||||||
|
case "text/plain":
|
||||||
|
return DomainPermSubContentTypeCSV
|
||||||
|
default:
|
||||||
|
return DomainPermSubContentTypeUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -249,7 +249,8 @@ func (p *Processor) DomainPermissionDraftAccept(
|
||||||
deleteDraft()
|
deleteDraft()
|
||||||
|
|
||||||
return new, actionID, errWithCode
|
return new, actionID, errWithCode
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// Domain permission exists but we should overwrite
|
// Domain permission exists but we should overwrite
|
||||||
// it by just updating the existing domain permission.
|
// it by just updating the existing domain permission.
|
||||||
// Domain can't change, so no need to re-run side effects.
|
// Domain can't change, so no need to re-run side effects.
|
||||||
|
@ -260,7 +261,6 @@ func (p *Processor) DomainPermissionDraftAccept(
|
||||||
existing.SetObfuscate(permDraft.Obfuscate)
|
existing.SetObfuscate(permDraft.Obfuscate)
|
||||||
existing.SetSubscriptionID(permDraft.SubscriptionID)
|
existing.SetSubscriptionID(permDraft.SubscriptionID)
|
||||||
|
|
||||||
var err error
|
|
||||||
switch dp := existing.(type) {
|
switch dp := existing.(type) {
|
||||||
case *gtsmodel.DomainBlock:
|
case *gtsmodel.DomainBlock:
|
||||||
err = p.state.DB.UpdateDomainBlock(ctx, dp)
|
err = p.state.DB.UpdateDomainBlock(ctx, dp)
|
||||||
|
@ -281,7 +281,6 @@ func (p *Processor) DomainPermissionDraftAccept(
|
||||||
apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false)
|
apiPerm, errWithCode := p.apiDomainPerm(ctx, existing, false)
|
||||||
return apiPerm, "", errWithCode
|
return apiPerm, "", errWithCode
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Processor) DomainPermissionDraftRemove(
|
func (p *Processor) DomainPermissionDraftRemove(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
31
internal/processing/admin/domainpermissionsubscription.go
Normal file
31
internal/processing/admin/domainpermissionsubscription.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Processor) DomainPermissionSubscriptionCreate(
|
||||||
|
ctx context.Context,
|
||||||
|
acct *gtsmodel.Account,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
|
@ -46,7 +46,7 @@
|
||||||
domainGrp = `(?:` + alphaNumeric + `|\.|\-|\:)` // Non-capturing group that matches against a single valid domain character.
|
domainGrp = `(?:` + alphaNumeric + `|\.|\-|\:)` // Non-capturing group that matches against a single valid domain character.
|
||||||
mentionName = `^@(` + usernameGrp + `+)(?:@(` + domainGrp + `+))?$` // Extract parts of one mention, maybe including domain.
|
mentionName = `^@(` + usernameGrp + `+)(?:@(` + domainGrp + `+))?$` // Extract parts of one mention, maybe including domain.
|
||||||
mentionFinder = `(?:^|\s)(@` + usernameGrp + `+(?:@` + domainGrp + `+)?)` // Extract all mentions from a text, each mention may include domain.
|
mentionFinder = `(?:^|\s)(@` + usernameGrp + `+(?:@` + domainGrp + `+)?)` // Extract all mentions from a text, each mention may include domain.
|
||||||
emojiShortcode = `\w{2,30}` // Pattern for emoji shortcodes. maximumEmojiShortcodeLength = 30
|
emojiShortcode = `\w{1,30}` // Pattern for emoji shortcodes. maximumEmojiShortcodeLength = 30
|
||||||
emojiFinder = `(?:\b)?:(` + emojiShortcode + `):(?:\b)?` // Extract all emoji shortcodes from a text.
|
emojiFinder = `(?:\b)?:(` + emojiShortcode + `):(?:\b)?` // Extract all emoji shortcodes from a text.
|
||||||
emojiValidator = `^` + emojiShortcode + `$` // Validate a single emoji shortcode.
|
emojiValidator = `^` + emojiShortcode + `$` // Validate a single emoji shortcode.
|
||||||
usernameStrict = `^[a-z0-9_]{1,64}$` // Pattern for usernames on THIS instance. maximumUsernameLength = 64
|
usernameStrict = `^[a-z0-9_]{1,64}$` // Pattern for usernames on THIS instance. maximumUsernameLength = 64
|
||||||
|
|
|
@ -1993,7 +1993,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"domain_count": 2,
|
"domain_count": 2,
|
||||||
"status_count": 20,
|
"status_count": 19,
|
||||||
"user_count": 4
|
"user_count": 4
|
||||||
},
|
},
|
||||||
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
"thumbnail": "http://localhost:8080/assets/logo.webp",
|
||||||
|
|
|
@ -190,11 +190,11 @@ func CustomCSS(customCSS string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmojiShortcode just runs the given shortcode through the regular expression
|
// EmojiShortcode just runs the given shortcode through the regular expression
|
||||||
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
|
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 1-30 characters,
|
||||||
// a-zA-Z, numbers, and underscores.
|
// a-zA-Z, numbers, and underscores.
|
||||||
func EmojiShortcode(shortcode string) error {
|
func EmojiShortcode(shortcode string) error {
|
||||||
if !regexes.EmojiValidator.MatchString(shortcode) {
|
if !regexes.EmojiValidator.MatchString(shortcode) {
|
||||||
return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, letters, numbers, and underscores only", shortcode)
|
return fmt.Errorf("shortcode %s did not pass validation, must be between 1 and 30 characters, letters, numbers, and underscores only", shortcode)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,7 +345,7 @@ type testStruct struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcode: "p",
|
shortcode: "p",
|
||||||
ok: false,
|
ok: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcode: "pp",
|
shortcode: "pp",
|
||||||
|
@ -361,6 +361,10 @@ type testStruct struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcode: "_",
|
shortcode: "_",
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcode: "",
|
||||||
ok: false,
|
ok: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,6 +34,7 @@ EXPECT=$(cat << "EOF"
|
||||||
"client-mem-ratio": 0.1,
|
"client-mem-ratio": 0.1,
|
||||||
"conversation-last-status-ids-mem-ratio": 2,
|
"conversation-last-status-ids-mem-ratio": 2,
|
||||||
"conversation-mem-ratio": 1,
|
"conversation-mem-ratio": 1,
|
||||||
|
"domain-permission-draft-mem-ratio": 0.5,
|
||||||
"emoji-category-mem-ratio": 0.1,
|
"emoji-category-mem-ratio": 0.1,
|
||||||
"emoji-mem-ratio": 3,
|
"emoji-mem-ratio": 3,
|
||||||
"filter-keyword-mem-ratio": 0.5,
|
"filter-keyword-mem-ratio": 0.5,
|
||||||
|
|
|
@ -1403,7 +1403,8 @@ button.tab-button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain-permission-draft-details {
|
.domain-permission-draft-details,
|
||||||
|
.domain-permission-exclude-details {
|
||||||
.info-list {
|
.info-list {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ export default function NewEmojiForm() {
|
||||||
label="Shortcode, must be unique among the instance's local emoji"
|
label="Shortcode, must be unique among the instance's local emoji"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
{...{pattern: "^\\w{2,30}$"}}
|
{...{pattern: "^\\w{1,30}$"}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { useMemo } from "react";
|
||||||
import { useTextInput } from "../../../../lib/form";
|
import { useTextInput } from "../../../../lib/form";
|
||||||
import { useListEmojiQuery } from "../../../../lib/query/admin/custom-emoji";
|
import { useListEmojiQuery } from "../../../../lib/query/admin/custom-emoji";
|
||||||
|
|
||||||
const shortcodeRegex = /^\w{2,30}$/;
|
const shortcodeRegex = /^\w{1,30}$/;
|
||||||
|
|
||||||
export default function useShortcode() {
|
export default function useShortcode() {
|
||||||
const { data: emoji = [] } = useListEmojiQuery({
|
const { data: emoji = [] } = useListEmojiQuery({
|
||||||
|
@ -42,8 +42,8 @@ export default function useShortcode() {
|
||||||
return "Shortcode already in use";
|
return "Shortcode already in use";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code.length < 2 || code.length > 30) {
|
if (code.length < 1 || code.length > 30) {
|
||||||
return "Shortcode must be between 2 and 30 characters";
|
return "Shortcode must be between 1 and 30 characters";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shortcodeRegex.test(code)) {
|
if (!shortcodeRegex.test(code)) {
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function DomainPermissionDraftHelpText() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
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.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DomainPermissionDraftDocsLink() {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://docs.gotosocial.org/en/latest/admin/settings/#domain-permission-drafts"
|
||||||
|
target="_blank"
|
||||||
|
className="docslink"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Learn more about domain permission drafts (opens in a new tab)
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import { Error as ErrorC } from "../../../../components/error";
|
||||||
import { Select, TextInput } from "../../../../components/form/inputs";
|
import { Select, TextInput } from "../../../../components/form/inputs";
|
||||||
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||||
import { useCapitalize } from "../../../../lib/util";
|
import { useCapitalize } from "../../../../lib/util";
|
||||||
|
import { DomainPermissionDraftDocsLink, DomainPermissionDraftHelpText } from "./common";
|
||||||
|
|
||||||
export default function DomainPermissionDraftsSearch() {
|
export default function DomainPermissionDraftsSearch() {
|
||||||
return (
|
return (
|
||||||
|
@ -38,10 +39,9 @@ export default function DomainPermissionDraftsSearch() {
|
||||||
<p>
|
<p>
|
||||||
You can use the form below to search through domain permission drafts.
|
You can use the form below to search through domain permission drafts.
|
||||||
<br/>
|
<br/>
|
||||||
Domain permission drafts are domain block or domain allow entries that are not yet in force.
|
<DomainPermissionDraftHelpText />
|
||||||
<br/>
|
|
||||||
You can choose to accept or remove a draft.
|
|
||||||
</p>
|
</p>
|
||||||
|
<DomainPermissionDraftDocsLink />
|
||||||
</div>
|
</div>
|
||||||
<DomainPermissionDraftsSearchForm />
|
<DomainPermissionDraftsSearchForm />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||||
import MutationButton from "../../../../components/form/mutation-button";
|
import MutationButton from "../../../../components/form/mutation-button";
|
||||||
import { Checkbox, RadioGroup, TextArea, TextInput } from "../../../../components/form/inputs";
|
import { Checkbox, RadioGroup, TextArea, TextInput } from "../../../../components/form/inputs";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
import { DomainPermissionDraftDocsLink, DomainPermissionDraftHelpText } from "./common";
|
||||||
|
|
||||||
export default function DomainPermissionDraftNew() {
|
export default function DomainPermissionDraftNew() {
|
||||||
const [ _location, setLocation ] = useLocation();
|
const [ _location, setLocation ] = useLocation();
|
||||||
|
@ -67,13 +68,8 @@ export default function DomainPermissionDraftNew() {
|
||||||
>
|
>
|
||||||
<div className="form-section-docs">
|
<div className="form-section-docs">
|
||||||
<h2>New Domain Permission Draft</h2>
|
<h2>New Domain Permission Draft</h2>
|
||||||
<p>
|
<p><DomainPermissionDraftHelpText /></p>
|
||||||
You can use the form below to create a new domain permission draft.
|
<DomainPermissionDraftDocsLink />
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
GoToSocial
|
||||||
|
Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function DomainPermissionExcludeHelpText() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
Domain permission excludes prevent permissions for a domain (and all
|
||||||
|
subdomains) from being auomatically managed by domain permission subscriptions.
|
||||||
|
<br/>
|
||||||
|
For example, if you create an exclude entry for <code>example.org</code>, then
|
||||||
|
a blocklist or allowlist subscription will <em>exclude</em> entries for <code>example.org</code>
|
||||||
|
and any of its subdomains (<code>sub.example.org</code>, <code>another.sub.example.org</code> etc.)
|
||||||
|
when creating domain permission drafts and domain blocks/allows.
|
||||||
|
<br/>
|
||||||
|
This functionality allows you to manually manage permissions for excluded domains,
|
||||||
|
in cases where you know you definitely do or don't want to federate with a given domain,
|
||||||
|
no matter what entries are contained in a domain permission subscription.
|
||||||
|
<br/>
|
||||||
|
Note that by itself, creation of an exclude entry for a given domain does not affect
|
||||||
|
federation with that domain at all, it is only useful in combination with permission subscriptions.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DomainPermissionExcludeDocsLink() {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://docs.gotosocial.org/en/latest/admin/settings/#domain-permission-excludes"
|
||||||
|
target="_blank"
|
||||||
|
className="docslink"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Learn more about domain permission excludes (opens in a new tab)
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,20 +18,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useParams } from "wouter";
|
import { useLocation, useParams } from "wouter";
|
||||||
import Loading from "../../../../components/loading";
|
import Loading from "../../../../components/loading";
|
||||||
import { useBaseUrl } from "../../../../lib/navigation/util";
|
import { useBaseUrl } from "../../../../lib/navigation/util";
|
||||||
import BackButton from "../../../../components/back-button";
|
import BackButton from "../../../../components/back-button";
|
||||||
import { Error as ErrorC } from "../../../../components/error";
|
import { Error as ErrorC } from "../../../../components/error";
|
||||||
import UsernameLozenge from "../../../../components/username-lozenge";
|
import UsernameLozenge from "../../../../components/username-lozenge";
|
||||||
import { useGetDomainPermissionExcludeQuery } from "../../../../lib/query/admin/domain-permissions/excludes";
|
import { useDeleteDomainPermissionExcludeMutation, useGetDomainPermissionExcludeQuery } from "../../../../lib/query/admin/domain-permissions/excludes";
|
||||||
|
import MutationButton from "../../../../components/form/mutation-button";
|
||||||
|
|
||||||
export default function DomainPermissionExcludeDetail() {
|
export default function DomainPermissionExcludeDetail() {
|
||||||
const baseUrl = useBaseUrl();
|
const baseUrl = useBaseUrl();
|
||||||
const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
|
const backLocation: string = history.state?.backLocation ?? `~${baseUrl}`;
|
||||||
const params = useParams();
|
|
||||||
|
|
||||||
let id = params.permExcludeId as string | undefined;
|
const params = useParams();
|
||||||
|
let id = params.excludeId as string | undefined;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw "no perm ID";
|
throw "no perm ID";
|
||||||
}
|
}
|
||||||
|
@ -54,13 +55,7 @@ export default function DomainPermissionExcludeDetail() {
|
||||||
|
|
||||||
const created = permExclude.created_at ? new Date(permExclude.created_at).toDateString(): "unknown";
|
const created = permExclude.created_at ? new Date(permExclude.created_at).toDateString(): "unknown";
|
||||||
const domain = permExclude.domain;
|
const domain = permExclude.domain;
|
||||||
const permType = permExclude.permission_type;
|
|
||||||
if (!permType) {
|
|
||||||
return <ErrorC error={new Error("permission_type was undefined")} />;
|
|
||||||
}
|
|
||||||
const publicComment = permExclude.public_comment ?? "[none]";
|
|
||||||
const privateComment = permExclude.private_comment ?? "[none]";
|
const privateComment = permExclude.private_comment ?? "[none]";
|
||||||
const subscriptionID = permExclude.subscription_id ?? "[none]";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="domain-permission-exclude-details">
|
<div className="domain-permission-exclude-details">
|
||||||
|
@ -84,28 +79,10 @@ export default function DomainPermissionExcludeDetail() {
|
||||||
<dt>Domain</dt>
|
<dt>Domain</dt>
|
||||||
<dd>{domain}</dd>
|
<dd>{domain}</dd>
|
||||||
</div>
|
</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">
|
<div className="info-list-entry">
|
||||||
<dt>Private comment</dt>
|
<dt>Private comment</dt>
|
||||||
<dd>{privateComment}</dd>
|
<dd>{privateComment}</dd>
|
||||||
</div>
|
</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>
|
</dl>
|
||||||
<HandleExclude
|
<HandleExclude
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -116,5 +93,27 @@ export default function DomainPermissionExcludeDetail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function HandleExclude({ id, backLocation}: {id: string, backLocation: string}) {
|
function HandleExclude({ id, backLocation}: {id: string, backLocation: string}) {
|
||||||
return <></>;
|
const [_location, setLocation] = useLocation();
|
||||||
|
const [ deleteExclude, deleteResult ] = useDeleteDomainPermissionExcludeMutation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MutationButton
|
||||||
|
label={`Delete exclude`}
|
||||||
|
title={`Delete exclude`}
|
||||||
|
type="button"
|
||||||
|
className="button danger"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteExclude(id).then(res => {
|
||||||
|
if ("data" in res) {
|
||||||
|
setLocation(backLocation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={false}
|
||||||
|
showError={true}
|
||||||
|
result={deleteResult}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { DomainPerm } from "../../../../lib/types/domain-permission";
|
||||||
import { Error as ErrorC } from "../../../../components/error";
|
import { Error as ErrorC } from "../../../../components/error";
|
||||||
import { Select, TextInput } from "../../../../components/form/inputs";
|
import { Select, TextInput } from "../../../../components/form/inputs";
|
||||||
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||||
|
import { DomainPermissionExcludeDocsLink, DomainPermissionExcludeHelpText } from "./common";
|
||||||
|
|
||||||
export default function DomainPermissionExcludesSearch() {
|
export default function DomainPermissionExcludesSearch() {
|
||||||
return (
|
return (
|
||||||
|
@ -37,10 +38,9 @@ export default function DomainPermissionExcludesSearch() {
|
||||||
<p>
|
<p>
|
||||||
You can use the form below to search through domain permission excludes.
|
You can use the form below to search through domain permission excludes.
|
||||||
<br/>
|
<br/>
|
||||||
Domain permission excludes are domain block or domain allow entries that are not yet in force.
|
<DomainPermissionExcludeHelpText />
|
||||||
<br/>
|
|
||||||
You can choose to accept or remove a exclude.
|
|
||||||
</p>
|
</p>
|
||||||
|
<DomainPermissionExcludeDocsLink />
|
||||||
</div>
|
</div>
|
||||||
<DomainPermissionExcludesSearchForm />
|
<DomainPermissionExcludesSearchForm />
|
||||||
</div>
|
</div>
|
||||||
|
@ -204,7 +204,6 @@ function ExcludeListEntry({ permExclude, linkTo, backLocation }: ExcludeEntryPro
|
||||||
role="link"
|
role="link"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<h3>{`Exclude ${domain}`}</h3>
|
|
||||||
<dl className="info-list">
|
<dl className="info-list">
|
||||||
<div className="info-list-entry">
|
<div className="info-list-entry">
|
||||||
<dt>Domain:</dt>
|
<dt>Domain:</dt>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { formDomainValidator } from "../../../../lib/util/formvalidators";
|
||||||
import MutationButton from "../../../../components/form/mutation-button";
|
import MutationButton from "../../../../components/form/mutation-button";
|
||||||
import { TextArea, TextInput } from "../../../../components/form/inputs";
|
import { TextArea, TextInput } from "../../../../components/form/inputs";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
import { DomainPermissionExcludeDocsLink, DomainPermissionExcludeHelpText } from "./common";
|
||||||
|
|
||||||
export default function DomainPermissionExcludeNew() {
|
export default function DomainPermissionExcludeNew() {
|
||||||
const [ _location, setLocation ] = useLocation();
|
const [ _location, setLocation ] = useLocation();
|
||||||
|
@ -59,13 +60,8 @@ export default function DomainPermissionExcludeNew() {
|
||||||
>
|
>
|
||||||
<div className="form-section-docs">
|
<div className="form-section-docs">
|
||||||
<h2>New Domain Permission Exclude</h2>
|
<h2>New Domain Permission Exclude</h2>
|
||||||
<p>
|
<p><DomainPermissionExcludeHelpText /></p>
|
||||||
You can use the form below to create a new domain permission exclude.
|
<DomainPermissionExcludeDocsLink />
|
||||||
<br/>
|
|
||||||
Domain permission excludes are domain block or domain allow entries that are not yet in force.
|
|
||||||
<br/>
|
|
||||||
You can choose to accept or remove a exclude.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
|
@ -79,7 +75,7 @@ export default function DomainPermissionExcludeNew() {
|
||||||
<TextArea
|
<TextArea
|
||||||
field={form.private_comment}
|
field={form.private_comment}
|
||||||
label={"Private comment"}
|
label={"Private comment"}
|
||||||
placeholder="This domain is like unto a clown car full of clowns, I suggest we block it forthwith."
|
placeholder="Created an exclude for this domain because we should manage it manually."
|
||||||
autoCapitalize="sentences"
|
autoCapitalize="sentences"
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2922,9 +2922,9 @@ create-require@^1.1.0:
|
||||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||||
|
|
||||||
cross-spawn@^7.0.2:
|
cross-spawn@^7.0.2:
|
||||||
version "7.0.3"
|
version "7.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.1.0"
|
path-key "^3.1.0"
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
|
|
Loading…
Reference in a new issue