From dc7b614909d18595cf658e3ced61de971cccd1ac Mon Sep 17 00:00:00 2001 From: tobi Date: Wed, 23 Oct 2024 16:53:48 +0200 Subject: [PATCH] mmmmmmmmmm, perms!!! --- .../api/client/admin/domainallowdraftsget.go | 116 +++++ .../api/client/admin/domainpermissiondraft.go | 167 +++++++ internal/api/model/domain.go | 44 ++ internal/cache/db.go | 69 +++ internal/cache/size.go | 29 ++ internal/config/config.go | 106 ++-- internal/config/defaults.go | 104 ++-- internal/config/helpers.gen.go | 62 +++ internal/db/bundb/domain.go | 471 ++++++++++++++++++ ...2153016_domain_permission_subscriptions.go | 85 ++++ internal/db/domain.go | 45 ++ internal/gtsmodel/domainpermissiondraft.go | 34 ++ .../gtsmodel/domainpermissionsubscription.go | 38 ++ 13 files changed, 1267 insertions(+), 103 deletions(-) create mode 100644 internal/api/client/admin/domainallowdraftsget.go create mode 100644 internal/api/client/admin/domainpermissiondraft.go create mode 100644 internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions.go create mode 100644 internal/gtsmodel/domainpermissiondraft.go create mode 100644 internal/gtsmodel/domainpermissionsubscription.go diff --git a/internal/api/client/admin/domainallowdraftsget.go b/internal/api/client/admin/domainallowdraftsget.go new file mode 100644 index 000000000..b8b5d0a6d --- /dev/null +++ b/internal/api/client/admin/domainallowdraftsget.go @@ -0,0 +1,116 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +// DomainAllowDraftsGETHandler swagger:operation GET /api/v1/admin/domain_allow_drafts domainAllowDraftsGet +// +// View domain allow drafts. +// +// The drafts will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). +// +// The next and previous queries can be parsed from the returned Link header. +// +// Example: +// +// ``` +// ; rel="next", ; rel="prev" +// ```` +// +// --- +// tags: +// - admin +// +// produces: +// - application/json +// +// parameters: +// - +// name: subscription_id +// type: string +// description: Show only drafts created by the given subscription ID. +// in: query +// - +// name: domain +// type: string +// description: Return only drafts that pertain to the given domain. +// in: query +// - +// name: max_id +// type: string +// description: >- +// Return only items *OLDER* than the given max ID (for paging downwards). +// The item with the specified ID will not be included in the response. +// in: query +// - +// name: since_id +// type: string +// description: >- +// Return only items *NEWER* than the given since ID. +// The item with the specified ID will not be included in the response. +// in: query +// - +// name: min_id +// type: string +// description: >- +// Return only items immediately *NEWER* than the given min ID (for paging upwards). +// The item with the specified ID will not be included in the response. +// in: query +// - +// name: limit +// type: integer +// description: Number of items to return. +// default: 20 +// minimum: 1 +// maximum: 100 +// in: query +// +// security: +// - OAuth2 Bearer: +// - admin +// +// responses: +// '200': +// description: Domain allow drafts. +// schema: +// type: array +// items: +// "$ref": "#/definitions/domainPermission" +// headers: +// Link: +// type: string +// description: Links to the next and previous queries. +// '400': +// description: bad request +// '401': +// description: unauthorized +// '403': +// description: forbidden +// '404': +// description: not found +// '406': +// description: not acceptable +// '500': +// description: internal server error +func (m *Module) DomainAllowDraftsGETHandler(c *gin.Context) { + m.getDomainPermissionDrafts(c, gtsmodel.DomainPermissionAllow) +} diff --git a/internal/api/client/admin/domainpermissiondraft.go b/internal/api/client/admin/domainpermissiondraft.go new file mode 100644 index 000000000..548f966ef --- /dev/null +++ b/internal/api/client/admin/domainpermissiondraft.go @@ -0,0 +1,167 @@ +// 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 ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// deleteDomainPermissionDraft deletes a single +// domain permission draft (block or allow). +func (m *Module) deleteDomainPermissionDraft( + c *gin.Context, + permType gtsmodel.DomainPermissionType, +) { + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) + return + } + + if !*authed.User.Admin { + err := fmt.Errorf("user %s not an admin", authed.User.ID) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1) + return + } + + if authed.Account.IsMoving() { + apiutil.ForbiddenAfterMove(c) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + permDraftID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + domainPerm, _, errWithCode := m.processor.Admin().DomainPermissionDraftDelete( + c.Request.Context(), + permType, + authed.Account, + permDraftID, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + apiutil.JSON(c, http.StatusOK, domainPerm) +} + +// getDomainPermissionDraft gets a single domain permission (block or allow). +func (m *Module) getDomainPermissionDraft( + c *gin.Context, + permType gtsmodel.DomainPermissionType, +) { + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) + return + } + + if !*authed.User.Admin { + err := fmt.Errorf("user %s not an admin", authed.User.ID) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + domainPermID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + export, errWithCode := apiutil.ParseDomainPermissionDraftExport(c.Query(apiutil.DomainPermissionDraftExportKey), false) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + domainPerm, errWithCode := m.processor.Admin().DomainPermissionDraftGet( + c.Request.Context(), + permType, + domainPermID, + export, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + apiutil.JSON(c, http.StatusOK, domainPerm) +} + +// getDomainPermissionDrafts gets domain permission drafts of the given type (block, allow). +func (m *Module) getDomainPermissionDrafts( + c *gin.Context, + permType gtsmodel.DomainPermissionType, +) { + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) + return + } + + if !*authed.User.Admin { + err := fmt.Errorf("user %s not an admin", authed.User.ID) + apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + export, errWithCode := apiutil.ParseDomainPermissionDraftExport(c.Query(apiutil.DomainPermissionDraftExportKey), false) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + domainPerm, errWithCode := m.processor.Admin().DomainPermissionDraftsGet( + c.Request.Context(), + permType, + authed.Account, + export, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + apiutil.JSON(c, http.StatusOK, domainPerm) +} diff --git a/internal/api/model/domain.go b/internal/api/model/domain.go index ddc96ef05..808a1721c 100644 --- a/internal/api/model/domain.go +++ b/internal/api/model/domain.go @@ -63,6 +63,50 @@ type DomainPermission struct { CreatedAt string `json:"created_at,omitempty"` } +// DomainPermissionSubscription represents an auto-refreshing subscription to a list of domain permissions (allows, blocks). +// +// swagger:model domainPermission +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). // // swagger:ignore diff --git a/internal/cache/db.go b/internal/cache/db.go index dd4e8b212..64a3ab120 100644 --- a/internal/cache/db.go +++ b/internal/cache/db.go @@ -67,6 +67,12 @@ type DBCaches struct { // DomainBlock provides access to the domain block database cache. DomainBlock *domain.Cache + // DomainPermissionDraft provides access to the domain permission draft database cache. + DomainPermissionDraft StructCache[*gtsmodel.DomainPermissionDraft] + + // DomainPermissionSubscription provides access to the domain permission subscription database cache. + DomainPermissionSubscription StructCache[*gtsmodel.DomainPermissionSubscription] + // Emoji provides access to the gtsmodel Emoji database cache. Emoji StructCache[*gtsmodel.Emoji] @@ -548,6 +554,69 @@ func (c *Caches) initDomainBlock() { c.DB.DomainBlock = new(domain.Cache) } +func (c *Caches) initDomainPermissionDraft() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofDomainPermissionDraft(), // model in-mem size. + config.GetCacheDomainPermissionDraftMemRation(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(d1 *gtsmodel.DomainPermissionDraft) *gtsmodel.DomainPermissionDraft { + d2 := new(gtsmodel.DomainPermissionDraft) + *d2 = *d1 + + // Don't include ptr fields that + // will be populated separately. + d2.CreatedByAccount = nil + + return d2 + } + + c.DB.DomainPermissionDraft.Init(structr.CacheConfig[*gtsmodel.DomainPermissionDraft]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "Domain", Multiple: true}, + {Fields: "SubscriptionID", Multiple: true}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + Copy: copyF, + }) +} + +func (c *Caches) initDomainPermissionSubscription() { + // Calculate maximum cache size. + cap := calculateResultCacheMax( + sizeofDomainPermissionSubscription(), // model in-mem size. + config.GetCacheDomainPermissionSubscriptionMemRation(), + ) + + log.Infof(nil, "cache size = %d", cap) + + copyF := func(d1 *gtsmodel.DomainPermissionSubscription) *gtsmodel.DomainPermissionSubscription { + d2 := new(gtsmodel.DomainPermissionSubscription) + *d2 = *d1 + + // Don't include ptr fields that + // will be populated separately. + d2.CreatedByAccount = nil + + return d2 + } + + c.DB.DomainPermissionSubscription.Init(structr.CacheConfig[*gtsmodel.DomainPermissionSubscription]{ + Indices: []structr.IndexConfig{ + {Fields: "ID"}, + {Fields: "URI"}, + }, + MaxSize: cap, + IgnoreErr: ignoreErrors, + Copy: copyF, + }) +} + func (c *Caches) initEmoji() { // Calculate maximum cache size. cap := calculateResultCacheMax( diff --git a/internal/cache/size.go b/internal/cache/size.go index 8367e4c46..5a7688223 100644 --- a/internal/cache/size.go +++ b/internal/cache/size.go @@ -342,6 +342,35 @@ func sizeofConversation() uintptr { })) } +func sizeofDomainPermissionDraft() uintptr { + return uintptr(size.Of(>smodel.DomainPermissionDraft{ + ID: exampleID, + CreatedAt: exampleTime, + UpdatedAt: exampleTime, + PermissionType: gtsmodel.DomainPermissionBlock, + Domain: "example.org", + CreatedByAccountID: exampleID, + PrivateComment: exampleTextSmall, + PublicComment: exampleTextSmall, + Obfuscate: util.Ptr(false), + SubscriptionID: exampleID, + })) +} + +func sizeofDomainPermissionSubscription() uintptr { + return uintptr(size.Of(>smodel.DomainPermissionSubscription{ + ID: exampleID, + CreatedAt: exampleTime, + PermissionType: gtsmodel.DomainPermissionBlock, + CreatedByAccountID: exampleID, + URI: exampleURI, + FetchUsername: "username", + FetchPassword: "password", + FetchedAt: exampleTime, + AsDraft: util.Ptr(true), + })) +} + func sizeofEmoji() uintptr { return uintptr(size.Of(>smodel.Emoji{ ID: exampleID, diff --git a/internal/config/config.go b/internal/config/config.go index 4a40e9c13..8f48f1d86 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -194,58 +194,60 @@ type HTTPClientConfiguration struct { } type CacheConfiguration struct { - MemoryTarget bytesize.Size `name:"memory-target"` - AccountMemRatio float64 `name:"account-mem-ratio"` - AccountNoteMemRatio float64 `name:"account-note-mem-ratio"` - AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"` - AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"` - ApplicationMemRatio float64 `name:"application-mem-ratio"` - BlockMemRatio float64 `name:"block-mem-ratio"` - BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"` - BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"` - ClientMemRatio float64 `name:"client-mem-ratio"` - ConversationMemRatio float64 `name:"conversation-mem-ratio"` - ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"` - EmojiMemRatio float64 `name:"emoji-mem-ratio"` - EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"` - FilterMemRatio float64 `name:"filter-mem-ratio"` - FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"` - FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"` - FollowMemRatio float64 `name:"follow-mem-ratio"` - FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"` - FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"` - FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"` - FollowingTagIDsMemRatio float64 `name:"following-tag-ids-mem-ratio"` - InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"` - InstanceMemRatio float64 `name:"instance-mem-ratio"` - InteractionRequestMemRatio float64 `name:"interaction-request-mem-ratio"` - ListMemRatio float64 `name:"list-mem-ratio"` - ListIDsMemRatio float64 `name:"list-ids-mem-ratio"` - ListedIDsMemRatio float64 `name:"listed-ids-mem-ratio"` - MarkerMemRatio float64 `name:"marker-mem-ratio"` - MediaMemRatio float64 `name:"media-mem-ratio"` - MentionMemRatio float64 `name:"mention-mem-ratio"` - MoveMemRatio float64 `name:"move-mem-ratio"` - NotificationMemRatio float64 `name:"notification-mem-ratio"` - PollMemRatio float64 `name:"poll-mem-ratio"` - PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"` - PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"` - ReportMemRatio float64 `name:"report-mem-ratio"` - SinBinStatusMemRatio float64 `name:"sin-bin-status-mem-ratio"` - StatusMemRatio float64 `name:"status-mem-ratio"` - StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"` - StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"` - StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"` - StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"` - TagMemRatio float64 `name:"tag-mem-ratio"` - ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"` - TokenMemRatio float64 `name:"token-mem-ratio"` - TombstoneMemRatio float64 `name:"tombstone-mem-ratio"` - UserMemRatio float64 `name:"user-mem-ratio"` - UserMuteMemRatio float64 `name:"user-mute-mem-ratio"` - UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"` - WebfingerMemRatio float64 `name:"webfinger-mem-ratio"` - VisibilityMemRatio float64 `name:"visibility-mem-ratio"` + MemoryTarget bytesize.Size `name:"memory-target"` + AccountMemRatio float64 `name:"account-mem-ratio"` + AccountNoteMemRatio float64 `name:"account-note-mem-ratio"` + AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"` + AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"` + ApplicationMemRatio float64 `name:"application-mem-ratio"` + BlockMemRatio float64 `name:"block-mem-ratio"` + BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"` + BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"` + ClientMemRatio float64 `name:"client-mem-ratio"` + ConversationMemRatio float64 `name:"conversation-mem-ratio"` + ConversationLastStatusIDsMemRatio float64 `name:"conversation-last-status-ids-mem-ratio"` + DomainPermissionDraftMemRation float64 `name:"domain-permission-draft-mem-ratio"` + DomainPermissionSubscriptionMemRation float64 `name:"domain-permission-subscription-mem-ratio"` + EmojiMemRatio float64 `name:"emoji-mem-ratio"` + EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"` + FilterMemRatio float64 `name:"filter-mem-ratio"` + FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"` + FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"` + FollowMemRatio float64 `name:"follow-mem-ratio"` + FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"` + FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"` + FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"` + FollowingTagIDsMemRatio float64 `name:"following-tag-ids-mem-ratio"` + InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"` + InstanceMemRatio float64 `name:"instance-mem-ratio"` + InteractionRequestMemRatio float64 `name:"interaction-request-mem-ratio"` + ListMemRatio float64 `name:"list-mem-ratio"` + ListIDsMemRatio float64 `name:"list-ids-mem-ratio"` + ListedIDsMemRatio float64 `name:"listed-ids-mem-ratio"` + MarkerMemRatio float64 `name:"marker-mem-ratio"` + MediaMemRatio float64 `name:"media-mem-ratio"` + MentionMemRatio float64 `name:"mention-mem-ratio"` + MoveMemRatio float64 `name:"move-mem-ratio"` + NotificationMemRatio float64 `name:"notification-mem-ratio"` + PollMemRatio float64 `name:"poll-mem-ratio"` + PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"` + PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"` + ReportMemRatio float64 `name:"report-mem-ratio"` + SinBinStatusMemRatio float64 `name:"sin-bin-status-mem-ratio"` + StatusMemRatio float64 `name:"status-mem-ratio"` + StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"` + StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"` + StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"` + StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"` + TagMemRatio float64 `name:"tag-mem-ratio"` + ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"` + TokenMemRatio float64 `name:"token-mem-ratio"` + TombstoneMemRatio float64 `name:"tombstone-mem-ratio"` + UserMemRatio float64 `name:"user-mem-ratio"` + UserMuteMemRatio float64 `name:"user-mute-mem-ratio"` + UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"` + WebfingerMemRatio float64 `name:"webfinger-mem-ratio"` + VisibilityMemRatio float64 `name:"visibility-mem-ratio"` } // MarshalMap will marshal current Configuration into a map structure (useful for JSON/TOML/YAML). diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 48d880e1b..827822635 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -158,57 +158,59 @@ // when TODO items in the size.go source // file have been addressed, these should // be able to make some more sense :D - AccountMemRatio: 5, - AccountNoteMemRatio: 1, - AccountSettingsMemRatio: 0.1, - AccountStatsMemRatio: 2, - ApplicationMemRatio: 0.1, - BlockMemRatio: 2, - BlockIDsMemRatio: 3, - BoostOfIDsMemRatio: 3, - ClientMemRatio: 0.1, - ConversationMemRatio: 1, - ConversationLastStatusIDsMemRatio: 2, - EmojiMemRatio: 3, - EmojiCategoryMemRatio: 0.1, - FilterMemRatio: 0.5, - FilterKeywordMemRatio: 0.5, - FilterStatusMemRatio: 0.5, - FollowMemRatio: 2, - FollowIDsMemRatio: 4, - FollowRequestMemRatio: 2, - FollowRequestIDsMemRatio: 2, - FollowingTagIDsMemRatio: 2, - InReplyToIDsMemRatio: 3, - InstanceMemRatio: 1, - InteractionRequestMemRatio: 1, - ListMemRatio: 1, - ListIDsMemRatio: 2, - ListedIDsMemRatio: 2, - MarkerMemRatio: 0.5, - MediaMemRatio: 4, - MentionMemRatio: 2, - MoveMemRatio: 0.1, - NotificationMemRatio: 2, - PollMemRatio: 1, - PollVoteMemRatio: 2, - PollVoteIDsMemRatio: 2, - ReportMemRatio: 1, - SinBinStatusMemRatio: 0.5, - StatusMemRatio: 5, - StatusBookmarkMemRatio: 0.5, - StatusBookmarkIDsMemRatio: 2, - StatusFaveMemRatio: 2, - StatusFaveIDsMemRatio: 3, - TagMemRatio: 2, - ThreadMuteMemRatio: 0.2, - TokenMemRatio: 0.75, - TombstoneMemRatio: 0.5, - UserMemRatio: 0.25, - UserMuteMemRatio: 2, - UserMuteIDsMemRatio: 3, - WebfingerMemRatio: 0.1, - VisibilityMemRatio: 2, + AccountMemRatio: 5, + AccountNoteMemRatio: 1, + AccountSettingsMemRatio: 0.1, + AccountStatsMemRatio: 2, + ApplicationMemRatio: 0.1, + BlockMemRatio: 2, + BlockIDsMemRatio: 3, + BoostOfIDsMemRatio: 3, + ClientMemRatio: 0.1, + ConversationMemRatio: 1, + ConversationLastStatusIDsMemRatio: 2, + DomainPermissionDraftMemRation: 0.5, + DomainPermissionSubscriptionMemRation: 0.5, + EmojiMemRatio: 3, + EmojiCategoryMemRatio: 0.1, + FilterMemRatio: 0.5, + FilterKeywordMemRatio: 0.5, + FilterStatusMemRatio: 0.5, + FollowMemRatio: 2, + FollowIDsMemRatio: 4, + FollowRequestMemRatio: 2, + FollowRequestIDsMemRatio: 2, + FollowingTagIDsMemRatio: 2, + InReplyToIDsMemRatio: 3, + InstanceMemRatio: 1, + InteractionRequestMemRatio: 1, + ListMemRatio: 1, + ListIDsMemRatio: 2, + ListedIDsMemRatio: 2, + MarkerMemRatio: 0.5, + MediaMemRatio: 4, + MentionMemRatio: 2, + MoveMemRatio: 0.1, + NotificationMemRatio: 2, + PollMemRatio: 1, + PollVoteMemRatio: 2, + PollVoteIDsMemRatio: 2, + ReportMemRatio: 1, + SinBinStatusMemRatio: 0.5, + StatusMemRatio: 5, + StatusBookmarkMemRatio: 0.5, + StatusBookmarkIDsMemRatio: 2, + StatusFaveMemRatio: 2, + StatusFaveIDsMemRatio: 3, + TagMemRatio: 2, + ThreadMuteMemRatio: 0.2, + TokenMemRatio: 0.75, + TombstoneMemRatio: 0.5, + UserMemRatio: 0.25, + UserMuteMemRatio: 2, + UserMuteIDsMemRatio: 3, + WebfingerMemRatio: 0.1, + VisibilityMemRatio: 2, }, HTTPClient: HTTPClientConfiguration{ diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index d25d6cca8..abc98b251 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -3106,6 +3106,68 @@ func SetCacheConversationLastStatusIDsMemRatio(v float64) { global.SetCacheConversationLastStatusIDsMemRatio(v) } +// GetCacheDomainPermissionDraftMemRation safely fetches the Configuration value for state's 'Cache.DomainPermissionDraftMemRation' field +func (st *ConfigState) GetCacheDomainPermissionDraftMemRation() (v float64) { + st.mutex.RLock() + v = st.config.Cache.DomainPermissionDraftMemRation + st.mutex.RUnlock() + return +} + +// SetCacheDomainPermissionDraftMemRation safely sets the Configuration value for state's 'Cache.DomainPermissionDraftMemRation' field +func (st *ConfigState) SetCacheDomainPermissionDraftMemRation(v float64) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.Cache.DomainPermissionDraftMemRation = v + st.reloadToViper() +} + +// CacheDomainPermissionDraftMemRationFlag returns the flag name for the 'Cache.DomainPermissionDraftMemRation' field +func CacheDomainPermissionDraftMemRationFlag() string { + return "cache-domain-permission-draft-mem-ratio" +} + +// GetCacheDomainPermissionDraftMemRation safely fetches the value for global configuration 'Cache.DomainPermissionDraftMemRation' field +func GetCacheDomainPermissionDraftMemRation() float64 { + return global.GetCacheDomainPermissionDraftMemRation() +} + +// SetCacheDomainPermissionDraftMemRation safely sets the value for global configuration 'Cache.DomainPermissionDraftMemRation' field +func SetCacheDomainPermissionDraftMemRation(v float64) { + global.SetCacheDomainPermissionDraftMemRation(v) +} + +// GetCacheDomainPermissionSubscriptionMemRation safely fetches the Configuration value for state's 'Cache.DomainPermissionSubscriptionMemRation' field +func (st *ConfigState) GetCacheDomainPermissionSubscriptionMemRation() (v float64) { + st.mutex.RLock() + v = st.config.Cache.DomainPermissionSubscriptionMemRation + st.mutex.RUnlock() + return +} + +// SetCacheDomainPermissionSubscriptionMemRation safely sets the Configuration value for state's 'Cache.DomainPermissionSubscriptionMemRation' field +func (st *ConfigState) SetCacheDomainPermissionSubscriptionMemRation(v float64) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.Cache.DomainPermissionSubscriptionMemRation = v + st.reloadToViper() +} + +// CacheDomainPermissionSubscriptionMemRationFlag returns the flag name for the 'Cache.DomainPermissionSubscriptionMemRation' field +func CacheDomainPermissionSubscriptionMemRationFlag() string { + return "cache-domain-permission-subscription-mem-ratio" +} + +// GetCacheDomainPermissionSubscriptionMemRation safely fetches the value for global configuration 'Cache.DomainPermissionSubscriptionMemRation' field +func GetCacheDomainPermissionSubscriptionMemRation() float64 { + return global.GetCacheDomainPermissionSubscriptionMemRation() +} + +// SetCacheDomainPermissionSubscriptionMemRation safely sets the value for global configuration 'Cache.DomainPermissionSubscriptionMemRation' field +func SetCacheDomainPermissionSubscriptionMemRation(v float64) { + global.SetCacheDomainPermissionSubscriptionMemRation(v) +} + // GetCacheEmojiMemRatio safely fetches the Configuration value for state's 'Cache.EmojiMemRatio' field func (st *ConfigState) GetCacheEmojiMemRatio() (v float64) { st.mutex.RLock() diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go index 0d2a13b34..1350c115c 100644 --- a/internal/db/bundb/domain.go +++ b/internal/db/bundb/domain.go @@ -19,12 +19,17 @@ import ( "context" + "errors" "net/url" + "slices" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/superseriousbusiness/gotosocial/internal/paging" "github.com/superseriousbusiness/gotosocial/internal/state" "github.com/superseriousbusiness/gotosocial/internal/util" "github.com/uptrace/bun" @@ -328,3 +333,469 @@ func (d *domainDB) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, e } return false, nil } + +func (d *domainDB) getDomainPermissionDraft( + ctx context.Context, + lookup string, + dbQuery func(*gtsmodel.DomainPermissionDraft) error, + keyParts ...any, +) (*gtsmodel.DomainPermissionDraft, error) { + // Fetch perm draft from database cache with loader callback. + permDraft, err := d.state.Caches.DB.DomainPermissionDraft.LoadOne( + lookup, + // Only called if not cached. + func() (*gtsmodel.DomainPermissionDraft, error) { + var permDraft gtsmodel.DomainPermissionDraft + if err := dbQuery(&permDraft); err != nil { + return nil, err + } + return &permDraft, nil + }, + keyParts..., + ) + if err != nil { + return nil, err + } + + if gtscontext.Barebones(ctx) { + // No need to fully populate. + return permDraft, nil + } + + if permDraft.CreatedByAccount == nil { + // Not set, fetch from database. + permDraft.CreatedByAccount, err = d.state.DB.GetAccountByID( + gtscontext.SetBarebones(ctx), + permDraft.CreatedByAccountID, + ) + if err != nil { + return nil, gtserror.Newf("error populating created by account: %w", err) + } + } + + return permDraft, nil +} + +func (d *domainDB) GetDomainPermissionDraftByID( + ctx context.Context, + id string, +) (*gtsmodel.DomainPermissionDraft, error) { + return d.getDomainPermissionDraft( + ctx, + "ID", + func(permDraft *gtsmodel.DomainPermissionDraft) error { + return d.db. + NewSelect(). + Model(permDraft). + Where("? = ?", bun.Ident("domain_permission_draft.id"), id). + Scan(ctx) + }, + id, + ) +} + +func (d *domainDB) GetDomainPermissionDrafts( + ctx context.Context, + permType *gtsmodel.DomainPermissionType, + permSubID string, + domain string, + page *paging.Page, +) ( + []*gtsmodel.DomainPermissionDraft, + error, +) { + var ( + // Get paging params. + minID = page.GetMin() + maxID = page.GetMax() + limit = page.GetLimit() + order = page.GetOrder() + + // Make educated guess for slice size + permDraftIDs = make([]string, 0, limit) + ) + + q := d.db. + NewSelect(). + TableExpr( + "? AS ?", + bun.Ident("domain_permission_drafts"), + bun.Ident("domain_permission_draft"), + ). + // Select only IDs from table + Column("domain_permission_draft.id") + + // Return only items with id + // lower than provided maxID. + if maxID != "" { + q = q.Where( + "? < ?", + bun.Ident("domain_permission_draft.id"), + maxID, + ) + } + + // Return only items with id + // greater than provided minID. + if minID != "" { + q = q.Where( + "? > ?", + bun.Ident("domain_permission_draft.id"), + minID, + ) + } + + // Return only items with + // given subscription ID. + if permType != nil { + q = q.Where( + "? = ?", + bun.Ident("domain_permission_draft.permission_type"), + *permType, + ) + } + + // Return only items with + // given subscription ID. + if permSubID != "" { + q = q.Where( + "? = ?", + bun.Ident("domain_permission_draft.subscription_id"), + permSubID, + ) + } + + // Return only items + // with given domain. + if domain != "" { + var err error + + // Normalize domain as punycode. + domain, err = util.Punify(domain) + if err != nil { + return nil, gtserror.Newf("error punifying domain %s: %w", domain, err) + } + + q = q.Where( + "? = ?", + bun.Ident("domain_permission_draft.domain"), + domain, + ) + } + + if limit > 0 { + // Limit amount of + // items returned. + q = q.Limit(limit) + } + + if order == paging.OrderAscending { + // Page up. + q = q.OrderExpr( + "? ASC", + bun.Ident("domain_permission_draft.id"), + ) + } else { + // Page down. + q = q.OrderExpr( + "? DESC", + bun.Ident("domain_permission_draft.id"), + ) + } + + if err := q.Scan(ctx, &permDraftIDs); err != nil { + return nil, err + } + + // Catch case of no items early + if len(permDraftIDs) == 0 { + return nil, db.ErrNoEntries + } + + // If we're paging up, we still want items + // to be sorted by ID desc, so reverse slice. + if order == paging.OrderAscending { + slices.Reverse(permDraftIDs) + } + + // Allocate return slice (will be at most len permDraftIDs) + permDrafts := make([]*gtsmodel.DomainPermissionDraft, 0, len(permDraftIDs)) + for _, id := range permDraftIDs { + permDraft, err := d.GetDomainPermissionDraftByID(ctx, id) + if err != nil { + log.Errorf(ctx, "error getting domain permission draft %q: %v", id, err) + continue + } + + // Append to return slice + permDrafts = append(permDrafts, permDraft) + } + + return permDrafts, nil +} + +func (d *domainDB) PutDomainPermissionDraft( + ctx context.Context, + permDraft *gtsmodel.DomainPermissionDraft, +) error { + var err error + + // Normalize the domain as punycode + permDraft.Domain, err = util.Punify(permDraft.Domain) + if err != nil { + return gtserror.Newf("error punifying domain %s: %w", permDraft.Domain, err) + } + + return d.state.Caches.DB.DomainPermissionDraft.Store( + permDraft, + func() error { + _, err := d.db. + NewInsert(). + Model(permDraft). + Exec(ctx) + return err + }, + ) +} + +func (d *domainDB) DeleteDomainPermissionDraft( + ctx context.Context, + id string, +) error { + // Delete the permDraft from DB. + q := d.db.NewDelete(). + TableExpr( + "? AS ?", + bun.Ident("domain_permission_drafts"), + bun.Ident("domain_permission_draft"), + ). + Where( + "? = ?", + bun.Ident("domain_permission_draft.id"), + id, + ) + + _, err := q.Exec(ctx) + if err != nil && !errors.Is(err, db.ErrNoEntries) { + return err + } + + // Invalidate any cached model by ID. + d.state.Caches.DB.DomainPermissionDraft.Invalidate("ID", id) + + return nil +} + +func (d *domainDB) getDomainPermissionSubscription( + ctx context.Context, + lookup string, + dbQuery func(*gtsmodel.DomainPermissionSubscription) error, + keyParts ...any, +) (*gtsmodel.DomainPermissionSubscription, error) { + // Fetch perm subscription from database cache with loader callback. + permDraft, err := d.state.Caches.DB.DomainPermissionSubscription.LoadOne( + lookup, + // Only called if not cached. + func() (*gtsmodel.DomainPermissionSubscription, error) { + var permDraft gtsmodel.DomainPermissionSubscription + if err := dbQuery(&permDraft); err != nil { + return nil, err + } + return &permDraft, nil + }, + keyParts..., + ) + if err != nil { + return nil, err + } + + if gtscontext.Barebones(ctx) { + // No need to fully populate. + return permDraft, nil + } + + if permDraft.CreatedByAccount == nil { + // Not set, fetch from database. + permDraft.CreatedByAccount, err = d.state.DB.GetAccountByID( + gtscontext.SetBarebones(ctx), + permDraft.CreatedByAccountID, + ) + if err != nil { + return nil, gtserror.Newf("error populating created by account: %w", err) + } + } + + return permDraft, nil +} + +func (d *domainDB) GetDomainPermissionSubscriptionByID( + ctx context.Context, + id string, +) (*gtsmodel.DomainPermissionSubscription, error) { + return d.getDomainPermissionSubscription( + ctx, + "ID", + func(permDraft *gtsmodel.DomainPermissionSubscription) error { + return d.db. + NewSelect(). + Model(permDraft). + Where("? = ?", bun.Ident("domain_permission_subscription.id"), id). + Scan(ctx) + }, + id, + ) +} + +func (d *domainDB) GetDomainPermissionSubscriptions( + ctx context.Context, + permType *gtsmodel.DomainPermissionType, + page *paging.Page, +) ( + []*gtsmodel.DomainPermissionSubscription, + error, +) { + var ( + // Get paging params. + minID = page.GetMin() + maxID = page.GetMax() + limit = page.GetLimit() + order = page.GetOrder() + + // Make educated guess for slice size + permSubIDs = make([]string, 0, limit) + ) + + q := d.db. + NewSelect(). + TableExpr( + "? AS ?", + bun.Ident("domain_permission_subscriptions"), + bun.Ident("domain_permission_subscription"), + ). + // Select only IDs from table + Column("domain_permission_subscription.id") + + // Return only items with id + // lower than provided maxID. + if maxID != "" { + q = q.Where( + "? < ?", + bun.Ident("domain_permission_subscription.id"), + maxID, + ) + } + + // Return only items with id + // greater than provided minID. + if minID != "" { + q = q.Where( + "? > ?", + bun.Ident("domain_permission_subscription.id"), + minID, + ) + } + + // Return only items with + // given subscription ID. + if permType != nil { + q = q.Where( + "? = ?", + bun.Ident("domain_permission_subscription.permission_type"), + *permType, + ) + } + + if limit > 0 { + // Limit amount of + // items returned. + q = q.Limit(limit) + } + + if order == paging.OrderAscending { + // Page up. + q = q.OrderExpr( + "? ASC", + bun.Ident("domain_permission_subscription.id"), + ) + } else { + // Page down. + q = q.OrderExpr( + "? DESC", + bun.Ident("domain_permission_subscription.id"), + ) + } + + if err := q.Scan(ctx, &permSubIDs); err != nil { + return nil, err + } + + // Catch case of no items early + if len(permSubIDs) == 0 { + return nil, db.ErrNoEntries + } + + // If we're paging up, we still want items + // to be sorted by ID desc, so reverse slice. + if order == paging.OrderAscending { + slices.Reverse(permSubIDs) + } + + // Allocate return slice (will be at most len permSubIDs). + permSubs := make([]*gtsmodel.DomainPermissionSubscription, 0, len(permSubIDs)) + for _, id := range permSubIDs { + permDraft, 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, permDraft) + } + + 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..929529ced --- /dev/null +++ b/internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions.go @@ -0,0 +1,85 @@ +// 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_drafts`. + if _, err := tx. + NewCreateTable(). + Model((*gtsmodel.DomainPermissionDraft)(nil)). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + + // Create `domain_permission_subscriptions`. + if _, err := tx. + NewCreateTable(). + Model((*gtsmodel.DomainPermissionSubscription)(nil)). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + + // Create indexes. Indices. Indie sexes. + for table, indexes := range map[string]map[string][]string{ + "domain_permission_drafts": { + "domain_permission_drafts_domain_idx": {"domain"}, + "domain_permission_drafts_subscription_id_idx": {"subscription_id"}, + }, + "domain_permission_subscriptions": { + "domain_permission_subscriptions_permission_type_idx": {"permission_type"}, + }, + } { + for index, columns := range indexes { + if _, err := tx. + NewCreateIndex(). + Table(table). + Index(index). + Column(columns...). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + } + } + + return nil + }) + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} diff --git a/internal/db/domain.go b/internal/db/domain.go index 3f7803d62..0b9e56942 100644 --- a/internal/db/domain.go +++ b/internal/db/domain.go @@ -22,6 +22,7 @@ "net/url" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/paging" ) // Domain contains DB functions related to domains and domain blocks. @@ -78,4 +79,48 @@ type Domain interface { // AreURIsBlocked calls IsURIBlocked for each URI. // Will return true if even one of the given URIs is blocked. AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, error) + + /* + Domain permission draft stuff. + */ + + // GetDomainPermissionDraftByID gets one DomainPermissionDraft with the given ID. + GetDomainPermissionDraftByID(ctx context.Context, id string) (*gtsmodel.DomainPermissionDraft, error) + + // GetDomainPermissionDrafts returns a page of + // DomainPermissionDrafts using the given parameters. + GetDomainPermissionDrafts( + ctx context.Context, + permType *gtsmodel.DomainPermissionType, + permSubID string, + domain string, + page *paging.Page, + ) ([]*gtsmodel.DomainPermissionDraft, error) + + // PutDomainPermissionDraft stores one DomainPermissionDraft. + PutDomainPermissionDraft(ctx context.Context, permDraft *gtsmodel.DomainPermissionDraft) error + + // DeleteDomainPermissionDraft deletes one DomainPermissionDraft with the given id. + DeleteDomainPermissionDraft(ctx context.Context, id string) error + + /* + Domain permission 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/domainpermissiondraft.go b/internal/gtsmodel/domainpermissiondraft.go new file mode 100644 index 000000000..aa066a5db --- /dev/null +++ b/internal/gtsmodel/domainpermissiondraft.go @@ -0,0 +1,34 @@ +// 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 DomainPermissionDraft struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created. + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was last updated. + PermissionType DomainPermissionType `bun:",notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // Permission type of the draft. + Domain string `bun:",nullzero,notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // Domain to block or allow. Eg. 'whatever.com'. + CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription. + CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID. + PrivateComment string `bun:""` // Private comment on this perm, viewable to admins. + PublicComment string `bun:""` // Public comment on this perm, viewable (optionally) by everyone. + Obfuscate *bool `bun:",nullzero,notnull,default:false"` // Obfuscate domain name when displaying it publicly. + SubscriptionID string `bun:"type:CHAR(26),nullzero,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"` // ID of the subscription that created this draft, if any. +} diff --git a/internal/gtsmodel/domainpermissionsubscription.go b/internal/gtsmodel/domainpermissionsubscription.go new file mode 100644 index 000000000..813d906da --- /dev/null +++ b/internal/gtsmodel/domainpermissionsubscription.go @@ -0,0 +1,38 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package gtsmodel + +import "time" + +type DomainPermissionSubscription struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database. + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Time when this item was created. + Title string `bun:",nullzero"` // Moderator-set title for this list. + PermissionType DomainPermissionType `bun:",notnull"` // Permission type of the subscription. + AsDraft *bool `bun:",nullzero,notnull,default:true"` // Create domain permission entries resulting from this subscription as drafts. + CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this subscription. + CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID. + ContentType string `bun:",nullzero,notnull"` // Content type to expect from the URI. + URI string `bun:",unique,nullzero,notnull"` // URI of the domain permission list. + FetchUsername string `bun:",nullzero"` // Username to send when doing a GET of URI using basic auth. + FetchPassword string `bun:",nullzero"` // Password to send when doing a GET of URI using basic auth. + FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // Time when fetch of URI was last attempted. + IsError *bool `bun:",nullzero,notnull,default:false"` // True if last fetch attempt of URI resulted in an error. + Error string `bun:",nullzero"` // If IsError=true, this field contains the error resulting from the attempted fetch. + Count uint64 `bun:""` // Count of domain permission entries discovered at URI. +}