From 3c2362e88584260d585dd0dcd3fcafa9ba4780b4 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:51:01 +0200 Subject: [PATCH] Add submission blacklist support Introduces feature- and target-scoped submission blacklist storage with gallery and integrations support, database wiring, audit resources, and guild purge exemptions. Also improves row cleanup and error propagation in blacklist listing methods. --- auditlog.go | 4 ++ database.go | 3 + globalblacklist.go | 2 + guildpurge.go | 7 ++- serverblacklist.go | 6 +- submissionblacklist.go | 125 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 submissionblacklist.go diff --git a/auditlog.go b/auditlog.go index f243528..6aaa553 100644 --- a/auditlog.go +++ b/auditlog.go @@ -155,6 +155,9 @@ const ( AuditActionFeatureFlagToggle AuditActionType = 440 AuditActionFeatureFlagCreate AuditActionType = 441 AuditActionFeatureFlagRulesUpdate AuditActionType = 442 + + AuditActionSubmissionBlacklistAdd AuditActionType = 450 + AuditActionSubmissionBlacklistRemove AuditActionType = 451 ) type AuditCategory int16 @@ -206,6 +209,7 @@ const ( AuditResourceNotification AuditResourceType = 36 AuditResourceUserEmailVerification AuditResourceType = 37 AuditResourceFeatureFlag AuditResourceType = 38 + AuditResourceSubmissionBlacklist AuditResourceType = 39 ) type AuditLogEntry struct { diff --git a/database.go b/database.go index 87f16bb..9485327 100644 --- a/database.go +++ b/database.go @@ -93,6 +93,7 @@ type Database struct { Settings *SettingsTable Skus *Skus StaffOverride *StaffOverride + SubmissionBlacklist *SubmissionBlacklist SubscriptionSkus *SubscriptionSkus SupportTeam *SupportTeamTable SupportTeamMembers *SupportTeamMembersTable @@ -204,6 +205,7 @@ func NewDatabase(pool *pgxpool.Pool) *Database { Settings: newSettingsTable(pool), Skus: newSkusTable(pool), StaffOverride: newStaffOverride(pool), + SubmissionBlacklist: newSubmissionBlacklist(pool), SubscriptionSkus: newSubscriptionSkusTable(pool), SupportTeam: newSupportTeamTable(pool), SupportTeamMembers: newSupportTeamMembersTable(pool), @@ -320,6 +322,7 @@ func (d *Database) CreateTables(ctx context.Context, pool *pgxpool.Pool) { d.ServerBlacklist, d.Settings, d.StaffOverride, + d.SubmissionBlacklist, d.SupportTeam, d.SupportTeamMembers, d.SupportTeamRoles, diff --git a/globalblacklist.go b/globalblacklist.go index 0e0ffcb..406bd8e 100644 --- a/globalblacklist.go +++ b/globalblacklist.go @@ -50,6 +50,7 @@ func (b *GlobalBlacklist) ListAll(ctx context.Context) (users []uint64, err erro if err != nil { return } + defer rows.Close() for rows.Next() { var userId uint64 @@ -60,6 +61,7 @@ func (b *GlobalBlacklist) ListAll(ctx context.Context) (users []uint64, err erro users = append(users, userId) } + err = rows.Err() return } diff --git a/guildpurge.go b/guildpurge.go index 6f86424..7f45cbe 100644 --- a/guildpurge.go +++ b/guildpurge.go @@ -102,9 +102,10 @@ var guildPurgeLegacy = []string{ // Guild-scoped but deliberately retained; keeps the drift test honest. var guildPurgeExempt = map[string]string{ - "guild_leave_time": "drives the purge itself; the caller deletes the row once the purge succeeds", - "entitlements": "billing record, retained beyond the guild", - "server_blacklist": "global ban list; purging it would silently un-ban the guild", + "guild_leave_time": "drives the purge itself; the caller deletes the row once the purge succeeds", + "entitlements": "billing record, retained beyond the guild", + "server_blacklist": "global ban list; purging it would silently un-ban the guild", + "submission_blacklist": "gallery ban list; purging it would silently un-ban the guild", } // PurgeGuildData deletes all data associated with a guild from all tables. diff --git a/serverblacklist.go b/serverblacklist.go index fc4f852..646eb57 100644 --- a/serverblacklist.go +++ b/serverblacklist.go @@ -79,6 +79,7 @@ func (b *ServerBlacklist) ListAll(ctx context.Context) ([]uint64, error) { if err != nil { return nil, err } + defer rows.Close() var guilds []uint64 for rows.Next() { @@ -90,7 +91,7 @@ func (b *ServerBlacklist) ListAll(ctx context.Context) ([]uint64, error) { guilds = append(guilds, guildId) } - return guilds, nil + return guilds, rows.Err() } func (b *ServerBlacklist) Add(ctx context.Context, guildId uint64, reason *string, ownerId *uint64, realOwnerId *uint64) (err error) { @@ -110,6 +111,7 @@ func (b *ServerBlacklist) ListAllEntries(ctx context.Context) ([]ServerBlacklist if err != nil { return nil, err } + defer rows.Close() var entries []ServerBlacklistEntry for rows.Next() { @@ -119,6 +121,6 @@ func (b *ServerBlacklist) ListAllEntries(ctx context.Context) ([]ServerBlacklist } entries = append(entries, entry) } - return entries, nil + return entries, rows.Err() } diff --git a/submissionblacklist.go b/submissionblacklist.go new file mode 100644 index 0000000..7d364a4 --- /dev/null +++ b/submissionblacklist.go @@ -0,0 +1,125 @@ +package database + +import ( + "context" + "errors" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +type SubmissionFeature string + +const ( + SubmissionFeatureGallery SubmissionFeature = "gallery" + SubmissionFeatureIntegrations SubmissionFeature = "integrations" +) + +type SubmissionTargetType string + +const ( + SubmissionTargetUser SubmissionTargetType = "user" + SubmissionTargetGuild SubmissionTargetType = "guild" +) + +func (f SubmissionFeature) AllowsTarget(t SubmissionTargetType) bool { + switch f { + case SubmissionFeatureGallery: + return t == SubmissionTargetUser || t == SubmissionTargetGuild + case SubmissionFeatureIntegrations: + return t == SubmissionTargetUser + default: + return false + } +} + +type SubmissionBlacklistEntry struct { + TargetType SubmissionTargetType + TargetId uint64 + Reason *string +} + +type SubmissionBlacklist struct { + *pgxpool.Pool +} + +func newSubmissionBlacklist(db *pgxpool.Pool) *SubmissionBlacklist { + return &SubmissionBlacklist{ + db, + } +} + +func (b SubmissionBlacklist) Schema() string { + return ` +CREATE TABLE IF NOT EXISTS submission_blacklist( + "feature" TEXT NOT NULL, + "target_type" TEXT NOT NULL, + "target_id" int8 NOT NULL, + "reason" TEXT, + PRIMARY KEY("feature", "target_type", "target_id") +); +` +} + +func (b *SubmissionBlacklist) IsBlacklisted(ctx context.Context, feature SubmissionFeature, userId, guildId uint64) (bool, SubmissionTargetType, error) { + query := ` +SELECT "target_type" FROM submission_blacklist +WHERE "feature" = $1 +AND (("target_type" = 'user' AND "target_id" = $2) OR ("target_type" = 'guild' AND "target_id" = $3)) +ORDER BY "target_type" = 'user' DESC +LIMIT 1; +` + + var targetType SubmissionTargetType + if err := b.QueryRow(ctx, query, feature, userId, guildId).Scan(&targetType); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return false, "", nil + } + return false, "", err + } + + return true, targetType, nil +} + +func (b *SubmissionBlacklist) List(ctx context.Context, feature SubmissionFeature) ([]SubmissionBlacklistEntry, error) { + query := ` +SELECT "target_type", "target_id", "reason" +FROM submission_blacklist +WHERE "feature" = $1 +ORDER BY "target_type", "target_id"; +` + + rows, err := b.Query(ctx, query, feature) + if err != nil { + return nil, err + } + defer rows.Close() + + var entries []SubmissionBlacklistEntry + for rows.Next() { + var entry SubmissionBlacklistEntry + if err := rows.Scan(&entry.TargetType, &entry.TargetId, &entry.Reason); err != nil { + return nil, err + } + + entries = append(entries, entry) + } + + return entries, rows.Err() +} + +func (b *SubmissionBlacklist) Add(ctx context.Context, feature SubmissionFeature, targetType SubmissionTargetType, targetId uint64, reason *string) (err error) { + query := ` +INSERT INTO submission_blacklist("feature", "target_type", "target_id", "reason") +VALUES($1, $2, $3, $4) +ON CONFLICT("feature", "target_type", "target_id") DO UPDATE SET "reason" = EXCLUDED."reason"; +` + + _, err = b.Exec(ctx, query, feature, targetType, targetId, reason) + return +} + +func (b *SubmissionBlacklist) Delete(ctx context.Context, feature SubmissionFeature, targetType SubmissionTargetType, targetId uint64) (err error) { + _, err = b.Exec(ctx, `DELETE FROM submission_blacklist WHERE "feature" = $1 AND "target_type" = $2 AND "target_id" = $3;`, feature, targetType, targetId) + return +}