diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml
index 5c0c2ae3d..27aa7559b 100644
--- a/docs/api/swagger.yaml
+++ b/docs/api/swagger.yaml
@@ -1130,6 +1130,78 @@ 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 use when parsing the permissions list.
+ example: text/csv
+ type: string
+ x-go-name: ContentType
+ count:
+ description: Count of domain permission entries discovered at URI on last (successful) fetch.
+ example: 53
+ format: uint64
+ readOnly: true
+ type: integer
+ x-go-name: Count
+ created_by_account_id:
+ description: ID of the account that created this subscription.
+ example: 01FBW21XJA09XYX51KV5JVBW0F
+ readOnly: true
+ type: string
+ x-go-name: CreatedByAccountID
+ error:
+ description: If most recent fetch attempt failed, this field will contain an error message related to the fetch attempt.
+ example: Oopsie doopsie, we made a fucky wucky.
+ readOnly: true
+ type: string
+ x-go-name: Error
+ fetch_password:
+ description: (Optional) password to set for basic auth when doing a fetch of URI.
+ example: admin123
+ type: string
+ x-go-name: FetchPassword
+ fetch_username:
+ description: (Optional) username to set for basic auth when doing a fetch of URI.
+ example: admin123
+ type: string
+ x-go-name: FetchUsername
+ fetched_at:
+ description: Time at which the most recent fetch was attempted (ISO 8601 Datetime).
+ example: "2021-07-30T09:20:25+00:00"
+ readOnly: true
+ type: string
+ x-go-name: FetchedAt
+ id:
+ description: The ID of the domain permission subscription.
+ example: 01FBW21XJA09XYX51KV5JVBW0F
+ readOnly: true
+ type: string
+ x-go-name: ID
+ permission_type:
+ description: The type of domain permission subscription (allow, block).
+ example: block
+ type: string
+ x-go-name: PermissionType
+ title:
+ description: Title of this list, as set by admin who created or updated it.f
+ example: really cool list of neato pals
+ type: string
+ x-go-name: Title
+ uri:
+ description: URI to call in order to fetch the permissions list.
+ example: https://www.example.org/blocklists/list1.csv
+ type: string
+ x-go-name: URI
+ title: DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks).
+ type: object
+ x-go-name: DomainPermissionSubscription
+ x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
emoji:
properties:
category:
diff --git a/internal/api/model/domain.go b/internal/api/model/domain.go
index c973c7d92..3d11ad27e 100644
--- a/internal/api/model/domain.go
+++ b/internal/api/model/domain.go
@@ -99,3 +99,77 @@ type DomainKeysExpireRequest struct {
// hostname/domain to expire keys for.
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"`
+}
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
index a4f9f2044..31c4301da 100644
--- a/internal/cache/cache.go
+++ b/internal/cache/cache.go
@@ -75,6 +75,7 @@ func (c *Caches) Init() {
c.initDomainAllow()
c.initDomainBlock()
c.initDomainPermissionDraft()
+ c.initDomainPermissionSubscription()
c.initDomainPermissionExclude()
c.initEmoji()
c.initEmojiCategory()
diff --git a/internal/cache/db.go b/internal/cache/db.go
index aac11236a..b0bbe0eaa 100644
--- a/internal/cache/db.go
+++ b/internal/cache/db.go
@@ -70,6 +70,9 @@ type DBCaches struct {
// 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]
+
// DomainPermissionExclude provides access to the domain permission exclude database cache.
DomainPermissionExclude *domain.Cache
@@ -586,6 +589,37 @@ func (c *Caches) initDomainPermissionDraft() {
})
}
+func (c *Caches) initDomainPermissionSubscription() {
+ // Calculate maximum cache size.
+ cap := calculateResultCacheMax(
+ sizeofDomainPermissionSubscription(), // model in-mem size.
+ config.GetCacheDomainPermissionSubscriptionMemRation(),
+ )
+
+ log.Infof(nil, "cache size = %d", cap)
+
+ copyF := func(d1 *gtsmodel.DomainPermissionSubscription) *gtsmodel.DomainPermissionSubscription {
+ d2 := new(gtsmodel.DomainPermissionSubscription)
+ *d2 = *d1
+
+ // Don't include ptr fields that
+ // will be populated separately.
+ d2.CreatedByAccount = nil
+
+ return d2
+ }
+
+ c.DB.DomainPermissionSubscription.Init(structr.CacheConfig[*gtsmodel.DomainPermissionSubscription]{
+ Indices: []structr.IndexConfig{
+ {Fields: "ID"},
+ {Fields: "URI"},
+ },
+ MaxSize: cap,
+ IgnoreErr: ignoreErrors,
+ Copy: copyF,
+ })
+}
+
func (c *Caches) initDomainPermissionExclude() {
c.DB.DomainPermissionExclude = new(domain.Cache)
}
diff --git a/internal/cache/size.go b/internal/cache/size.go
index 26f4096ed..5a7688223 100644
--- a/internal/cache/size.go
+++ b/internal/cache/size.go
@@ -357,6 +357,20 @@ func sizeofDomainPermissionDraft() uintptr {
}))
}
+func sizeofDomainPermissionSubscription() uintptr {
+ return uintptr(size.Of(>smodel.DomainPermissionSubscription{
+ ID: exampleID,
+ CreatedAt: exampleTime,
+ PermissionType: gtsmodel.DomainPermissionBlock,
+ CreatedByAccountID: exampleID,
+ URI: exampleURI,
+ FetchUsername: "username",
+ FetchPassword: "password",
+ FetchedAt: exampleTime,
+ AsDraft: util.Ptr(true),
+ }))
+}
+
func sizeofEmoji() uintptr {
return uintptr(size.Of(>smodel.Emoji{
ID: exampleID,
diff --git a/internal/config/config.go b/internal/config/config.go
index 2e3ad8ec1..661744929 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -196,59 +196,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"`
- DomainPermissionDraftMemRation float64 `name:"domain-permission-draft-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).
diff --git a/internal/config/defaults.go b/internal/config/defaults.go
index 9b45002d0..827822635 100644
--- a/internal/config/defaults.go
+++ b/internal/config/defaults.go
@@ -158,58 +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,
- DomainPermissionDraftMemRation: 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,
+ 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{
diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go
index a35622f8e..e9330573b 100644
--- a/internal/config/helpers.gen.go
+++ b/internal/config/helpers.gen.go
@@ -3187,6 +3187,37 @@ 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()
diff --git a/internal/db/bundb/domainpermissionsubscription.go b/internal/db/bundb/domainpermissionsubscription.go
new file mode 100644
index 000000000..0e4a95abf
--- /dev/null
+++ b/internal/db/bundb/domainpermissionsubscription.go
@@ -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 .
+
+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
+}
diff --git a/internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions.go b/internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions.go
new file mode 100644
index 000000000..e5f719cc2
--- /dev/null
+++ b/internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions.go
@@ -0,0 +1,71 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package migrations
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ // Create `domain_permission_subscriptions`.
+ if _, err := tx.
+ NewCreateTable().
+ Model((*gtsmodel.DomainPermissionSubscription)(nil)).
+ IfNotExists().
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ // Create indexes. Indices. Indie sexes.
+ for table, indexes := range map[string]map[string][]string{
+ "domain_permission_subscriptions": {
+ "domain_permission_subscriptions_permission_type_idx": {"permission_type"},
+ },
+ } {
+ for index, columns := range indexes {
+ if _, err := tx.
+ NewCreateIndex().
+ Table(table).
+ Index(index).
+ Column(columns...).
+ IfNotExists().
+ Exec(ctx); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+ })
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/db/domain.go b/internal/db/domain.go
index f4d05ad1d..c5a9bea6d 100644
--- a/internal/db/domain.go
+++ b/internal/db/domain.go
@@ -132,4 +132,25 @@ type Domain interface {
// 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.
+ */
+
+ // 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
}
diff --git a/internal/gtsmodel/domainpermissionsubscription.go b/internal/gtsmodel/domainpermissionsubscription.go
new file mode 100644
index 000000000..9dfac0769
--- /dev/null
+++ b/internal/gtsmodel/domainpermissionsubscription.go
@@ -0,0 +1,73 @@
+// 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 .
+
+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:",nullzero,notnull"` // Permission type of the subscription.
+ AsDraft *bool `bun:",nullzero,notnull,default:true"` // Create domain permission entries resulting from this subscription as drafts.
+ CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription.
+ CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID.
+ URI string `bun:",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.
+ 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.
+}
+
+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
+ }
+}
diff --git a/internal/processing/admin/domainpermissionsubscription.go b/internal/processing/admin/domainpermissionsubscription.go
new file mode 100644
index 000000000..834ca45e6
--- /dev/null
+++ b/internal/processing/admin/domainpermissionsubscription.go
@@ -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 .
+
+package admin
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+func (p *Processor) DomainPermissionSubscriptionCreate(
+ ctx context.Context,
+ acct *gtsmodel.Account,
+) {
+
+}