diff --git a/go.mod b/go.mod
index 999068087..391d8c126 100644
--- a/go.mod
+++ b/go.mod
@@ -28,7 +28,6 @@ require (
 	github.com/gin-gonic/gin v1.9.1
 	github.com/go-fed/httpsig v1.1.0
 	github.com/go-playground/form/v4 v4.2.1
-	github.com/go-playground/validator/v10 v10.14.1
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/feeds v1.1.1
 	github.com/gorilla/websocket v1.5.0
@@ -108,6 +107,7 @@ require (
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.14.1 // indirect
 	github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/godbus/dbus/v5 v5.0.4 // indirect
diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go
index bce9065c1..7b27f076a 100644
--- a/internal/gtsmodel/account.go
+++ b/internal/gtsmodel/account.go
@@ -32,55 +32,55 @@ import (
 
 // Account represents either a local or a remote fediverse account, gotosocial or otherwise (mastodon, pleroma, etc).
 type Account struct {
-	ID                      string           `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                               // id of this item in the database
-	CreatedAt               time.Time        `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                        // when was item created.
-	UpdatedAt               time.Time        `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                        // when was item was last updated.
-	FetchedAt               time.Time        `validate:"required_with=Domain" bun:"type:timestamptz,nullzero"`                                                       // when was item (remote) last fetched.
-	Username                string           `validate:"required" bun:",nullzero,notnull,unique:usernamedomain"`                                                     // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other
-	Domain                  string           `validate:"omitempty,fqdn" bun:",nullzero,unique:usernamedomain"`                                                       // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username.
-	AvatarMediaAttachmentID string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // Database ID of the media attachment, if present
-	AvatarMediaAttachment   *MediaAttachment `validate:"-" bun:"rel:belongs-to"`                                                                                     // MediaAttachment corresponding to avatarMediaAttachmentID
-	AvatarRemoteURL         string           `validate:"omitempty,url" bun:",nullzero"`                                                                              // For a non-local account, where can the header be fetched?
-	HeaderMediaAttachmentID string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // Database ID of the media attachment, if present
-	HeaderMediaAttachment   *MediaAttachment `validate:"-" bun:"rel:belongs-to"`                                                                                     // MediaAttachment corresponding to headerMediaAttachmentID
-	HeaderRemoteURL         string           `validate:"omitempty,url" bun:",nullzero"`                                                                              // For a non-local account, where can the header be fetched?
-	DisplayName             string           `validate:"-" bun:""`                                                                                                   // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
-	EmojiIDs                []string         `validate:"dive,ulid" bun:"emojis,array"`                                                                               // Database IDs of any emojis used in this account's bio, display name, etc
-	Emojis                  []*Emoji         `validate:"-" bun:"attached_emojis,m2m:account_to_emojis"`                                                              // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
-	Fields                  []*Field         `validate:"-"`                                                                                                          // A slice of of fields that this account has added to their profile.
-	FieldsRaw               []*Field         `validate:"-"`                                                                                                          // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target
-	Note                    string           `validate:"-" bun:""`                                                                                                   // A note that this account has on their profile (ie., the account's bio/description of themselves)
-	NoteRaw                 string           `validate:"-" bun:""`                                                                                                   // The raw contents of .Note without conversion to HTML, only available when requester = target
-	Memorial                *bool            `validate:"-" bun:",default:false"`                                                                                     // Is this a memorial account, ie., has the user passed away?
-	AlsoKnownAs             string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // This account is associated with x account id (TODO: migrate to be AlsoKnownAsID)
-	MovedToAccountID        string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // This account has moved this account id in the database
-	Bot                     *bool            `validate:"-" bun:",default:false"`                                                                                     // Does this account identify itself as a bot?
-	Reason                  string           `validate:"-" bun:""`                                                                                                   // What reason was given for signing up when this account was created?
-	Locked                  *bool            `validate:"-" bun:",default:true"`                                                                                      // Does this account need an approval for new followers?
-	Discoverable            *bool            `validate:"-" bun:",default:false"`                                                                                     // Should this account be shown in the instance's profile directory?
-	Privacy                 Visibility       `validate:"required_without=Domain,omitempty,oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero"` // Default post privacy for this account
-	Sensitive               *bool            `validate:"-" bun:",default:false"`                                                                                     // Set posts from this account to sensitive by default?
-	Language                string           `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"`                                          // What language does this account post in?
-	StatusContentType       string           `validate:"required_without=Domain,omitempty,oneof=text/plain text/markdown" bun:",nullzero"`                           // What is the default format for statuses posted by this account (only for local accounts).
-	CustomCSS               string           `validate:"-" bun:",nullzero"`                                                                                          // Custom CSS that should be displayed for this Account's profile and statuses.
-	URI                     string           `validate:"required,url" bun:",nullzero,notnull,unique"`                                                                // ActivityPub URI for this account.
-	URL                     string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // Web URL for this account's profile
-	InboxURI                string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // Address of this account's ActivityPub inbox, for sending activity to
-	SharedInboxURI          *string          `validate:"-" bun:""`                                                                                                   // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string.
-	OutboxURI               string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // Address of this account's activitypub outbox
-	FollowingURI            string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // URI for getting the following list of this account
-	FollowersURI            string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // URI for getting the followers list of this account
-	FeaturedCollectionURI   string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // URL for getting the featured collection list of this account
-	ActorType               string           `validate:"oneof=Application Group Organization Person Service" bun:",nullzero,notnull"`                                // What type of activitypub actor is this account?
-	PrivateKey              *rsa.PrivateKey  `validate:"required_without=Domain" bun:""`                                                                             // Privatekey for validating activitypub requests, will only be defined for local accounts
-	PublicKey               *rsa.PublicKey   `validate:"required" bun:",notnull"`                                                                                    // Publickey for encoding activitypub requests, will be defined for both local and remote accounts
-	PublicKeyURI            string           `validate:"required,url" bun:",nullzero,notnull,unique"`                                                                // Web-reachable location of this account's public key
-	SensitizedAt            time.Time        `validate:"-" bun:"type:timestamptz,nullzero"`                                                                          // When was this account set to have all its media shown as sensitive?
-	SilencedAt              time.Time        `validate:"-" bun:"type:timestamptz,nullzero"`                                                                          // When was this account silenced (eg., statuses only visible to followers, not public)?
-	SuspendedAt             time.Time        `validate:"-" bun:"type:timestamptz,nullzero"`                                                                          // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
-	HideCollections         *bool            `validate:"-" bun:",default:false"`                                                                                     // Hide this account's collections
-	SuspensionOrigin        string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
-	EnableRSS               *bool            `validate:"-" bun:",default:false"`                                                                                     // enable RSS feed subscription for this account's public posts at [URL]/feed
+	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"` // when was item created.
+	UpdatedAt               time.Time        `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
+	FetchedAt               time.Time        `bun:"type:timestamptz,nullzero"`                                   // when was item (remote) last fetched.
+	Username                string           `bun:",nullzero,notnull,unique:usernamedomain"`                     // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other
+	Domain                  string           `bun:",nullzero,unique:usernamedomain"`                             // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username.
+	AvatarMediaAttachmentID string           `bun:"type:CHAR(26),nullzero"`                                      // Database ID of the media attachment, if present
+	AvatarMediaAttachment   *MediaAttachment `bun:"rel:belongs-to"`                                              // MediaAttachment corresponding to avatarMediaAttachmentID
+	AvatarRemoteURL         string           `bun:",nullzero"`                                                   // For a non-local account, where can the header be fetched?
+	HeaderMediaAttachmentID string           `bun:"type:CHAR(26),nullzero"`                                      // Database ID of the media attachment, if present
+	HeaderMediaAttachment   *MediaAttachment `bun:"rel:belongs-to"`                                              // MediaAttachment corresponding to headerMediaAttachmentID
+	HeaderRemoteURL         string           `bun:",nullzero"`                                                   // For a non-local account, where can the header be fetched?
+	DisplayName             string           `bun:""`                                                            // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
+	EmojiIDs                []string         `bun:"emojis,array"`                                                // Database IDs of any emojis used in this account's bio, display name, etc
+	Emojis                  []*Emoji         `bun:"attached_emojis,m2m:account_to_emojis"`                       // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
+	Fields                  []*Field         // A slice of of fields that this account has added to their profile.
+	FieldsRaw               []*Field         // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target
+	Note                    string           `bun:""`                               // A note that this account has on their profile (ie., the account's bio/description of themselves)
+	NoteRaw                 string           `bun:""`                               // The raw contents of .Note without conversion to HTML, only available when requester = target
+	Memorial                *bool            `bun:",default:false"`                 // Is this a memorial account, ie., has the user passed away?
+	AlsoKnownAs             string           `bun:"type:CHAR(26),nullzero"`         // This account is associated with x account id (TODO: migrate to be AlsoKnownAsID)
+	MovedToAccountID        string           `bun:"type:CHAR(26),nullzero"`         // This account has moved this account id in the database
+	Bot                     *bool            `bun:",default:false"`                 // Does this account identify itself as a bot?
+	Reason                  string           `bun:""`                               // What reason was given for signing up when this account was created?
+	Locked                  *bool            `bun:",default:true"`                  // Does this account need an approval for new followers?
+	Discoverable            *bool            `bun:",default:false"`                 // Should this account be shown in the instance's profile directory?
+	Privacy                 Visibility       `bun:",nullzero"`                      // Default post privacy for this account
+	Sensitive               *bool            `bun:",default:false"`                 // Set posts from this account to sensitive by default?
+	Language                string           `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
+	StatusContentType       string           `bun:",nullzero"`                      // What is the default format for statuses posted by this account (only for local accounts).
+	CustomCSS               string           `bun:",nullzero"`                      // Custom CSS that should be displayed for this Account's profile and statuses.
+	URI                     string           `bun:",nullzero,notnull,unique"`       // ActivityPub URI for this account.
+	URL                     string           `bun:",nullzero,unique"`               // Web URL for this account's profile
+	InboxURI                string           `bun:",nullzero,unique"`               // Address of this account's ActivityPub inbox, for sending activity to
+	SharedInboxURI          *string          `bun:""`                               // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string.
+	OutboxURI               string           `bun:",nullzero,unique"`               // Address of this account's activitypub outbox
+	FollowingURI            string           `bun:",nullzero,unique"`               // URI for getting the following list of this account
+	FollowersURI            string           `bun:",nullzero,unique"`               // URI for getting the followers list of this account
+	FeaturedCollectionURI   string           `bun:",nullzero,unique"`               // URL for getting the featured collection list of this account
+	ActorType               string           `bun:",nullzero,notnull"`              // What type of activitypub actor is this account?
+	PrivateKey              *rsa.PrivateKey  `bun:""`                               // Privatekey for validating activitypub requests, will only be defined for local accounts
+	PublicKey               *rsa.PublicKey   `bun:",notnull"`                       // Publickey for encoding activitypub requests, will be defined for both local and remote accounts
+	PublicKeyURI            string           `bun:",nullzero,notnull,unique"`       // Web-reachable location of this account's public key
+	SensitizedAt            time.Time        `bun:"type:timestamptz,nullzero"`      // When was this account set to have all its media shown as sensitive?
+	SilencedAt              time.Time        `bun:"type:timestamptz,nullzero"`      // When was this account silenced (eg., statuses only visible to followers, not public)?
+	SuspendedAt             time.Time        `bun:"type:timestamptz,nullzero"`      // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
+	HideCollections         *bool            `bun:",default:false"`                 // Hide this account's collections
+	SuspensionOrigin        string           `bun:"type:CHAR(26),nullzero"`         // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
+	EnableRSS               *bool            `bun:",default:false"`                 // enable RSS feed subscription for this account's public posts at [URL]/feed
 }
 
 // IsLocal returns whether account is a local user account.
@@ -131,19 +131,19 @@ func (a *Account) EmojisPopulated() bool {
 
 // AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis.
 type AccountToEmoji struct {
-	AccountID string   `validate:"ulid,required" bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
-	Account   *Account `validate:"-" bun:"rel:belongs-to"`
-	EmojiID   string   `validate:"ulid,required" bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
-	Emoji     *Emoji   `validate:"-" bun:"rel:belongs-to"`
+	AccountID string   `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
+	Account   *Account `bun:"rel:belongs-to"`
+	EmojiID   string   `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
+	Emoji     *Emoji   `bun:"rel:belongs-to"`
 }
 
 // Field represents a key value field on an account, for things like pronouns, website, etc.
 // VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the
 // username of the user.
 type Field struct {
-	Name       string    `validate:"required"`          // Name of this field.
-	Value      string    `validate:"required"`          // Value of this field.
-	VerifiedAt time.Time `validate:"-" bun:",nullzero"` // This field was verified at (optional).
+	Name       string    // Name of this field.
+	Value      string    // Value of this field.
+	VerifiedAt time.Time `bun:",nullzero"` // This field was verified at (optional).
 }
 
 // Relationship describes a requester's relationship with another account.
diff --git a/internal/gtsmodel/accountnote.go b/internal/gtsmodel/accountnote.go
index 239ed6ce9..74b730eae 100644
--- a/internal/gtsmodel/accountnote.go
+++ b/internal/gtsmodel/accountnote.go
@@ -21,12 +21,12 @@ import "time"
 
 // AccountNote stores a private note from a local account related to any account.
 type AccountNote struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                              // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                       // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                       // when was item last updated
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // ID of the local account that created the note
-	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                                                                    // Account corresponding to accountID
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // Who is the target of this note?
-	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                                                                    // Account corresponding to targetAccountID
-	Comment         string    `validate:"-" bun:""`                                                                                                  // The text of the note.
+	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"`                           // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                           // when was item last updated
+	AccountID       string    `bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // ID of the local account that created the note
+	Account         *Account  `bun:"rel:belongs-to"`                                                                        // Account corresponding to accountID
+	TargetAccountID string    `bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // Who is the target of this note?
+	TargetAccount   *Account  `bun:"rel:belongs-to"`                                                                        // Account corresponding to targetAccountID
+	Comment         string    `bun:""`                                                                                      // The text of the note.
 }
diff --git a/internal/gtsmodel/admin.go b/internal/gtsmodel/admin.go
index 22a38f32c..f1f30db2d 100644
--- a/internal/gtsmodel/admin.go
+++ b/internal/gtsmodel/admin.go
@@ -24,17 +24,17 @@ import (
 
 // AdminAccountAction models an action taken by an instance administrator on an account.
 type AdminAccountAction struct {
-	ID              string          `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt       time.Time       `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt       time.Time       `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	AccountID       string          `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero"`                  // Who performed this admin action.
-	Account         *Account        `validate:"-" bun:"rel:has-one"`                                                 // Account corresponding to accountID
-	TargetAccountID string          `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero"`                  // Who is the target of this action
-	TargetAccount   *Account        `validate:"-" bun:"rel:has-one"`                                                 // Account corresponding to targetAccountID
-	Text            string          `validate:"-" bun:""`                                                            // text explaining why this action was taken
-	Type            AdminActionType `validate:"oneof=disable silence suspend" bun:",nullzero,notnull"`               // type of action that was taken
-	SendEmail       bool            `validate:"-" bun:""`                                                            // should an email be sent to the account owner to explain what happened
-	ReportID        string          `validate:",omitempty,ulid" bun:"type:CHAR(26),nullzero"`                        // id of a report connected to this action, if it exists
+	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"` // when was item created
+	UpdatedAt       time.Time       `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	AccountID       string          `bun:"type:CHAR(26),notnull,nullzero"`                              // Who performed this admin action.
+	Account         *Account        `bun:"rel:has-one"`                                                 // Account corresponding to accountID
+	TargetAccountID string          `bun:"type:CHAR(26),notnull,nullzero"`                              // Who is the target of this action
+	TargetAccount   *Account        `bun:"rel:has-one"`                                                 // Account corresponding to targetAccountID
+	Text            string          `bun:""`                                                            // text explaining why this action was taken
+	Type            AdminActionType `bun:",nullzero,notnull"`                                           // type of action that was taken
+	SendEmail       bool            `bun:""`                                                            // should an email be sent to the account owner to explain what happened
+	ReportID        string          `bun:"type:CHAR(26),nullzero"`                                      // id of a report connected to this action, if it exists
 }
 
 // AdminActionType describes a type of action taken on an entity by an admin
diff --git a/internal/gtsmodel/application.go b/internal/gtsmodel/application.go
index a9057b558..5f2d4f4b1 100644
--- a/internal/gtsmodel/application.go
+++ b/internal/gtsmodel/application.go
@@ -22,13 +22,13 @@ import "time"
 // Application represents an application that can perform actions on behalf of a user.
 // It is used to authorize tokens etc, and is associated with an oauth client id in the database.
 type Application struct {
-	ID           string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt    time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt    time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Name         string    `validate:"required" bun:",notnull"`                                             // name of the application given when it was created (eg., 'tusky')
-	Website      string    `validate:"omitempty,url" bun:",nullzero"`                                       // website for the application given when it was created (eg., 'https://tusky.app')
-	RedirectURI  string    `validate:"required,uri" bun:",nullzero,notnull"`                                // redirect uri requested by the application for oauth2 flow
-	ClientID     string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // id of the associated oauth client entity in the db
-	ClientSecret string    `validate:"required,uuid" bun:",nullzero,notnull"`                               // secret of the associated oauth client entity in the db
-	Scopes       string    `validate:"required" bun:",notnull"`                                             // scopes requested when this app was created
+	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"` // when was item created
+	UpdatedAt    time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Name         string    `bun:",notnull"`                                                    // name of the application given when it was created (eg., 'tusky')
+	Website      string    `bun:",nullzero"`                                                   // website for the application given when it was created (eg., 'https://tusky.app')
+	RedirectURI  string    `bun:",nullzero,notnull"`                                           // redirect uri requested by the application for oauth2 flow
+	ClientID     string    `bun:"type:CHAR(26),nullzero,notnull"`                              // id of the associated oauth client entity in the db
+	ClientSecret string    `bun:",nullzero,notnull"`                                           // secret of the associated oauth client entity in the db
+	Scopes       string    `bun:",notnull"`                                                    // scopes requested when this app was created
 }
diff --git a/internal/gtsmodel/block.go b/internal/gtsmodel/block.go
index 98f68f451..1fcdb364a 100644
--- a/internal/gtsmodel/block.go
+++ b/internal/gtsmodel/block.go
@@ -21,12 +21,12 @@ import "time"
 
 // Block refers to the blocking of one account by another.
 type Block struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`             // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`      // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`      // when was item last updated
-	URI             string    `validate:"required,url" bun:",notnull,nullzero,unique"`                              // ActivityPub uri of this block.
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"` // Who does this block originate from?
-	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                                   // Account corresponding to accountID
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"` // Who is the target of this block ?
-	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                                   // Account corresponding to targetAccountID
+	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"` // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	URI             string    `bun:",notnull,nullzero,unique"`                                    // ActivityPub uri of this block.
+	AccountID       string    `bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"`        // Who does this block originate from?
+	Account         *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to accountID
+	TargetAccountID string    `bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"`        // Who is the target of this block ?
+	TargetAccount   *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to targetAccountID
 }
diff --git a/internal/gtsmodel/client.go b/internal/gtsmodel/client.go
index a712257e5..35a85fdbe 100644
--- a/internal/gtsmodel/client.go
+++ b/internal/gtsmodel/client.go
@@ -21,10 +21,10 @@ import "time"
 
 // Client is a wrapper for OAuth client details.
 type Client struct {
-	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Secret    string    `validate:"required,uuid" bun:",nullzero,notnull"`                               // secret generated when client was created
-	Domain    string    `validate:"required,uri" bun:",nullzero,notnull"`                                // domain requested for client
-	UserID    string    `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                         // id of the user that this client acts on behalf of
+	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"` // when was item created
+	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Secret    string    `bun:",nullzero,notnull"`                                           // secret generated when client was created
+	Domain    string    `bun:",nullzero,notnull"`                                           // domain requested for client
+	UserID    string    `bun:"type:CHAR(26),nullzero"`                                      // id of the user that this client acts on behalf of
 }
diff --git a/internal/gtsmodel/domainblock.go b/internal/gtsmodel/domainblock.go
index 0d038d18c..dfe642ef5 100644
--- a/internal/gtsmodel/domainblock.go
+++ b/internal/gtsmodel/domainblock.go
@@ -21,14 +21,14 @@ import "time"
 
 // DomainBlock represents a federation block against a particular domain
 type DomainBlock struct {
-	ID                 string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt          time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt          time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Domain             string    `validate:"required,fqdn" bun:",nullzero,notnull"`                               // domain to block. Eg. 'whatever.com'
-	CreatedByAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // Account ID of the creator of this block
-	CreatedByAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                              // Account corresponding to createdByAccountID
-	PrivateComment     string    `validate:"-" bun:""`                                                            // Private comment on this block, viewable to admins
-	PublicComment      string    `validate:"-" bun:""`                                                            // Public comment on this block, viewable (optionally) by everyone
-	Obfuscate          *bool     `validate:"-" bun:",nullzero,notnull,default:false"`                             // whether the domain name should appear obfuscated when displaying it publicly
-	SubscriptionID     string    `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                         // if this block was created through a subscription, what's the subscription ID?
+	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"` // when was item created
+	UpdatedAt          time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Domain             string    `bun:",nullzero,notnull"`                                           // domain to block. Eg. 'whatever.com'
+	CreatedByAccountID string    `bun:"type:CHAR(26),nullzero,notnull"`                              // Account ID of the creator of this block
+	CreatedByAccount   *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to createdByAccountID
+	PrivateComment     string    `bun:""`                                                            // Private comment on this block, viewable to admins
+	PublicComment      string    `bun:""`                                                            // Public comment on this block, viewable (optionally) by everyone
+	Obfuscate          *bool     `bun:",nullzero,notnull,default:false"`                             // whether the domain name should appear obfuscated when displaying it publicly
+	SubscriptionID     string    `bun:"type:CHAR(26),nullzero"`                                      // if this block was created through a subscription, what's the subscription ID?
 }
diff --git a/internal/gtsmodel/emaildomainblock.go b/internal/gtsmodel/emaildomainblock.go
index 7258f64d7..c81eec365 100644
--- a/internal/gtsmodel/emaildomainblock.go
+++ b/internal/gtsmodel/emaildomainblock.go
@@ -21,10 +21,10 @@ import "time"
 
 // EmailDomainBlock represents a domain that the server should automatically reject sign-up requests from.
 type EmailDomainBlock struct {
-	ID                 string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt          time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt          time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Domain             string    `validate:"required,fqdn" bun:",nullzero,notnull"`                               // Email domain to block. Eg. 'gmail.com' or 'hotmail.com'
-	CreatedByAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // Account ID of the creator of this block
-	CreatedByAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                              // Account corresponding to createdByAccountID
+	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"` // when was item created
+	UpdatedAt          time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Domain             string    `bun:",nullzero,notnull"`                                           // Email domain to block. Eg. 'gmail.com' or 'hotmail.com'
+	CreatedByAccountID string    `bun:"type:CHAR(26),nullzero,notnull"`                              // Account ID of the creator of this block
+	CreatedByAccount   *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to createdByAccountID
 }
diff --git a/internal/gtsmodel/emoji.go b/internal/gtsmodel/emoji.go
index 0fcc3247b..596a64110 100644
--- a/internal/gtsmodel/emoji.go
+++ b/internal/gtsmodel/emoji.go
@@ -21,26 +21,26 @@ import "time"
 
 // Emoji represents a custom emoji that's been uploaded through the admin UI or downloaded from a remote instance.
 type Emoji struct {
-	ID                     string         `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                // id of this item in the database
-	CreatedAt              time.Time      `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                         // when was item created
-	UpdatedAt              time.Time      `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                         // when was item last updated
-	Shortcode              string         `validate:"required" bun:",nullzero,notnull,unique:domainshortcode"`                                     // String shortcode for this emoji -- the part that's between colons. This should be a-zA-Z_  eg., 'blob_hug' 'purple_heart' 'Gay_Otter' Must be unique with domain.
-	Domain                 string         `validate:"omitempty,fqdn" bun:",nullzero,unique:domainshortcode"`                                       // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis.
-	ImageRemoteURL         string         `validate:"required_without=ImageURL,omitempty,url" bun:",nullzero"`                                     // Where can this emoji be retrieved remotely? Null for local emojis.
-	ImageStaticRemoteURL   string         `validate:"required_without=ImageStaticURL,omitempty,url" bun:",nullzero"`                               // Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis.
-	ImageURL               string         `validate:"required_without=ImageRemoteURL,required_without=Domain,omitempty,url" bun:",nullzero"`       // Where can this emoji be retrieved from the local server? Null for remote emojis.
-	ImageStaticURL         string         `validate:"required_without=ImageStaticRemoteURL,required_without=Domain,omitempty,url" bun:",nullzero"` // Where can a static version of this emoji be retrieved from the local server? Null for remote emojis.
-	ImagePath              string         `validate:"required,file" bun:",nullzero,notnull"`                                                       // Path of the emoji image in the server storage system.
-	ImageStaticPath        string         `validate:"required,file" bun:",nullzero,notnull"`                                                       // Path of a static version of the emoji image in the server storage system
-	ImageContentType       string         `validate:"required" bun:",nullzero,notnull"`                                                            // MIME content type of the emoji image
-	ImageStaticContentType string         `validate:"required" bun:",nullzero,notnull"`                                                            // MIME content type of the static version of the emoji image.
-	ImageFileSize          int            `validate:"required,min=1" bun:",nullzero,notnull"`                                                      // Size of the emoji image file in bytes, for serving purposes.
-	ImageStaticFileSize    int            `validate:"required,min=1" bun:",nullzero,notnull"`                                                      // Size of the static version of the emoji image file in bytes, for serving purposes.
-	ImageUpdatedAt         time.Time      `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                         // When was the emoji image last updated?
-	Disabled               *bool          `validate:"-" bun:",nullzero,notnull,default:false"`                                                     // Has a moderation action disabled this emoji from being shown?
-	URI                    string         `validate:"url" bun:",nullzero,notnull,unique"`                                                          // ActivityPub uri of this emoji. Something like 'https://example.org/emojis/1234'
-	VisibleInPicker        *bool          `validate:"-" bun:",nullzero,notnull,default:true"`                                                      // Is this emoji visible in the admin emoji picker?
-	Category               *EmojiCategory `validate:"-" bun:"rel:belongs-to"`                                                                      // In which emoji category is this emoji visible?
-	CategoryID             string         `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                 // ID of the category this emoji belongs to.
-	Cached                 *bool          `validate:"-" bun:",nullzero,notnull,default:false"`
+	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"` // when was item created
+	UpdatedAt              time.Time      `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Shortcode              string         `bun:",nullzero,notnull,unique:domainshortcode"`                    // String shortcode for this emoji -- the part that's between colons. This should be a-zA-Z_  eg., 'blob_hug' 'purple_heart' 'Gay_Otter' Must be unique with domain.
+	Domain                 string         `bun:",nullzero,unique:domainshortcode"`                            // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis.
+	ImageRemoteURL         string         `bun:",nullzero"`                                                   // Where can this emoji be retrieved remotely? Null for local emojis.
+	ImageStaticRemoteURL   string         `bun:",nullzero"`                                                   // Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis.
+	ImageURL               string         `bun:",nullzero"`                                                   // Where can this emoji be retrieved from the local server? Null for remote emojis.
+	ImageStaticURL         string         `bun:",nullzero"`                                                   // Where can a static version of this emoji be retrieved from the local server? Null for remote emojis.
+	ImagePath              string         `bun:",nullzero,notnull"`                                           // Path of the emoji image in the server storage system.
+	ImageStaticPath        string         `bun:",nullzero,notnull"`                                           // Path of a static version of the emoji image in the server storage system
+	ImageContentType       string         `bun:",nullzero,notnull"`                                           // MIME content type of the emoji image
+	ImageStaticContentType string         `bun:",nullzero,notnull"`                                           // MIME content type of the static version of the emoji image.
+	ImageFileSize          int            `bun:",nullzero,notnull"`                                           // Size of the emoji image file in bytes, for serving purposes.
+	ImageStaticFileSize    int            `bun:",nullzero,notnull"`                                           // Size of the static version of the emoji image file in bytes, for serving purposes.
+	ImageUpdatedAt         time.Time      `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the emoji image last updated?
+	Disabled               *bool          `bun:",nullzero,notnull,default:false"`                             // Has a moderation action disabled this emoji from being shown?
+	URI                    string         `bun:",nullzero,notnull,unique"`                                    // ActivityPub uri of this emoji. Something like 'https://example.org/emojis/1234'
+	VisibleInPicker        *bool          `bun:",nullzero,notnull,default:true"`                              // Is this emoji visible in the admin emoji picker?
+	Category               *EmojiCategory `bun:"rel:belongs-to"`                                              // In which emoji category is this emoji visible?
+	CategoryID             string         `bun:"type:CHAR(26),nullzero"`                                      // ID of the category this emoji belongs to.
+	Cached                 *bool          `bun:",nullzero,notnull,default:false"`
 }
diff --git a/internal/gtsmodel/emojicategory.go b/internal/gtsmodel/emojicategory.go
index ad17a8600..80a448f3c 100644
--- a/internal/gtsmodel/emojicategory.go
+++ b/internal/gtsmodel/emojicategory.go
@@ -21,8 +21,8 @@ import "time"
 
 // EmojiCategory represents a grouping of custom emojis.
 type EmojiCategory struct {
-	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Name      string    `validate:"required" bun:",nullzero,notnull,unique"`                             // name of this category
+	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"` // when was item created
+	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Name      string    `bun:",nullzero,notnull,unique"`                                    // name of this category
 }
diff --git a/internal/gtsmodel/follow.go b/internal/gtsmodel/follow.go
index 871d01e20..f359a7bc4 100644
--- a/internal/gtsmodel/follow.go
+++ b/internal/gtsmodel/follow.go
@@ -21,14 +21,14 @@ import "time"
 
 // Follow represents one account following another, and the metadata around that follow.
 type Follow struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	URI             string    `validate:"required,url" bun:",notnull,nullzero,unique"`                         // ActivityPub uri of this follow.
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"` // Who does this follow originate from?
-	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                              // Account corresponding to accountID
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"` // Who is the target of this follow ?
-	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                              // Account corresponding to targetAccountID
-	ShowReblogs     *bool     `validate:"-" bun:",nullzero,notnull,default:true"`                              // Does this follow also want to see reblogs and not just posts?
-	Notify          *bool     `validate:"-" bun:",nullzero,notnull,default:false"`                             // does the following account want to be notified when the followed account posts?
+	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"` // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	URI             string    `bun:",notnull,nullzero,unique"`                                    // ActivityPub uri of this follow.
+	AccountID       string    `bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"`             // Who does this follow originate from?
+	Account         *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to accountID
+	TargetAccountID string    `bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"`             // Who is the target of this follow ?
+	TargetAccount   *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to targetAccountID
+	ShowReblogs     *bool     `bun:",nullzero,notnull,default:true"`                              // Does this follow also want to see reblogs and not just posts?
+	Notify          *bool     `bun:",nullzero,notnull,default:false"`                             // does the following account want to be notified when the followed account posts?
 }
diff --git a/internal/gtsmodel/followrequest.go b/internal/gtsmodel/followrequest.go
index 3fc161af7..32eb13950 100644
--- a/internal/gtsmodel/followrequest.go
+++ b/internal/gtsmodel/followrequest.go
@@ -21,14 +21,14 @@ import "time"
 
 // FollowRequest represents one account requesting to follow another, and the metadata around that request.
 type FollowRequest struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`          // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`   // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`   // when was item last updated
-	URI             string    `validate:"required,url" bun:",notnull,nullzero,unique"`                           // ActivityPub uri of this follow (request).
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"` // Who does this follow request originate from?
-	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                                // Account corresponding to accountID
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"` // Who is the target of this follow request?
-	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                                // Account corresponding to targetAccountID
-	ShowReblogs     *bool     `validate:"-" bun:",nullzero,notnull,default:true"`                                // Does this follow also want to see reblogs and not just posts?
-	Notify          *bool     `validate:"-" bun:",nullzero,notnull,default:false"`                               // does the following account want to be notified when the followed account posts?
+	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"` // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	URI             string    `bun:",notnull,nullzero,unique"`                                    // ActivityPub uri of this follow (request).
+	AccountID       string    `bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"`           // Who does this follow request originate from?
+	Account         *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to accountID
+	TargetAccountID string    `bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"`           // Who is the target of this follow request?
+	TargetAccount   *Account  `bun:"rel:belongs-to"`                                              // Account corresponding to targetAccountID
+	ShowReblogs     *bool     `bun:",nullzero,notnull,default:true"`                              // Does this follow also want to see reblogs and not just posts?
+	Notify          *bool     `bun:",nullzero,notnull,default:false"`                             // does the following account want to be notified when the followed account posts?
 }
diff --git a/internal/gtsmodel/instance.go b/internal/gtsmodel/instance.go
index 3172726fd..388f0f4ed 100644
--- a/internal/gtsmodel/instance.go
+++ b/internal/gtsmodel/instance.go
@@ -21,22 +21,22 @@ import "time"
 
 // Instance represents a federated instance, either local or remote.
 type Instance struct {
-	ID                     string       `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                     // id of this item in the database
-	CreatedAt              time.Time    `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`              // when was item created
-	UpdatedAt              time.Time    `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`              // when was item last updated
-	Domain                 string       `validate:"required,fqdn" bun:",nullzero,notnull,unique"`                                     // Instance domain eg example.org
-	Title                  string       `validate:"-" bun:""`                                                                         // Title of this instance as it would like to be displayed.
-	URI                    string       `validate:"required,url" bun:",nullzero,notnull,unique"`                                      // base URI of this instance eg https://example.org
-	SuspendedAt            time.Time    `validate:"-" bun:"type:timestamptz,nullzero"`                                                // When was this instance suspended, if at all?
-	DomainBlockID          string       `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                      // ID of any existing domain block for this instance in the database
-	DomainBlock            *DomainBlock `validate:"-" bun:"rel:belongs-to"`                                                           // Domain block corresponding to domainBlockID
-	ShortDescription       string       `validate:"-" bun:""`                                                                         // Short description of this instance
-	Description            string       `validate:"-" bun:""`                                                                         // Longer description of this instance
-	Terms                  string       `validate:"-" bun:""`                                                                         // Terms and conditions of this instance
-	ContactEmail           string       `validate:"omitempty,email" bun:""`                                                           // Contact email address for this instance
-	ContactAccountUsername string       `validate:"required_with=ContactAccountID" bun:",nullzero"`                                   // Username of the contact account for this instance
-	ContactAccountID       string       `validate:"required_with=ContactAccountUsername,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Contact account ID in the database for this instance
-	ContactAccount         *Account     `validate:"-" bun:"rel:belongs-to"`                                                           // account corresponding to contactAccountID
-	Reputation             int64        `validate:"-" bun:",notnull,default:0"`                                                       // Reputation score of this instance
-	Version                string       `validate:"-" bun:",nullzero"`                                                                // Version of the software used on this instance
+	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"` // when was item created
+	UpdatedAt              time.Time    `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Domain                 string       `bun:",nullzero,notnull,unique"`                                    // Instance domain eg example.org
+	Title                  string       `bun:""`                                                            // Title of this instance as it would like to be displayed.
+	URI                    string       `bun:",nullzero,notnull,unique"`                                    // base URI of this instance eg https://example.org
+	SuspendedAt            time.Time    `bun:"type:timestamptz,nullzero"`                                   // When was this instance suspended, if at all?
+	DomainBlockID          string       `bun:"type:CHAR(26),nullzero"`                                      // ID of any existing domain block for this instance in the database
+	DomainBlock            *DomainBlock `bun:"rel:belongs-to"`                                              // Domain block corresponding to domainBlockID
+	ShortDescription       string       `bun:""`                                                            // Short description of this instance
+	Description            string       `bun:""`                                                            // Longer description of this instance
+	Terms                  string       `bun:""`                                                            // Terms and conditions of this instance
+	ContactEmail           string       `bun:""`                                                            // Contact email address for this instance
+	ContactAccountUsername string       `bun:",nullzero"`                                                   // Username of the contact account for this instance
+	ContactAccountID       string       `bun:"type:CHAR(26),nullzero"`                                      // Contact account ID in the database for this instance
+	ContactAccount         *Account     `bun:"rel:belongs-to"`                                              // account corresponding to contactAccountID
+	Reputation             int64        `bun:",notnull,default:0"`                                          // Reputation score of this instance
+	Version                string       `bun:",nullzero"`                                                   // Version of the software used on this instance
 }
diff --git a/internal/gtsmodel/list.go b/internal/gtsmodel/list.go
index 98188b113..ea53df9b3 100644
--- a/internal/gtsmodel/list.go
+++ b/internal/gtsmodel/list.go
@@ -21,24 +21,24 @@ import "time"
 
 // List refers to a list of follows for which the owning account wants to view a timeline of posts.
 type List struct {
-	ID            string        `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`               // id of this item in the database
-	CreatedAt     time.Time     `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`        // when was item created
-	UpdatedAt     time.Time     `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`        // when was item last updated
-	Title         string        `validate:"required" bun:",nullzero,notnull,unique:listaccounttitle"`                   // Title of this list.
-	AccountID     string        `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero,unique:listaccounttitle"` // Account that created/owns the list
-	Account       *Account      `validate:"-" bun:"-"`                                                                  // Account corresponding to accountID
-	ListEntries   []*ListEntry  `validate:"-" bun:"-"`                                                                  // Entries contained by this list.
-	RepliesPolicy RepliesPolicy `validate:"-" bun:",nullzero,notnull,default:'followed'"`                               // RepliesPolicy for this list.
+	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"` // when was item created
+	UpdatedAt     time.Time     `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Title         string        `bun:",nullzero,notnull,unique:listaccounttitle"`                   // Title of this list.
+	AccountID     string        `bun:"type:CHAR(26),notnull,nullzero,unique:listaccounttitle"`      // Account that created/owns the list
+	Account       *Account      `bun:"-"`                                                           // Account corresponding to accountID
+	ListEntries   []*ListEntry  `bun:"-"`                                                           // Entries contained by this list.
+	RepliesPolicy RepliesPolicy `bun:",nullzero,notnull,default:'followed'"`                        // RepliesPolicy for this list.
 }
 
 // ListEntry refers to a single follow entry in a list.
 type ListEntry struct {
-	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                  // id of this item in the database
-	CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`           // when was item created
-	UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`           // when was item last updated
-	ListID    string    `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"` // ID of the list that this entry belongs to.
-	FollowID  string    `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"` // Follow that the account owning this entry wants to see posts of in the timeline.
-	Follow    *Follow   `validate:"-" bun:"-"`                                                                     // Follow corresponding to followID.
+	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"` // when was item created
+	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	ListID    string    `bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"`   // ID of the list that this entry belongs to.
+	FollowID  string    `bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"`   // Follow that the account owning this entry wants to see posts of in the timeline.
+	Follow    *Follow   `bun:"-"`                                                           // Follow corresponding to followID.
 }
 
 // RepliesPolicy denotes which replies should be shown in the list.
diff --git a/internal/gtsmodel/marker.go b/internal/gtsmodel/marker.go
index 3aeb376ff..93fbebfe0 100644
--- a/internal/gtsmodel/marker.go
+++ b/internal/gtsmodel/marker.go
@@ -21,11 +21,11 @@ import "time"
 
 // Marker stores a local account's read position on a given timeline.
 type Marker struct {
-	AccountID  string     `validate:"required,ulid" bun:"type:CHAR(26),pk,unique:markers_account_id_timeline_uniq,notnull,nullzero"` // ID of the local account that owns the marker
-	Name       MarkerName `validate:"oneof=home notifications" bun:",nullzero,notnull,pk,unique:markers_account_id_timeline_uniq"`   // Name of the marked timeline
-	UpdatedAt  time.Time  `validate:"required" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                    // When marker was last updated
-	Version    int        `validate:"required,min=0" bun:",nullzero,notnull,default:0"`                                              // For optimistic concurrency control
-	LastReadID string     `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero"`                                            // Last ID read on this timeline (status ID for home, notification ID for notifications)
+	AccountID  string     `bun:"type:CHAR(26),pk,unique:markers_account_id_timeline_uniq,notnull,nullzero"` // ID of the local account that owns the marker
+	Name       MarkerName `bun:",nullzero,notnull,pk,unique:markers_account_id_timeline_uniq"`              // Name of the marked timeline
+	UpdatedAt  time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`               // When marker was last updated
+	Version    int        `bun:",nullzero,notnull,default:0"`                                               // For optimistic concurrency control
+	LastReadID string     `bun:"type:CHAR(26),notnull,nullzero"`                                            // Last ID read on this timeline (status ID for home, notification ID for notifications)
 }
 
 // MarkerName is the name of one of the timelines we can store markers for.
diff --git a/internal/gtsmodel/mediaattachment.go b/internal/gtsmodel/mediaattachment.go
index b8e8b57f7..e418de7d2 100644
--- a/internal/gtsmodel/mediaattachment.go
+++ b/internal/gtsmodel/mediaattachment.go
@@ -24,42 +24,42 @@ import (
 // MediaAttachment represents a user-uploaded media attachment: an image/video/audio/gif that is
 // somewhere in storage and that can be retrieved and served by the router.
 type MediaAttachment struct {
-	ID                string           `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                       // id of this item in the database
-	CreatedAt         time.Time        `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                // when was item created
-	UpdatedAt         time.Time        `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                // when was item last updated
-	StatusID          string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                        // ID of the status to which this is attached
-	URL               string           `validate:"required_without=RemoteURL,omitempty,url" bun:",nullzero"`                           // Where can the attachment be retrieved on *this* server
-	RemoteURL         string           `validate:"required_without=URL,omitempty,url" bun:",nullzero"`                                 // Where can the attachment be retrieved on a remote server (empty for local media)
-	Type              FileType         `validate:"oneof=Image Gifv Audio Video Unknown" bun:",nullzero,notnull"`                       // Type of file (image/gifv/audio/video)
-	FileMeta          FileMeta         `validate:"required" bun:",embed:,nullzero,notnull"`                                            // Metadata about the file
-	AccountID         string           `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                                 // To which account does this attachment belong
-	Description       string           `validate:"-" bun:""`                                                                           // Description of the attachment (for screenreaders)
-	ScheduledStatusID string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                        // To which scheduled status does this attachment belong
-	Blurhash          string           `validate:"required_if=Type Image,required_if=Type Gif,required_if=Type Video" bun:",nullzero"` // What is the generated blurhash of this attachment
-	Processing        ProcessingStatus `validate:"oneof=0 1 2 666" bun:",notnull,default:2"`                                           // What is the processing status of this attachment
-	File              File             `validate:"required" bun:",embed:file_,notnull,nullzero"`                                       // metadata for the whole file
-	Thumbnail         Thumbnail        `validate:"required" bun:",embed:thumbnail_,notnull,nullzero"`                                  // small image thumbnail derived from a larger image, video, or audio file.
-	Avatar            *bool            `validate:"-" bun:",nullzero,notnull,default:false"`                                            // Is this attachment being used as an avatar?
-	Header            *bool            `validate:"-" bun:",nullzero,notnull,default:false"`                                            // Is this attachment being used as a header?
-	Cached            *bool            `validate:"-" bun:",nullzero,notnull,default:false"`                                            // Is this attachment currently cached by our instance?
+	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"` // when was item created
+	UpdatedAt         time.Time        `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	StatusID          string           `bun:"type:CHAR(26),nullzero"`                                      // ID of the status to which this is attached
+	URL               string           `bun:",nullzero"`                                                   // Where can the attachment be retrieved on *this* server
+	RemoteURL         string           `bun:",nullzero"`                                                   // Where can the attachment be retrieved on a remote server (empty for local media)
+	Type              FileType         `bun:",nullzero,notnull"`                                           // Type of file (image/gifv/audio/video)
+	FileMeta          FileMeta         `bun:",embed:,nullzero,notnull"`                                    // Metadata about the file
+	AccountID         string           `bun:"type:CHAR(26),nullzero,notnull"`                              // To which account does this attachment belong
+	Description       string           `bun:""`                                                            // Description of the attachment (for screenreaders)
+	ScheduledStatusID string           `bun:"type:CHAR(26),nullzero"`                                      // To which scheduled status does this attachment belong
+	Blurhash          string           `bun:",nullzero"`                                                   // What is the generated blurhash of this attachment
+	Processing        ProcessingStatus `bun:",notnull,default:2"`                                          // What is the processing status of this attachment
+	File              File             `bun:",embed:file_,notnull,nullzero"`                               // metadata for the whole file
+	Thumbnail         Thumbnail        `bun:",embed:thumbnail_,notnull,nullzero"`                          // small image thumbnail derived from a larger image, video, or audio file.
+	Avatar            *bool            `bun:",nullzero,notnull,default:false"`                             // Is this attachment being used as an avatar?
+	Header            *bool            `bun:",nullzero,notnull,default:false"`                             // Is this attachment being used as a header?
+	Cached            *bool            `bun:",nullzero,notnull,default:false"`                             // Is this attachment currently cached by our instance?
 }
 
 // File refers to the metadata for the whole file
 type File struct {
-	Path        string    `validate:"required,file" bun:",nullzero,notnull"`                               // Path of the file in storage.
-	ContentType string    `validate:"required" bun:",nullzero,notnull"`                                    // MIME content type of the file.
-	FileSize    int       `validate:"required" bun:",notnull"`                                             // File size in bytes
-	UpdatedAt   time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
+	Path        string    `bun:",nullzero,notnull"`                                           // Path of the file in storage.
+	ContentType string    `bun:",nullzero,notnull"`                                           // MIME content type of the file.
+	FileSize    int       `bun:",notnull"`                                                    // File size in bytes
+	UpdatedAt   time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
 }
 
 // Thumbnail refers to a small image thumbnail derived from a larger image, video, or audio file.
 type Thumbnail struct {
-	Path        string    `validate:"required,file" bun:",nullzero,notnull"`                               // Path of the file in storage.
-	ContentType string    `validate:"required" bun:",nullzero,notnull"`                                    // MIME content type of the file.
-	FileSize    int       `validate:"required" bun:",notnull"`                                             // File size in bytes
-	UpdatedAt   time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
-	URL         string    `validate:"required_without=RemoteURL,omitempty,url" bun:",nullzero"`            // What is the URL of the thumbnail on the local server
-	RemoteURL   string    `validate:"required_without=URL,omitempty,url" bun:",nullzero"`                  // What is the remote URL of the thumbnail (empty for local media)
+	Path        string    `bun:",nullzero,notnull"`                                           // Path of the file in storage.
+	ContentType string    `bun:",nullzero,notnull"`                                           // MIME content type of the file.
+	FileSize    int       `bun:",notnull"`                                                    // File size in bytes
+	UpdatedAt   time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
+	URL         string    `bun:",nullzero"`                                                   // What is the URL of the thumbnail on the local server
+	RemoteURL   string    `bun:",nullzero"`                                                   // What is the remote URL of the thumbnail (empty for local media)
 }
 
 // ProcessingStatus refers to how far along in the processing stage the attachment is.
@@ -87,33 +87,33 @@ const (
 
 // FileMeta describes metadata about the actual contents of the file.
 type FileMeta struct {
-	Original Original `validate:"required" bun:"embed:original_"`
+	Original Original `bun:"embed:original_"`
 	Small    Small    `bun:"embed:small_"`
 	Focus    Focus    `bun:"embed:focus_"`
 }
 
 // Small can be used for a thumbnail of any media type
 type Small struct {
-	Width  int     `validate:"required_with=Height Size Aspect"`  // width in pixels
-	Height int     `validate:"required_with=Width Size Aspect"`   // height in pixels
-	Size   int     `validate:"required_with=Width Height Aspect"` // size in pixels (width * height)
-	Aspect float32 `validate:"required_with=Width Height Size"`   // aspect ratio (width / height)
+	Width  int     // width in pixels
+	Height int     // height in pixels
+	Size   int     // size in pixels (width * height)
+	Aspect float32 // aspect ratio (width / height)
 }
 
 // Original can be used for original metadata for any media type
 type Original struct {
-	Width     int      `validate:"required_with=Height Size Aspect"`  // width in pixels
-	Height    int      `validate:"required_with=Width Size Aspect"`   // height in pixels
-	Size      int      `validate:"required_with=Width Height Aspect"` // size in pixels (width * height)
-	Aspect    float32  `validate:"required_with=Width Height Size"`   // aspect ratio (width / height)
-	Duration  *float32 `validate:"-"`                                 // video-specific: duration of the video in seconds
-	Framerate *float32 `validate:"-"`                                 // video-specific: fps
-	Bitrate   *uint64  `validate:"-"`                                 // video-specific: bitrate
+	Width     int      // width in pixels
+	Height    int      // height in pixels
+	Size      int      // size in pixels (width * height)
+	Aspect    float32  // aspect ratio (width / height)
+	Duration  *float32 // video-specific: duration of the video in seconds
+	Framerate *float32 // video-specific: fps
+	Bitrate   *uint64  // video-specific: bitrate
 }
 
 // Focus describes the 'center' of the image for display purposes.
 // X and Y should each be between -1 and 1
 type Focus struct {
-	X float32 `validate:"omitempty,max=1,min=-1"`
-	Y float32 `validate:"omitempty,max=1,min=-1"`
+	X float32
+	Y float32
 }
diff --git a/internal/gtsmodel/mention.go b/internal/gtsmodel/mention.go
index 9ad9204eb..fb7f3b51a 100644
--- a/internal/gtsmodel/mention.go
+++ b/internal/gtsmodel/mention.go
@@ -24,17 +24,17 @@ import (
 
 // Mention refers to the 'tagging' or 'mention' of a user within a status.
 type Mention struct {
-	ID               string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt        time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt        time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	StatusID         string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // ID of the status this mention originates from
-	Status           *Status   `validate:"-" bun:"rel:belongs-to"`                                              // status referred to by statusID
-	OriginAccountID  string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // ID of the mention creator account
-	OriginAccountURI string    `validate:"url" bun:",nullzero,notnull"`                                         // ActivityPub URI of the originator/creator of the mention
-	OriginAccount    *Account  `validate:"-" bun:"rel:belongs-to"`                                              // account referred to by originAccountID
-	TargetAccountID  string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // Mention target/receiver account ID
-	TargetAccount    *Account  `validate:"-" bun:"rel:belongs-to"`                                              // account referred to by targetAccountID
-	Silent           *bool     `validate:"-" bun:",nullzero,notnull,default:false"`                             // Prevent this mention from generating a notification?
+	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"` // when was item created
+	UpdatedAt        time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	StatusID         string    `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the status this mention originates from
+	Status           *Status   `bun:"rel:belongs-to"`                                              // status referred to by statusID
+	OriginAccountID  string    `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the mention creator account
+	OriginAccountURI string    `bun:",nullzero,notnull"`                                           // ActivityPub URI of the originator/creator of the mention
+	OriginAccount    *Account  `bun:"rel:belongs-to"`                                              // account referred to by originAccountID
+	TargetAccountID  string    `bun:"type:CHAR(26),nullzero,notnull"`                              // Mention target/receiver account ID
+	TargetAccount    *Account  `bun:"rel:belongs-to"`                                              // account referred to by targetAccountID
+	Silent           *bool     `bun:",nullzero,notnull,default:false"`                             // Prevent this mention from generating a notification?
 
 	/*
 		NON-DATABASE CONVENIENCE FIELDS
@@ -48,15 +48,15 @@ type Mention struct {
 	// @whatever_username@example.org
 	//
 	// This will not be put in the database, it's just for convenience.
-	NameString string `validate:"-" bun:"-"`
+	NameString string `bun:"-"`
 	// TargetAccountURI is the AP ID (uri) of the user mentioned.
 	//
 	// This will not be put in the database, it's just for convenience.
-	TargetAccountURI string `validate:"-" bun:"-"`
+	TargetAccountURI string `bun:"-"`
 	// TargetAccountURL is the web url of the user mentioned.
 	//
 	// This will not be put in the database, it's just for convenience.
-	TargetAccountURL string `validate:"-" bun:"-"`
+	TargetAccountURL string `bun:"-"`
 	// A pointer to the gtsmodel account of the mentioned account.
 }
 
diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go
index 7e8db7713..5e2eff167 100644
--- a/internal/gtsmodel/notification.go
+++ b/internal/gtsmodel/notification.go
@@ -21,17 +21,17 @@ import "time"
 
 // Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc.
 type Notification struct {
-	ID               string           `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                                                                                                                    // id of this item in the database
-	CreatedAt        time.Time        `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                                                                                                             // when was item created
-	UpdatedAt        time.Time        `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                                                                                                             // when was item last updated
-	NotificationType NotificationType `validate:"oneof=follow follow_request mention reblog favourite poll status" bun:",nullzero,notnull"`                                                                                                        // Type of this notification
-	TargetAccountID  string           `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                                                                                       // ID of the account targeted by the notification (ie., who will receive the notification?)
-	TargetAccount    *Account         `validate:"-" bun:"-"`                                                                                                                                                                                       // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary.
-	OriginAccountID  string           `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                                                                                       // ID of the account that performed the action that created the notification.
-	OriginAccount    *Account         `validate:"-" bun:"-"`                                                                                                                                                                                       // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary.
-	StatusID         string           `validate:"required_if=NotificationType mention,required_if=NotificationType reblog,required_if=NotificationType favourite,required_if=NotificationType status,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // If the notification pertains to a status, what is the database ID of that status?
-	Status           *Status          `validate:"-" bun:"-"`                                                                                                                                                                                       // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary.
-	Read             *bool            `validate:"-" bun:",nullzero,notnull,default:false"`                                                                                                                                                         // Notification has been seen/read
+	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"` // when was item created
+	UpdatedAt        time.Time        `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	NotificationType NotificationType `bun:",nullzero,notnull"`                                           // Type of this notification
+	TargetAccountID  string           `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the account targeted by the notification (ie., who will receive the notification?)
+	TargetAccount    *Account         `bun:"-"`                                                           // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary.
+	OriginAccountID  string           `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the account that performed the action that created the notification.
+	OriginAccount    *Account         `bun:"-"`                                                           // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary.
+	StatusID         string           `bun:"type:CHAR(26),nullzero"`                                      // If the notification pertains to a status, what is the database ID of that status?
+	Status           *Status          `bun:"-"`                                                           // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary.
+	Read             *bool            `bun:",nullzero,notnull,default:false"`                             // Notification has been seen/read
 }
 
 // NotificationType describes the reason/type of this notification.
diff --git a/internal/gtsmodel/report.go b/internal/gtsmodel/report.go
index eb45c820c..e5b942563 100644
--- a/internal/gtsmodel/report.go
+++ b/internal/gtsmodel/report.go
@@ -26,20 +26,20 @@ import "time"
 // or another instance, OR a report that was created remotely (on another instance)
 // about a user on this instance, and received via the federated (s2s) API.
 type Report struct {
-	ID                     string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt              time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt              time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	URI                    string    `validate:"required,url" bun:",unique,nullzero,notnull"`                         // activitypub URI of this report
-	AccountID              string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // which account created this report
-	Account                *Account  `validate:"-" bun:"-"`                                                           // account corresponding to AccountID
-	TargetAccountID        string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // which account is targeted by this report
-	TargetAccount          *Account  `validate:"-" bun:"-"`                                                           // account corresponding to TargetAccountID
-	Comment                string    `validate:"-" bun:",nullzero"`                                                   // comment / explanation for this report, by the reporter
-	StatusIDs              []string  `validate:"dive,ulid" bun:"statuses,array"`                                      // database IDs of any statuses referenced by this report
-	Statuses               []*Status `validate:"-" bun:"-"`                                                           // statuses corresponding to StatusIDs
-	Forwarded              *bool     `validate:"-" bun:",nullzero,notnull,default:false"`                             // flag to indicate report should be forwarded to remote instance
-	ActionTaken            string    `validate:"-" bun:",nullzero"`                                                   // string description of what action was taken in response to this report
-	ActionTakenAt          time.Time `validate:"-" bun:"type:timestamptz,nullzero"`                                   // time at which action was taken, if any
-	ActionTakenByAccountID string    `validate:",omitempty,ulid" bun:"type:CHAR(26),nullzero"`                        // database ID of account which took action, if any
-	ActionTakenByAccount   *Account  `validate:"-" bun:"-"`                                                           // account corresponding to ActionTakenByID, if any
+	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"` // when was item created
+	UpdatedAt              time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	URI                    string    `bun:",unique,nullzero,notnull"`                                    // activitypub URI of this report
+	AccountID              string    `bun:"type:CHAR(26),nullzero,notnull"`                              // which account created this report
+	Account                *Account  `bun:"-"`                                                           // account corresponding to AccountID
+	TargetAccountID        string    `bun:"type:CHAR(26),nullzero,notnull"`                              // which account is targeted by this report
+	TargetAccount          *Account  `bun:"-"`                                                           // account corresponding to TargetAccountID
+	Comment                string    `bun:",nullzero"`                                                   // comment / explanation for this report, by the reporter
+	StatusIDs              []string  `bun:"statuses,array"`                                              // database IDs of any statuses referenced by this report
+	Statuses               []*Status `bun:"-"`                                                           // statuses corresponding to StatusIDs
+	Forwarded              *bool     `bun:",nullzero,notnull,default:false"`                             // flag to indicate report should be forwarded to remote instance
+	ActionTaken            string    `bun:",nullzero"`                                                   // string description of what action was taken in response to this report
+	ActionTakenAt          time.Time `bun:"type:timestamptz,nullzero"`                                   // time at which action was taken, if any
+	ActionTakenByAccountID string    `bun:"type:CHAR(26),nullzero"`                                      // database ID of account which took action, if any
+	ActionTakenByAccount   *Account  `bun:"-"`                                                           // account corresponding to ActionTakenByID, if any
 }
diff --git a/internal/gtsmodel/routersession.go b/internal/gtsmodel/routersession.go
index 050c0715b..d51d93bca 100644
--- a/internal/gtsmodel/routersession.go
+++ b/internal/gtsmodel/routersession.go
@@ -21,9 +21,9 @@ import "time"
 
 // RouterSession is used to store and retrieve settings for a router session.
 type RouterSession struct {
-	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Auth      []byte    `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"`
-	Crypt     []byte    `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"`
+	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"` // when was item created
+	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Auth      []byte    `bun:"type:bytea,notnull,nullzero"`
+	Crypt     []byte    `bun:"type:bytea,notnull,nullzero"`
 }
diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go
index 393bb1ac7..3e8880798 100644
--- a/internal/gtsmodel/status.go
+++ b/internal/gtsmodel/status.go
@@ -25,47 +25,47 @@ import (
 
 // Status represents a user-created 'post' or 'status' in the database, either remote or local
 type Status struct {
-	ID                       string             `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                              // id of this item in the database
-	CreatedAt                time.Time          `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                       // when was item created
-	UpdatedAt                time.Time          `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                       // when was item last updated
-	FetchedAt                time.Time          `validate:"required_with=!Local" bun:"type:timestamptz,nullzero"`                                      // when was item (remote) last fetched.
-	PinnedAt                 time.Time          `validate:"-" bun:"type:timestamptz,nullzero"`                                                         // Status was pinned by owning account at this time.
-	URI                      string             `validate:"required,url" bun:",unique,nullzero,notnull"`                                               // activitypub URI of this status
-	URL                      string             `validate:"url" bun:",nullzero"`                                                                       // web url for viewing this status
-	Content                  string             `validate:"-" bun:""`                                                                                  // content of this status; likely html-formatted but not guaranteed
-	AttachmentIDs            []string           `validate:"dive,ulid" bun:"attachments,array"`                                                         // Database IDs of any media attachments associated with this status
-	Attachments              []*MediaAttachment `validate:"-" bun:"attached_media,rel:has-many"`                                                       // Attachments corresponding to attachmentIDs
-	TagIDs                   []string           `validate:"dive,ulid" bun:"tags,array"`                                                                // Database IDs of any tags used in this status
-	Tags                     []*Tag             `validate:"-" bun:"attached_tags,m2m:status_to_tags"`                                                  // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
-	MentionIDs               []string           `validate:"dive,ulid" bun:"mentions,array"`                                                            // Database IDs of any mentions in this status
-	Mentions                 []*Mention         `validate:"-" bun:"attached_mentions,rel:has-many"`                                                    // Mentions corresponding to mentionIDs
-	EmojiIDs                 []string           `validate:"dive,ulid" bun:"emojis,array"`                                                              // Database IDs of any emojis used in this status
-	Emojis                   []*Emoji           `validate:"-" bun:"attached_emojis,m2m:status_to_emojis"`                                              // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
-	Local                    *bool              `validate:"-" bun:",nullzero,notnull,default:false"`                                                   // is this status from a local account?
-	AccountID                string             `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                                        // which account posted this status?
-	Account                  *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account corresponding to accountID
-	AccountURI               string             `validate:"required,url" bun:",nullzero,notnull"`                                                      // activitypub uri of the owner of this status
-	InReplyToID              string             `validate:"required_with=InReplyToURI InReplyToAccountID,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
-	InReplyToURI             string             `validate:"required_with=InReplyToID InReplyToAccountID,omitempty,url" bun:",nullzero"`                // activitypub uri of the status this status is a reply to
-	InReplyToAccountID       string             `validate:"required_with=InReplyToID InReplyToURI,omitempty,ulid" bun:"type:CHAR(26),nullzero"`        // id of the account that this status replies to
-	InReplyTo                *Status            `validate:"-" bun:"-"`                                                                                 // status corresponding to inReplyToID
-	InReplyToAccount         *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account corresponding to inReplyToAccountID
-	BoostOfID                string             `validate:"required_with=BoostOfAccountID,omitempty,ulid" bun:"type:CHAR(26),nullzero"`                // id of the status this status is a boost of
-	BoostOfAccountID         string             `validate:"required_with=BoostOfID,omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // id of the account that owns the boosted status
-	BoostOf                  *Status            `validate:"-" bun:"-"`                                                                                 // status that corresponds to boostOfID
-	BoostOfAccount           *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account that corresponds to boostOfAccountID
-	ContentWarning           string             `validate:"-" bun:",nullzero"`                                                                         // cw string for this status
-	Visibility               Visibility         `validate:"oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero,notnull"`          // visibility entry for this status
-	Sensitive                *bool              `validate:"-" bun:",nullzero,notnull,default:false"`                                                   // mark the status as sensitive?
-	Language                 string             `validate:"-" bun:",nullzero"`                                                                         // what language is this status written in?
-	CreatedWithApplicationID string             `validate:"required_if=Local true,omitempty,ulid" bun:"type:CHAR(26),nullzero"`                        // Which application was used to create this status?
-	CreatedWithApplication   *Application       `validate:"-" bun:"rel:belongs-to"`                                                                    // application corresponding to createdWithApplicationID
-	ActivityStreamsType      string             `validate:"required" bun:",nullzero,notnull"`                                                          // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
-	Text                     string             `validate:"-" bun:""`                                                                                  // Original text of the status without formatting
-	Federated                *bool              `validate:"-" bun:",notnull"`                                                                          // This status will be federated beyond the local timeline(s)
-	Boostable                *bool              `validate:"-" bun:",notnull"`                                                                          // This status can be boosted/reblogged
-	Replyable                *bool              `validate:"-" bun:",notnull"`                                                                          // This status can be replied to
-	Likeable                 *bool              `validate:"-" bun:",notnull"`                                                                          // This status can be liked/faved
+	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"` // when was item created
+	UpdatedAt                time.Time          `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	FetchedAt                time.Time          `bun:"type:timestamptz,nullzero"`                                   // when was item (remote) last fetched.
+	PinnedAt                 time.Time          `bun:"type:timestamptz,nullzero"`                                   // Status was pinned by owning account at this time.
+	URI                      string             `bun:",unique,nullzero,notnull"`                                    // activitypub URI of this status
+	URL                      string             `bun:",nullzero"`                                                   // web url for viewing this status
+	Content                  string             `bun:""`                                                            // content of this status; likely html-formatted but not guaranteed
+	AttachmentIDs            []string           `bun:"attachments,array"`                                           // Database IDs of any media attachments associated with this status
+	Attachments              []*MediaAttachment `bun:"attached_media,rel:has-many"`                                 // Attachments corresponding to attachmentIDs
+	TagIDs                   []string           `bun:"tags,array"`                                                  // Database IDs of any tags used in this status
+	Tags                     []*Tag             `bun:"attached_tags,m2m:status_to_tags"`                            // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
+	MentionIDs               []string           `bun:"mentions,array"`                                              // Database IDs of any mentions in this status
+	Mentions                 []*Mention         `bun:"attached_mentions,rel:has-many"`                              // Mentions corresponding to mentionIDs
+	EmojiIDs                 []string           `bun:"emojis,array"`                                                // Database IDs of any emojis used in this status
+	Emojis                   []*Emoji           `bun:"attached_emojis,m2m:status_to_emojis"`                        // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
+	Local                    *bool              `bun:",nullzero,notnull,default:false"`                             // is this status from a local account?
+	AccountID                string             `bun:"type:CHAR(26),nullzero,notnull"`                              // which account posted this status?
+	Account                  *Account           `bun:"rel:belongs-to"`                                              // account corresponding to accountID
+	AccountURI               string             `bun:",nullzero,notnull"`                                           // activitypub uri of the owner of this status
+	InReplyToID              string             `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status replies to
+	InReplyToURI             string             `bun:",nullzero"`                                                   // activitypub uri of the status this status is a reply to
+	InReplyToAccountID       string             `bun:"type:CHAR(26),nullzero"`                                      // id of the account that this status replies to
+	InReplyTo                *Status            `bun:"-"`                                                           // status corresponding to inReplyToID
+	InReplyToAccount         *Account           `bun:"rel:belongs-to"`                                              // account corresponding to inReplyToAccountID
+	BoostOfID                string             `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status is a boost of
+	BoostOfAccountID         string             `bun:"type:CHAR(26),nullzero"`                                      // id of the account that owns the boosted status
+	BoostOf                  *Status            `bun:"-"`                                                           // status that corresponds to boostOfID
+	BoostOfAccount           *Account           `bun:"rel:belongs-to"`                                              // account that corresponds to boostOfAccountID
+	ContentWarning           string             `bun:",nullzero"`                                                   // cw string for this status
+	Visibility               Visibility         `bun:",nullzero,notnull"`                                           // visibility entry for this status
+	Sensitive                *bool              `bun:",nullzero,notnull,default:false"`                             // mark the status as sensitive?
+	Language                 string             `bun:",nullzero"`                                                   // what language is this status written in?
+	CreatedWithApplicationID string             `bun:"type:CHAR(26),nullzero"`                                      // Which application was used to create this status?
+	CreatedWithApplication   *Application       `bun:"rel:belongs-to"`                                              // application corresponding to createdWithApplicationID
+	ActivityStreamsType      string             `bun:",nullzero,notnull"`                                           // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
+	Text                     string             `bun:""`                                                            // Original text of the status without formatting
+	Federated                *bool              `bun:",notnull"`                                                    // This status will be federated beyond the local timeline(s)
+	Boostable                *bool              `bun:",notnull"`                                                    // This status can be boosted/reblogged
+	Replyable                *bool              `bun:",notnull"`                                                    // This status can be replied to
+	Likeable                 *bool              `bun:",notnull"`                                                    // This status can be liked/faved
 }
 
 // GetID implements timeline.Timelineable{}.
@@ -252,18 +252,18 @@ func (s *Status) MentionsAccount(id string) bool {
 
 // StatusToTag is an intermediate struct to facilitate the many2many relationship between a status and one or more tags.
 type StatusToTag struct {
-	StatusID string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
-	Status   *Status `validate:"-" bun:"rel:belongs-to"`
-	TagID    string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
-	Tag      *Tag    `validate:"-" bun:"rel:belongs-to"`
+	StatusID string  `bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
+	Status   *Status `bun:"rel:belongs-to"`
+	TagID    string  `bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
+	Tag      *Tag    `bun:"rel:belongs-to"`
 }
 
 // StatusToEmoji is an intermediate struct to facilitate the many2many relationship between a status and one or more emojis.
 type StatusToEmoji struct {
-	StatusID string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
-	Status   *Status `validate:"-" bun:"rel:belongs-to"`
-	EmojiID  string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
-	Emoji    *Emoji  `validate:"-" bun:"rel:belongs-to"`
+	StatusID string  `bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
+	Status   *Status `bun:"rel:belongs-to"`
+	EmojiID  string  `bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
+	Emoji    *Emoji  `bun:"rel:belongs-to"`
 }
 
 // Visibility represents the visibility granularity of a status.
diff --git a/internal/gtsmodel/statusbookmark.go b/internal/gtsmodel/statusbookmark.go
index 6141b6e34..c7590a5c3 100644
--- a/internal/gtsmodel/statusbookmark.go
+++ b/internal/gtsmodel/statusbookmark.go
@@ -21,13 +21,13 @@ import "time"
 
 // StatusBookmark refers to one account having a 'bookmark' of the status of another account.
 type StatusBookmark struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // id of the account that created ('did') the bookmark
-	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                              // account that created the bookmark
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // id the account owning the bookmarked status
-	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                              // account owning the bookmarked status
-	StatusID        string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // database id of the status that has been bookmarked
-	Status          *Status   `validate:"-" bun:"rel:belongs-to"`                                              // the bookmarked status
+	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"` // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	AccountID       string    `bun:"type:CHAR(26),nullzero,notnull"`                              // id of the account that created ('did') the bookmark
+	Account         *Account  `bun:"rel:belongs-to"`                                              // account that created the bookmark
+	TargetAccountID string    `bun:"type:CHAR(26),nullzero,notnull"`                              // id the account owning the bookmarked status
+	TargetAccount   *Account  `bun:"rel:belongs-to"`                                              // account owning the bookmarked status
+	StatusID        string    `bun:"type:CHAR(26),nullzero,notnull"`                              // database id of the status that has been bookmarked
+	Status          *Status   `bun:"rel:belongs-to"`                                              // the bookmarked status
 }
diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go
index 86096ef5e..f81226f8b 100644
--- a/internal/gtsmodel/statusfave.go
+++ b/internal/gtsmodel/statusfave.go
@@ -21,14 +21,14 @@ import "time"
 
 // StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account
 type StatusFave struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                      // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`               // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`               // when was item last updated
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // id of the account that created ('did') the fave
-	Account         *Account  `validate:"-" bun:"-"`                                                                         // account that created the fave
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                                // id the account owning the faved status
-	TargetAccount   *Account  `validate:"-" bun:"-"`                                                                         // account owning the faved status
-	StatusID        string    `validate:"required,ulid" bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // database id of the status that has been 'faved'
-	Status          *Status   `validate:"-" bun:"-"`                                                                         // the faved status
-	URI             string    `validate:"required,url" bun:",nullzero,notnull,unique"`                                       // ActivityPub URI of this fave
+	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"`   // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`   // when was item last updated
+	AccountID       string    `bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // id of the account that created ('did') the fave
+	Account         *Account  `bun:"-"`                                                             // account that created the fave
+	TargetAccountID string    `bun:"type:CHAR(26),nullzero,notnull"`                                // id the account owning the faved status
+	TargetAccount   *Account  `bun:"-"`                                                             // account owning the faved status
+	StatusID        string    `bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // database id of the status that has been 'faved'
+	Status          *Status   `bun:"-"`                                                             // the faved status
+	URI             string    `bun:",nullzero,notnull,unique"`                                      // ActivityPub URI of this fave
 }
diff --git a/internal/gtsmodel/statusmute.go b/internal/gtsmodel/statusmute.go
index 27af921e4..b8aca1c7a 100644
--- a/internal/gtsmodel/statusmute.go
+++ b/internal/gtsmodel/statusmute.go
@@ -21,13 +21,13 @@ import "time"
 
 // StatusMute refers to one account having muted the status of another account or its own.
 type StatusMute struct {
-	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // id of the account that created ('did') the mute
-	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                              // pointer to the account specified by accountID
-	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // id the account owning the muted status (can be the same as accountID)
-	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                              // pointer to the account specified by targetAccountID
-	StatusID        string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // database id of the status that has been muted
-	Status          *Status   `validate:"-" bun:"rel:belongs-to"`                                              // pointer to the muted status specified by statusID
+	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"` // when was item created
+	UpdatedAt       time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	AccountID       string    `bun:"type:CHAR(26),nullzero,notnull"`                              // id of the account that created ('did') the mute
+	Account         *Account  `bun:"rel:belongs-to"`                                              // pointer to the account specified by accountID
+	TargetAccountID string    `bun:"type:CHAR(26),nullzero,notnull"`                              // id the account owning the muted status (can be the same as accountID)
+	TargetAccount   *Account  `bun:"rel:belongs-to"`                                              // pointer to the account specified by targetAccountID
+	StatusID        string    `bun:"type:CHAR(26),nullzero,notnull"`                              // database id of the status that has been muted
+	Status          *Status   `bun:"rel:belongs-to"`                                              // pointer to the muted status specified by statusID
 }
diff --git a/internal/gtsmodel/tag.go b/internal/gtsmodel/tag.go
index a43c4a5ec..514389f23 100644
--- a/internal/gtsmodel/tag.go
+++ b/internal/gtsmodel/tag.go
@@ -21,10 +21,10 @@ import "time"
 
 // Tag represents a hashtag for gathering public statuses together.
 type Tag struct {
-	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Name      string    `validate:"required" bun:",unique,nullzero,notnull"`                             // (lowercase) name of the tag without the hash prefix
-	Useable   *bool     `validate:"-" bun:",nullzero,notnull,default:true"`                              // Tag is useable on this instance.
-	Listable  *bool     `validate:"-" bun:",nullzero,notnull,default:true"`                              // Tagged statuses can be listed on this instance.
+	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"` // when was item created
+	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Name      string    `bun:",unique,nullzero,notnull"`                                    // (lowercase) name of the tag without the hash prefix
+	Useable   *bool     `bun:",nullzero,notnull,default:true"`                              // Tag is useable on this instance.
+	Listable  *bool     `bun:",nullzero,notnull,default:true"`                              // Tagged statuses can be listed on this instance.
 }
diff --git a/internal/gtsmodel/token.go b/internal/gtsmodel/token.go
index 06db7e394..fd640abde 100644
--- a/internal/gtsmodel/token.go
+++ b/internal/gtsmodel/token.go
@@ -21,22 +21,22 @@ import "time"
 
 // Token is a translation of the gotosocial token with the ExpiresIn fields replaced with ExpiresAt.
 type Token struct {
-	ID                  string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt           time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt           time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	ClientID            string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                  // ID of the client who owns this token
-	UserID              string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero"`                          // ID of the user who owns this token
-	RedirectURI         string    `validate:"required,uri" bun:",nullzero,notnull"`                                // Oauth redirect URI for this token
-	Scope               string    `validate:"required" bun:",notnull"`                                             // Oauth scope
-	Code                string    `validate:"-" bun:",pk,nullzero,notnull,default:''"`                             // Code, if present
-	CodeChallenge       string    `validate:"-" bun:",nullzero"`                                                   // Code challenge, if code present
-	CodeChallengeMethod string    `validate:"-" bun:",nullzero"`                                                   // Code challenge method, if code present
-	CodeCreateAt        time.Time `validate:"required_with=Code" bun:"type:timestamptz,nullzero"`                  // Code created time, if code present
-	CodeExpiresAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero"`                                   // Code expires at -- null means the code never expires
-	Access              string    `validate:"-" bun:",pk,nullzero,notnull,default:''"`                             // User level access token, if present
-	AccessCreateAt      time.Time `validate:"required_with=Access" bun:"type:timestamptz,nullzero"`                // User level access token created time, if access present
-	AccessExpiresAt     time.Time `validate:"-" bun:"type:timestamptz,nullzero"`                                   // User level access token expires at -- null means the token never expires
-	Refresh             string    `validate:"-" bun:",pk,nullzero,notnull,default:''"`                             // Refresh token, if present
-	RefreshCreateAt     time.Time `validate:"required_with=Refresh" bun:"type:timestamptz,nullzero"`               // Refresh created at, if refresh present
-	RefreshExpiresAt    time.Time `validate:"-" bun:"type:timestamptz,nullzero"`                                   // Refresh expires at -- null means the refresh token never expires
+	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"` // when was item created
+	UpdatedAt           time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	ClientID            string    `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the client who owns this token
+	UserID              string    `bun:"type:CHAR(26),nullzero"`                                      // ID of the user who owns this token
+	RedirectURI         string    `bun:",nullzero,notnull"`                                           // Oauth redirect URI for this token
+	Scope               string    `bun:",notnull"`                                                    // Oauth scope
+	Code                string    `bun:",pk,nullzero,notnull,default:''"`                             // Code, if present
+	CodeChallenge       string    `bun:",nullzero"`                                                   // Code challenge, if code present
+	CodeChallengeMethod string    `bun:",nullzero"`                                                   // Code challenge method, if code present
+	CodeCreateAt        time.Time `bun:"type:timestamptz,nullzero"`                                   // Code created time, if code present
+	CodeExpiresAt       time.Time `bun:"type:timestamptz,nullzero"`                                   // Code expires at -- null means the code never expires
+	Access              string    `bun:",pk,nullzero,notnull,default:''"`                             // User level access token, if present
+	AccessCreateAt      time.Time `bun:"type:timestamptz,nullzero"`                                   // User level access token created time, if access present
+	AccessExpiresAt     time.Time `bun:"type:timestamptz,nullzero"`                                   // User level access token expires at -- null means the token never expires
+	Refresh             string    `bun:",pk,nullzero,notnull,default:''"`                             // Refresh token, if present
+	RefreshCreateAt     time.Time `bun:"type:timestamptz,nullzero"`                                   // Refresh created at, if refresh present
+	RefreshExpiresAt    time.Time `bun:"type:timestamptz,nullzero"`                                   // Refresh expires at -- null means the refresh token never expires
 }
diff --git a/internal/gtsmodel/tombstone.go b/internal/gtsmodel/tombstone.go
index f7e1c2504..7c6af6de3 100644
--- a/internal/gtsmodel/tombstone.go
+++ b/internal/gtsmodel/tombstone.go
@@ -29,9 +29,9 @@ import (
 // It's useful in cases where a remote account has been deleted, and we don't want to keep trying to process
 // subsequent activities from that account, or deletes which target it.
 type Tombstone struct {
-	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Domain    string    `validate:"omitempty,fqdn" bun:",nullzero,notnull"`                              // Domain of the Object/Actor.
-	URI       string    `validate:"required,url" bun:",nullzero,notnull,unique"`                         // ActivityPub URI for this Object/Actor.
+	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"` // when was item created
+	UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Domain    string    `bun:",nullzero,notnull"`                                           // Domain of the Object/Actor.
+	URI       string    `bun:",nullzero,notnull,unique"`                                    // ActivityPub URI for this Object/Actor.
 }
diff --git a/internal/gtsmodel/user.go b/internal/gtsmodel/user.go
index 2f2029636..16c4d4ee2 100644
--- a/internal/gtsmodel/user.go
+++ b/internal/gtsmodel/user.go
@@ -25,35 +25,35 @@ import (
 // User represents an actual human user of gotosocial. Note, this is a LOCAL gotosocial user, not a remote account.
 // To cross reference this local user with their account (which can be local or remote), use the AccountID field.
 type User struct {
-	ID                     string       `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`        // id of this item in the database
-	CreatedAt              time.Time    `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
-	UpdatedAt              time.Time    `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
-	Email                  string       `validate:"required_with=ConfirmedAt" bun:",nullzero,unique"`                    // confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported
-	AccountID              string       `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull,unique"`           // The id of the local gtsmodel.Account entry for this user.
-	Account                *Account     `validate:"-" bun:"rel:belongs-to"`                                              // Pointer to the account of this user that corresponds to AccountID.
-	EncryptedPassword      string       `validate:"required" bun:",nullzero,notnull"`                                    // The encrypted password of this user, generated using https://pkg.go.dev/golang.org/x/crypto/bcrypt#GenerateFromPassword. A salt is included so we're safe against 🌈 tables.
-	SignUpIP               net.IP       `validate:"-" bun:",nullzero"`                                                   // From what IP was this user created?
-	CurrentSignInAt        time.Time    `validate:"-" bun:"type:timestamptz,nullzero"`                                   // When did the user sign in with their current session.
-	CurrentSignInIP        net.IP       `validate:"-" bun:",nullzero"`                                                   // What's the most recent IP of this user
-	LastSignInAt           time.Time    `validate:"-" bun:"type:timestamptz,nullzero"`                                   // When did this user last sign in?
-	LastSignInIP           net.IP       `validate:"-" bun:",nullzero"`                                                   // What's the previous IP of this user?
-	SignInCount            int          `validate:"min=0" bun:",notnull,default:0"`                                      // How many times has this user signed in?
-	InviteID               string       `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                         // id of the user who invited this user (who let this joker in?)
-	ChosenLanguages        []string     `validate:"-" bun:",nullzero"`                                                   // What languages does this user want to see?
-	FilteredLanguages      []string     `validate:"-" bun:",nullzero"`                                                   // What languages does this user not want to see?
-	Locale                 string       `validate:"-" bun:",nullzero"`                                                   // In what timezone/locale is this user located?
-	CreatedByApplicationID string       `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                         // Which application id created this user? See gtsmodel.Application
-	CreatedByApplication   *Application `validate:"-" bun:"rel:belongs-to"`                                              // Pointer to the application corresponding to createdbyapplicationID.
-	LastEmailedAt          time.Time    `validate:"-" bun:"type:timestamptz,nullzero"`                                   // When was this user last contacted by email.
-	ConfirmationToken      string       `validate:"required_with=ConfirmationSentAt" bun:",nullzero"`                    // What confirmation token did we send this user/what are we expecting back?
-	ConfirmationSentAt     time.Time    `validate:"required_with=ConfirmationToken" bun:"type:timestamptz,nullzero"`     // When did we send email confirmation to this user?
-	ConfirmedAt            time.Time    `validate:"required_with=Email" bun:"type:timestamptz,nullzero"`                 // When did the user confirm their email address
-	UnconfirmedEmail       string       `validate:"required_without=Email" bun:",nullzero"`                              // Email address that hasn't yet been confirmed
-	Moderator              *bool        `validate:"-" bun:",nullzero,notnull,default:false"`                             // Is this user a moderator?
-	Admin                  *bool        `validate:"-" bun:",nullzero,notnull,default:false"`                             // Is this user an admin?
-	Disabled               *bool        `validate:"-" bun:",nullzero,notnull,default:false"`                             // Is this user disabled from posting?
-	Approved               *bool        `validate:"-" bun:",nullzero,notnull,default:false"`                             // Has this user been approved by a moderator?
-	ResetPasswordToken     string       `validate:"required_with=ResetPasswordSentAt" bun:",nullzero"`                   // The generated token that the user can use to reset their password
-	ResetPasswordSentAt    time.Time    `validate:"required_with=ResetPasswordToken" bun:"type:timestamptz,nullzero"`    // When did we email the user their reset-password email?
-	ExternalID             string       `validate:"-" bun:",nullzero,unique"`                                            // If the login for the user is managed externally (e.g OIDC), we need to keep a stable reference to the external object (e.g OIDC sub claim)
+	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"` // when was item created
+	UpdatedAt              time.Time    `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+	Email                  string       `bun:",nullzero,unique"`                                            // confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported
+	AccountID              string       `bun:"type:CHAR(26),nullzero,notnull,unique"`                       // The id of the local gtsmodel.Account entry for this user.
+	Account                *Account     `bun:"rel:belongs-to"`                                              // Pointer to the account of this user that corresponds to AccountID.
+	EncryptedPassword      string       `bun:",nullzero,notnull"`                                           // The encrypted password of this user, generated using https://pkg.go.dev/golang.org/x/crypto/bcrypt#GenerateFromPassword. A salt is included so we're safe against 🌈 tables.
+	SignUpIP               net.IP       `bun:",nullzero"`                                                   // From what IP was this user created?
+	CurrentSignInAt        time.Time    `bun:"type:timestamptz,nullzero"`                                   // When did the user sign in with their current session.
+	CurrentSignInIP        net.IP       `bun:",nullzero"`                                                   // What's the most recent IP of this user
+	LastSignInAt           time.Time    `bun:"type:timestamptz,nullzero"`                                   // When did this user last sign in?
+	LastSignInIP           net.IP       `bun:",nullzero"`                                                   // What's the previous IP of this user?
+	SignInCount            int          `bun:",notnull,default:0"`                                          // How many times has this user signed in?
+	InviteID               string       `bun:"type:CHAR(26),nullzero"`                                      // id of the user who invited this user (who let this joker in?)
+	ChosenLanguages        []string     `bun:",nullzero"`                                                   // What languages does this user want to see?
+	FilteredLanguages      []string     `bun:",nullzero"`                                                   // What languages does this user not want to see?
+	Locale                 string       `bun:",nullzero"`                                                   // In what timezone/locale is this user located?
+	CreatedByApplicationID string       `bun:"type:CHAR(26),nullzero"`                                      // Which application id created this user? See gtsmodel.Application
+	CreatedByApplication   *Application `bun:"rel:belongs-to"`                                              // Pointer to the application corresponding to createdbyapplicationID.
+	LastEmailedAt          time.Time    `bun:"type:timestamptz,nullzero"`                                   // When was this user last contacted by email.
+	ConfirmationToken      string       `bun:",nullzero"`                                                   // What confirmation token did we send this user/what are we expecting back?
+	ConfirmationSentAt     time.Time    `bun:"type:timestamptz,nullzero"`                                   // When did we send email confirmation to this user?
+	ConfirmedAt            time.Time    `bun:"type:timestamptz,nullzero"`                                   // When did the user confirm their email address
+	UnconfirmedEmail       string       `bun:",nullzero"`                                                   // Email address that hasn't yet been confirmed
+	Moderator              *bool        `bun:",nullzero,notnull,default:false"`                             // Is this user a moderator?
+	Admin                  *bool        `bun:",nullzero,notnull,default:false"`                             // Is this user an admin?
+	Disabled               *bool        `bun:",nullzero,notnull,default:false"`                             // Is this user disabled from posting?
+	Approved               *bool        `bun:",nullzero,notnull,default:false"`                             // Has this user been approved by a moderator?
+	ResetPasswordToken     string       `bun:",nullzero"`                                                   // The generated token that the user can use to reset their password
+	ResetPasswordSentAt    time.Time    `bun:"type:timestamptz,nullzero"`                                   // When did we email the user their reset-password email?
+	ExternalID             string       `bun:",nullzero,unique"`                                            // If the login for the user is managed externally (e.g OIDC), we need to keep a stable reference to the external object (e.g OIDC sub claim)
 }
diff --git a/internal/validate/account_test.go b/internal/validate/account_test.go
deleted file mode 100644
index 57f0f4900..000000000
--- a/internal/validate/account_test.go
+++ /dev/null
@@ -1,343 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"crypto/rand"
-	"crypto/rsa"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/ap"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-	"github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-func happyAccount() *gtsmodel.Account {
-	priv, err := rsa.GenerateKey(rand.Reader, 2048)
-	if err != nil {
-		panic(err)
-	}
-	pub := &priv.PublicKey
-
-	return &gtsmodel.Account{
-		ID:                      "01F8MH1H7YV1Z7D2C8K2730QBF",
-		CreatedAt:               time.Now().Add(-48 * time.Hour),
-		UpdatedAt:               time.Now().Add(-48 * time.Hour),
-		Username:                "the_mighty_zork",
-		Domain:                  "",
-		AvatarMediaAttachmentID: "01F8MH58A357CV5K7R7TJMSH6S",
-		AvatarMediaAttachment:   nil,
-		AvatarRemoteURL:         "",
-		HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3Q",
-		HeaderMediaAttachment:   nil,
-		HeaderRemoteURL:         "",
-		DisplayName:             "original zork (he/they)",
-		Fields:                  []*gtsmodel.Field{},
-		Note:                    "hey yo this is my profile!",
-		Memorial:                testrig.FalseBool(),
-		AlsoKnownAs:             "",
-		MovedToAccountID:        "",
-		Bot:                     testrig.FalseBool(),
-		Reason:                  "I wanna be on this damned webbed site so bad! Please! Wow",
-		Locked:                  testrig.FalseBool(),
-		Discoverable:            testrig.TrueBool(),
-		Privacy:                 gtsmodel.VisibilityPublic,
-		Sensitive:               testrig.FalseBool(),
-		Language:                "en",
-		StatusContentType:       "text/plain",
-		URI:                     "http://localhost:8080/users/the_mighty_zork",
-		URL:                     "http://localhost:8080/@the_mighty_zork",
-		FetchedAt:               time.Time{},
-		InboxURI:                "http://localhost:8080/users/the_mighty_zork/inbox",
-		OutboxURI:               "http://localhost:8080/users/the_mighty_zork/outbox",
-		FollowersURI:            "http://localhost:8080/users/the_mighty_zork/followers",
-		FollowingURI:            "http://localhost:8080/users/the_mighty_zork/following",
-		FeaturedCollectionURI:   "http://localhost:8080/users/the_mighty_zork/collections/featured",
-		ActorType:               ap.ActorPerson,
-		PrivateKey:              priv,
-		PublicKey:               pub,
-		PublicKeyURI:            "http://localhost:8080/users/the_mighty_zork#main-key",
-		SensitizedAt:            time.Time{},
-		SilencedAt:              time.Time{},
-		SuspendedAt:             time.Time{},
-		HideCollections:         testrig.FalseBool(),
-		SuspensionOrigin:        "",
-	}
-}
-
-type AccountValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *AccountValidateTestSuite) TestValidateAccountHappyPath() {
-	// no problem here
-	a := happyAccount()
-	err := validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// ID must be set and be valid ULID
-func (suite *AccountValidateTestSuite) TestValidateAccountBadID() {
-	a := happyAccount()
-
-	a.ID = ""
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	a.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-// CreatedAt can be set or not -- it will be set in the database anyway
-func (suite *AccountValidateTestSuite) TestValidateAccountNoCreatedAt() {
-	a := happyAccount()
-
-	a.CreatedAt = time.Time{}
-	err := validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// FetchedAt must be defined if remote account
-func (suite *AccountValidateTestSuite) TestValidateAccountNoWebfingeredAt() {
-	a := happyAccount()
-
-	a.Domain = "example.org"
-	a.FetchedAt = time.Time{}
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.FetchedAt' Error:Field validation for 'FetchedAt' failed on the 'required_with' tag")
-}
-
-// Username must be set
-func (suite *AccountValidateTestSuite) TestValidateAccountUsername() {
-	a := happyAccount()
-
-	a.Username = ""
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Username' Error:Field validation for 'Username' failed on the 'required' tag")
-}
-
-// Domain must be either empty (for local accounts) or proper fqdn (for remote accounts)
-func (suite *AccountValidateTestSuite) TestValidateAccountDomain() {
-	a := happyAccount()
-	a.FetchedAt = time.Now()
-
-	a.Domain = ""
-	err := validate.Struct(*a)
-	suite.NoError(err)
-
-	a.Domain = "localhost:8080"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-
-	a.Domain = "ahhhhh"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-
-	a.Domain = "https://www.example.org"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-
-	a.Domain = "example.org:8080"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-
-	a.Domain = "example.org"
-	err = validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// Attachment IDs must either be not set, or must be valid ULID
-func (suite *AccountValidateTestSuite) TestValidateAttachmentIDs() {
-	a := happyAccount()
-
-	a.AvatarMediaAttachmentID = ""
-	a.HeaderMediaAttachmentID = ""
-	err := validate.Struct(*a)
-	suite.NoError(err)
-
-	a.AvatarMediaAttachmentID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	a.HeaderMediaAttachmentID = "aaaa"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.AvatarMediaAttachmentID' Error:Field validation for 'AvatarMediaAttachmentID' failed on the 'ulid' tag\nKey: 'Account.HeaderMediaAttachmentID' Error:Field validation for 'HeaderMediaAttachmentID' failed on the 'ulid' tag")
-}
-
-// Attachment remote URLs must either not be set, or be valid URLs
-func (suite *AccountValidateTestSuite) TestValidateAttachmentRemoteURLs() {
-	a := happyAccount()
-
-	a.AvatarRemoteURL = ""
-	a.HeaderRemoteURL = ""
-	err := validate.Struct(*a)
-	suite.NoError(err)
-
-	a.AvatarRemoteURL = "-------------"
-	a.HeaderRemoteURL = "https://valid-url.com"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.AvatarRemoteURL' Error:Field validation for 'AvatarRemoteURL' failed on the 'url' tag")
-
-	a.AvatarRemoteURL = "https://valid-url.com"
-	a.HeaderRemoteURL = ""
-	err = validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// Default privacy must be set if account is local
-func (suite *AccountValidateTestSuite) TestValidatePrivacy() {
-	a := happyAccount()
-	a.FetchedAt = time.Now()
-
-	a.Privacy = ""
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Privacy' Error:Field validation for 'Privacy' failed on the 'required_without' tag")
-
-	a.Privacy = "not valid"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Privacy' Error:Field validation for 'Privacy' failed on the 'oneof' tag")
-
-	a.Privacy = gtsmodel.VisibilityFollowersOnly
-	err = validate.Struct(*a)
-	suite.NoError(err)
-
-	a.Privacy = ""
-	a.Domain = "example.org"
-	err = validate.Struct(*a)
-	suite.NoError(err)
-
-	a.Privacy = "invalid"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Privacy' Error:Field validation for 'Privacy' failed on the 'oneof' tag")
-}
-
-// If set, language must be a valid language
-func (suite *AccountValidateTestSuite) TestValidateLanguage() {
-	a := happyAccount()
-
-	a.Language = ""
-	err := validate.Struct(*a)
-	suite.NoError(err)
-
-	a.Language = "not valid"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.Language' Error:Field validation for 'Language' failed on the 'bcp47_language_tag' tag")
-
-	a.Language = "en-uk"
-	err = validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// Account URI must be set and must be valid
-func (suite *AccountValidateTestSuite) TestValidateAccountURI() {
-	a := happyAccount()
-
-	a.URI = "invalid-uri"
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-
-	a.URI = ""
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.URI' Error:Field validation for 'URI' failed on the 'required' tag")
-}
-
-// ActivityPub URIs must be set on account if it's local
-func (suite *AccountValidateTestSuite) TestValidateAccountURIs() {
-	a := happyAccount()
-	a.FetchedAt = time.Now()
-
-	a.InboxURI = "invalid-uri"
-	a.OutboxURI = "invalid-uri"
-	a.FollowersURI = "invalid-uri"
-	a.FollowingURI = "invalid-uri"
-	a.FeaturedCollectionURI = "invalid-uri"
-	a.PublicKeyURI = "invalid-uri"
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.InboxURI' Error:Field validation for 'InboxURI' failed on the 'url' tag\nKey: 'Account.OutboxURI' Error:Field validation for 'OutboxURI' failed on the 'url' tag\nKey: 'Account.FollowingURI' Error:Field validation for 'FollowingURI' failed on the 'url' tag\nKey: 'Account.FollowersURI' Error:Field validation for 'FollowersURI' failed on the 'url' tag\nKey: 'Account.FeaturedCollectionURI' Error:Field validation for 'FeaturedCollectionURI' failed on the 'url' tag\nKey: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'url' tag")
-
-	a.InboxURI = ""
-	a.OutboxURI = ""
-	a.FollowersURI = ""
-	a.FollowingURI = ""
-	a.FeaturedCollectionURI = ""
-	a.PublicKeyURI = ""
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.InboxURI' Error:Field validation for 'InboxURI' failed on the 'required_without' tag\nKey: 'Account.OutboxURI' Error:Field validation for 'OutboxURI' failed on the 'required_without' tag\nKey: 'Account.FollowingURI' Error:Field validation for 'FollowingURI' failed on the 'required_without' tag\nKey: 'Account.FollowersURI' Error:Field validation for 'FollowersURI' failed on the 'required_without' tag\nKey: 'Account.FeaturedCollectionURI' Error:Field validation for 'FeaturedCollectionURI' failed on the 'required_without' tag\nKey: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'required' tag")
-
-	a.Domain = "example.org"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'required' tag")
-
-	a.InboxURI = "invalid-uri"
-	a.OutboxURI = "invalid-uri"
-	a.FollowersURI = "invalid-uri"
-	a.FollowingURI = "invalid-uri"
-	a.FeaturedCollectionURI = "invalid-uri"
-	a.PublicKeyURI = "invalid-uri"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.InboxURI' Error:Field validation for 'InboxURI' failed on the 'url' tag\nKey: 'Account.OutboxURI' Error:Field validation for 'OutboxURI' failed on the 'url' tag\nKey: 'Account.FollowingURI' Error:Field validation for 'FollowingURI' failed on the 'url' tag\nKey: 'Account.FollowersURI' Error:Field validation for 'FollowersURI' failed on the 'url' tag\nKey: 'Account.FeaturedCollectionURI' Error:Field validation for 'FeaturedCollectionURI' failed on the 'url' tag\nKey: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'url' tag")
-}
-
-// Actor type must be set and valid
-func (suite *AccountValidateTestSuite) TestValidateActorType() {
-	a := happyAccount()
-
-	a.ActorType = ""
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.ActorType' Error:Field validation for 'ActorType' failed on the 'oneof' tag")
-
-	a.ActorType = "not valid"
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.ActorType' Error:Field validation for 'ActorType' failed on the 'oneof' tag")
-
-	a.ActorType = ap.ActivityArrive
-	err = validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.ActorType' Error:Field validation for 'ActorType' failed on the 'oneof' tag")
-
-	a.ActorType = ap.ActorOrganization
-	err = validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// Private key must be set on local accounts
-func (suite *AccountValidateTestSuite) TestValidatePrivateKey() {
-	a := happyAccount()
-	a.FetchedAt = time.Now()
-
-	a.PrivateKey = nil
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.PrivateKey' Error:Field validation for 'PrivateKey' failed on the 'required_without' tag")
-
-	a.Domain = "example.org"
-	err = validate.Struct(*a)
-	suite.NoError(err)
-}
-
-// Public key must be set
-func (suite *AccountValidateTestSuite) TestValidatePublicKey() {
-	a := happyAccount()
-
-	a.PublicKey = nil
-	err := validate.Struct(*a)
-	suite.EqualError(err, "Key: 'Account.PublicKey' Error:Field validation for 'PublicKey' failed on the 'required' tag")
-}
-
-func TestAccountValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(AccountValidateTestSuite))
-}
diff --git a/internal/validate/application_test.go b/internal/validate/application_test.go
deleted file mode 100644
index 86c53a615..000000000
--- a/internal/validate/application_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyApplication() *gtsmodel.Application {
-	return &gtsmodel.Application{
-		ID:           "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:    time.Now(),
-		UpdatedAt:    time.Now(),
-		Name:         "Tusky",
-		Website:      "https://tusky.app",
-		RedirectURI:  "oauth2redirect://com.keylesspalace.tusky/",
-		ClientID:     "01FEEDMF6C0QD589MRK7919Z0R",
-		ClientSecret: "bd740cf1-024a-4e4d-8c39-866538f52fe6",
-		Scopes:       "read write follow",
-	}
-}
-
-type ApplicationValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationHappyPath() {
-	// no problem here
-	a := happyApplication()
-	err := validate.Struct(a)
-	suite.NoError(err)
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationBadID() {
-	a := happyApplication()
-
-	a.ID = ""
-	err := validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	a.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationNoCreatedAt() {
-	a := happyApplication()
-
-	a.CreatedAt = time.Time{}
-	err := validate.Struct(a)
-	suite.NoError(err)
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationName() {
-	a := happyApplication()
-
-	a.Name = ""
-	err := validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.Name' Error:Field validation for 'Name' failed on the 'required' tag")
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationWebsite() {
-	a := happyApplication()
-
-	a.Website = "invalid-website"
-	err := validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.Website' Error:Field validation for 'Website' failed on the 'url' tag")
-
-	a.Website = ""
-	err = validate.Struct(a)
-	suite.NoError(err)
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationRedirectURI() {
-	a := happyApplication()
-
-	a.RedirectURI = "invalid-uri"
-	err := validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'uri' tag")
-
-	a.RedirectURI = ""
-	err = validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'required' tag")
-
-	a.RedirectURI = "urn:ietf:wg:oauth:2.0:oob"
-	err = validate.Struct(a)
-	suite.NoError(err)
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationClientSecret() {
-	a := happyApplication()
-
-	a.ClientSecret = "invalid-uuid"
-	err := validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.ClientSecret' Error:Field validation for 'ClientSecret' failed on the 'uuid' tag")
-
-	a.ClientSecret = ""
-	err = validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.ClientSecret' Error:Field validation for 'ClientSecret' failed on the 'required' tag")
-}
-
-func (suite *ApplicationValidateTestSuite) TestValidateApplicationScopes() {
-	a := happyApplication()
-
-	a.Scopes = ""
-	err := validate.Struct(a)
-	suite.EqualError(err, "Key: 'Application.Scopes' Error:Field validation for 'Scopes' failed on the 'required' tag")
-}
-
-func TestApplicationValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(ApplicationValidateTestSuite))
-}
diff --git a/internal/validate/block_test.go b/internal/validate/block_test.go
deleted file mode 100644
index 96e206140..000000000
--- a/internal/validate/block_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyBlock() *gtsmodel.Block {
-	return &gtsmodel.Block{
-		ID:              "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:       time.Now(),
-		UpdatedAt:       time.Now(),
-		URI:             "https://example.org/accounts/someone/blocks/01FE91RJR88PSEEE30EV35QR8N",
-		AccountID:       "01FEED79PRMVWPRMFHFQM8MJQN",
-		Account:         nil,
-		TargetAccountID: "01FEEDMF6C0QD589MRK7919Z0R",
-		TargetAccount:   nil,
-	}
-}
-
-type BlockValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *BlockValidateTestSuite) TestValidateBlockHappyPath() {
-	// no problem here
-	b := happyBlock()
-	err := validate.Struct(b)
-	suite.NoError(err)
-}
-
-func (suite *BlockValidateTestSuite) TestValidateBlockBadID() {
-	b := happyBlock()
-
-	b.ID = ""
-	err := validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	b.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *BlockValidateTestSuite) TestValidateBlockNoCreatedAt() {
-	b := happyBlock()
-
-	b.CreatedAt = time.Time{}
-	err := validate.Struct(b)
-	suite.NoError(err)
-}
-
-func (suite *BlockValidateTestSuite) TestValidateBlockCreatedByAccountID() {
-	b := happyBlock()
-
-	b.AccountID = ""
-	err := validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag")
-
-	b.AccountID = "this-is-not-a-valid-ulid"
-	err = validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'ulid' tag")
-}
-
-func (suite *BlockValidateTestSuite) TestValidateBlockTargetAccountID() {
-	b := happyBlock()
-
-	b.TargetAccountID = "invalid-ulid"
-	err := validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'ulid' tag")
-
-	b.TargetAccountID = "01FEEDHX4G7EGHF5GD9E82Y51Q"
-	err = validate.Struct(b)
-	suite.NoError(err)
-
-	b.TargetAccountID = ""
-	err = validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'required' tag")
-}
-
-func (suite *BlockValidateTestSuite) TestValidateBlockURI() {
-	b := happyBlock()
-
-	b.URI = "invalid-uri"
-	err := validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-
-	b.URI = ""
-	err = validate.Struct(b)
-	suite.EqualError(err, "Key: 'Block.URI' Error:Field validation for 'URI' failed on the 'required' tag")
-}
-
-func TestBlockValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(BlockValidateTestSuite))
-}
diff --git a/internal/validate/client_test.go b/internal/validate/client_test.go
deleted file mode 100644
index ee8f0e66e..000000000
--- a/internal/validate/client_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyClient() *gtsmodel.Client {
-	return &gtsmodel.Client{
-		ID:        "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt: time.Now(),
-		UpdatedAt: time.Now(),
-		Secret:    "bd740cf1-024a-4e4d-8c39-866538f52fe6",
-		Domain:    "oauth2redirect://com.keylesspalace.tusky/",
-		UserID:    "01FEEDMF6C0QD589MRK7919Z0R",
-	}
-}
-
-type ClientValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *ClientValidateTestSuite) TestValidateClientHappyPath() {
-	// no problem here
-	c := happyClient()
-	err := validate.Struct(c)
-	suite.NoError(err)
-}
-
-func (suite *ClientValidateTestSuite) TestValidateClientBadID() {
-	c := happyClient()
-
-	c.ID = ""
-	err := validate.Struct(c)
-	suite.EqualError(err, "Key: 'Client.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	c.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(c)
-	suite.EqualError(err, "Key: 'Client.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *ClientValidateTestSuite) TestValidateClientNoCreatedAt() {
-	c := happyClient()
-
-	c.CreatedAt = time.Time{}
-	err := validate.Struct(c)
-	suite.NoError(err)
-}
-
-func (suite *ClientValidateTestSuite) TestValidateClientDomain() {
-	c := happyClient()
-
-	c.Domain = "invalid-uri"
-	err := validate.Struct(c)
-	suite.EqualError(err, "Key: 'Client.Domain' Error:Field validation for 'Domain' failed on the 'uri' tag")
-
-	c.Domain = ""
-	err = validate.Struct(c)
-	suite.EqualError(err, "Key: 'Client.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
-
-	c.Domain = "urn:ietf:wg:oauth:2.0:oob"
-	err = validate.Struct(c)
-	suite.NoError(err)
-}
-
-func (suite *ClientValidateTestSuite) TestValidateSecret() {
-	c := happyClient()
-
-	c.Secret = "invalid-uuid"
-	err := validate.Struct(c)
-	suite.EqualError(err, "Key: 'Client.Secret' Error:Field validation for 'Secret' failed on the 'uuid' tag")
-
-	c.Secret = ""
-	err = validate.Struct(c)
-	suite.EqualError(err, "Key: 'Client.Secret' Error:Field validation for 'Secret' failed on the 'required' tag")
-}
-
-func TestClientValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(ClientValidateTestSuite))
-}
diff --git a/internal/validate/domainblock_test.go b/internal/validate/domainblock_test.go
deleted file mode 100644
index f6504d1f3..000000000
--- a/internal/validate/domainblock_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-	"github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-func happyDomainBlock() *gtsmodel.DomainBlock {
-	return &gtsmodel.DomainBlock{
-		ID:                 "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:          time.Now(),
-		UpdatedAt:          time.Now(),
-		Domain:             "baddudes.suck",
-		CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN",
-		PrivateComment:     "we don't like em",
-		PublicComment:      "poo poo dudes",
-		Obfuscate:          testrig.FalseBool(),
-		SubscriptionID:     "",
-	}
-}
-
-type DomainBlockValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockHappyPath() {
-	// no problem here
-	d := happyDomainBlock()
-	err := validate.Struct(d)
-	suite.NoError(err)
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockBadID() {
-	d := happyDomainBlock()
-
-	d.ID = ""
-	err := validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	d.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockNoCreatedAt() {
-	d := happyDomainBlock()
-
-	d.CreatedAt = time.Time{}
-	err := validate.Struct(d)
-	suite.NoError(err)
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockBadDomain() {
-	d := happyDomainBlock()
-
-	d.Domain = ""
-	err := validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
-
-	d.Domain = "this-is-not-a-valid-domain"
-	err = validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockCreatedByAccountID() {
-	d := happyDomainBlock()
-
-	d.CreatedByAccountID = ""
-	err := validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'required' tag")
-
-	d.CreatedByAccountID = "this-is-not-a-valid-ulid"
-	err = validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'ulid' tag")
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockComments() {
-	d := happyDomainBlock()
-
-	d.PrivateComment = ""
-	d.PublicComment = ""
-	err := validate.Struct(d)
-	suite.NoError(err)
-}
-
-func (suite *DomainBlockValidateTestSuite) TestValidateDomainSubscriptionID() {
-	d := happyDomainBlock()
-
-	d.SubscriptionID = "invalid-ulid"
-	err := validate.Struct(d)
-	suite.EqualError(err, "Key: 'DomainBlock.SubscriptionID' Error:Field validation for 'SubscriptionID' failed on the 'ulid' tag")
-
-	d.SubscriptionID = "01FEEDHX4G7EGHF5GD9E82Y51Q"
-	err = validate.Struct(d)
-	suite.NoError(err)
-}
-
-func TestDomainBlockValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(DomainBlockValidateTestSuite))
-}
diff --git a/internal/validate/emaildomainblock_test.go b/internal/validate/emaildomainblock_test.go
deleted file mode 100644
index 2aac0d72c..000000000
--- a/internal/validate/emaildomainblock_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyEmailDomainBlock() *gtsmodel.EmailDomainBlock {
-	return &gtsmodel.EmailDomainBlock{
-		ID:                 "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:          time.Now(),
-		UpdatedAt:          time.Now(),
-		Domain:             "baddudes.suck",
-		CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN",
-	}
-}
-
-type EmailDomainBlockValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockHappyPath() {
-	// no problem here
-	e := happyEmailDomainBlock()
-	err := validate.Struct(e)
-	suite.NoError(err)
-}
-
-func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadID() {
-	e := happyEmailDomainBlock()
-
-	e.ID = ""
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	e.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockNoCreatedAt() {
-	e := happyEmailDomainBlock()
-
-	e.CreatedAt = time.Time{}
-	err := validate.Struct(e)
-	suite.NoError(err)
-}
-
-func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadDomain() {
-	e := happyEmailDomainBlock()
-
-	e.Domain = ""
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'EmailDomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
-
-	e.Domain = "this-is-not-a-valid-domain"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'EmailDomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-}
-
-func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockCreatedByAccountID() {
-	e := happyEmailDomainBlock()
-
-	e.CreatedByAccountID = ""
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'EmailDomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'required' tag")
-
-	e.CreatedByAccountID = "this-is-not-a-valid-ulid"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'EmailDomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'ulid' tag")
-}
-
-func TestEmailDomainBlockValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(EmailDomainBlockValidateTestSuite))
-}
diff --git a/internal/validate/emoji_test.go b/internal/validate/emoji_test.go
deleted file mode 100644
index 9192cb1fc..000000000
--- a/internal/validate/emoji_test.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-	"github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-func happyEmoji() *gtsmodel.Emoji {
-	// the file validator actually runs os.Stat on given paths, so we need to just create small
-	// temp files for both the main attachment file and the thumbnail
-
-	imageFile, err := os.CreateTemp("", "gts_test_emoji")
-	if err != nil {
-		panic(err)
-	}
-	if _, err := imageFile.WriteString("main"); err != nil {
-		panic(err)
-	}
-	imagePath := imageFile.Name()
-	if err := imageFile.Close(); err != nil {
-		panic(err)
-	}
-
-	staticFile, err := os.CreateTemp("", "gts_test_emoji_static")
-	if err != nil {
-		panic(err)
-	}
-	if _, err := staticFile.WriteString("thumbnail"); err != nil {
-		panic(err)
-	}
-	imageStaticPath := staticFile.Name()
-	if err := staticFile.Close(); err != nil {
-		panic(err)
-	}
-
-	return &gtsmodel.Emoji{
-		ID:                     "01F8MH6NEM8D7527KZAECTCR76",
-		CreatedAt:              time.Now().Add(-71 * time.Hour),
-		UpdatedAt:              time.Now().Add(-71 * time.Hour),
-		Shortcode:              "blob_test",
-		Domain:                 "example.org",
-		ImageRemoteURL:         "https://example.org/emojis/blob_test.gif",
-		ImageStaticRemoteURL:   "https://example.org/emojis/blob_test.png",
-		ImageURL:               "",
-		ImageStaticURL:         "",
-		ImagePath:              imagePath,
-		ImageStaticPath:        imageStaticPath,
-		ImageContentType:       "image/gif",
-		ImageStaticContentType: "image/png",
-		ImageFileSize:          1024,
-		ImageStaticFileSize:    256,
-		ImageUpdatedAt:         time.Now(),
-		Disabled:               testrig.FalseBool(),
-		URI:                    "https://example.org/emojis/blob_test",
-		VisibleInPicker:        testrig.TrueBool(),
-		CategoryID:             "01FEE47ZH70PWDSEAVBRFNX325",
-	}
-}
-
-type EmojiValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *EmojiValidateTestSuite) TestValidateEmojiHappyPath() {
-	// no problem here
-	m := happyEmoji()
-	err := validate.Struct(*m)
-	suite.NoError(err)
-}
-
-func (suite *EmojiValidateTestSuite) TestValidateEmojiBadFilePaths() {
-	e := happyEmoji()
-
-	e.ImagePath = "/tmp/nonexistent/file/for/gotosocial/test"
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag")
-
-	e.ImagePath = ""
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'required' tag")
-
-	e.ImagePath = "???????????thisnot a valid path####"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag")
-
-	e.ImageStaticPath = "/tmp/nonexistent/file/for/gotosocial/test"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag\nKey: 'Emoji.ImageStaticPath' Error:Field validation for 'ImageStaticPath' failed on the 'file' tag")
-
-	e.ImageStaticPath = ""
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag\nKey: 'Emoji.ImageStaticPath' Error:Field validation for 'ImageStaticPath' failed on the 'required' tag")
-
-	e.ImageStaticPath = "???????????thisnot a valid path####"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag\nKey: 'Emoji.ImageStaticPath' Error:Field validation for 'ImageStaticPath' failed on the 'file' tag")
-}
-
-func (suite *EmojiValidateTestSuite) TestValidateEmojiURI() {
-	e := happyEmoji()
-
-	e.URI = "aaaaaaaaaa"
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-
-	e.URI = ""
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-}
-
-func (suite *EmojiValidateTestSuite) TestValidateEmojiURLCombos() {
-	e := happyEmoji()
-
-	e.ImageRemoteURL = ""
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageRemoteURL' Error:Field validation for 'ImageRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required_without' tag")
-
-	e.ImageURL = "https://whatever.org"
-	err = validate.Struct(e)
-	suite.NoError(err)
-
-	e.ImageStaticRemoteURL = ""
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageStaticRemoteURL' Error:Field validation for 'ImageStaticRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticURL' Error:Field validation for 'ImageStaticURL' failed on the 'required_without' tag")
-
-	e.ImageStaticURL = "https://whatever.org"
-	err = validate.Struct(e)
-	suite.NoError(err)
-
-	e.ImageURL = ""
-	e.ImageStaticURL = ""
-	e.ImageRemoteURL = ""
-	e.ImageStaticRemoteURL = ""
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageRemoteURL' Error:Field validation for 'ImageRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticRemoteURL' Error:Field validation for 'ImageStaticRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticURL' Error:Field validation for 'ImageStaticURL' failed on the 'required_without' tag")
-}
-
-func (suite *EmojiValidateTestSuite) TestValidateFileSize() {
-	e := happyEmoji()
-
-	e.ImageFileSize = 0
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'required' tag")
-
-	e.ImageStaticFileSize = 0
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'required' tag\nKey: 'Emoji.ImageStaticFileSize' Error:Field validation for 'ImageStaticFileSize' failed on the 'required' tag")
-
-	e.ImageFileSize = -1
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'min' tag\nKey: 'Emoji.ImageStaticFileSize' Error:Field validation for 'ImageStaticFileSize' failed on the 'required' tag")
-
-	e.ImageStaticFileSize = -1
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'min' tag\nKey: 'Emoji.ImageStaticFileSize' Error:Field validation for 'ImageStaticFileSize' failed on the 'min' tag")
-}
-
-func (suite *EmojiValidateTestSuite) TestValidateDomain() {
-	e := happyEmoji()
-
-	e.Domain = ""
-	err := validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticURL' Error:Field validation for 'ImageStaticURL' failed on the 'required_without' tag")
-
-	e.Domain = "aaaaaaaaa"
-	err = validate.Struct(e)
-	suite.EqualError(err, "Key: 'Emoji.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-}
-
-func TestEmojiValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(EmojiValidateTestSuite))
-}
diff --git a/internal/validate/follow_test.go b/internal/validate/follow_test.go
deleted file mode 100644
index 2128be4aa..000000000
--- a/internal/validate/follow_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyFollow() *gtsmodel.Follow {
-	return &gtsmodel.Follow{
-		ID:              "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:       time.Now(),
-		UpdatedAt:       time.Now(),
-		AccountID:       "01FE96MAE58MXCE5C4SSMEMCEK",
-		Account:         nil,
-		TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:   nil,
-		URI:             "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N",
-	}
-}
-
-type FollowValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *FollowValidateTestSuite) TestValidateFollowHappyPath() {
-	// no problem here
-	f := happyFollow()
-	err := validate.Struct(f)
-	suite.NoError(err)
-}
-
-func (suite *FollowValidateTestSuite) TestValidateFollowBadID() {
-	f := happyFollow()
-
-	f.ID = ""
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *FollowValidateTestSuite) TestValidateFollowNoCreatedAt() {
-	f := happyFollow()
-
-	f.CreatedAt = time.Time{}
-	err := validate.Struct(f)
-	suite.NoError(err)
-}
-
-func (suite *FollowValidateTestSuite) TestValidateFollowNoURI() {
-	f := happyFollow()
-
-	f.URI = ""
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'Follow.URI' Error:Field validation for 'URI' failed on the 'required' tag")
-
-	f.URI = "this-is-not-a-valid-url"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'Follow.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-}
-
-func TestFollowValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(FollowValidateTestSuite))
-}
diff --git a/internal/validate/followrequest_test.go b/internal/validate/followrequest_test.go
deleted file mode 100644
index 0c3d883bb..000000000
--- a/internal/validate/followrequest_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyFollowRequest() *gtsmodel.FollowRequest {
-	return &gtsmodel.FollowRequest{
-		ID:              "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:       time.Now(),
-		UpdatedAt:       time.Now(),
-		AccountID:       "01FE96MAE58MXCE5C4SSMEMCEK",
-		Account:         nil,
-		TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:   nil,
-		URI:             "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N",
-	}
-}
-
-type FollowRequestValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestHappyPath() {
-	// no problem here
-	f := happyFollowRequest()
-	err := validate.Struct(f)
-	suite.NoError(err)
-}
-
-func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestBadID() {
-	f := happyFollowRequest()
-
-	f.ID = ""
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoCreatedAt() {
-	f := happyFollowRequest()
-
-	f.CreatedAt = time.Time{}
-	err := validate.Struct(f)
-	suite.NoError(err)
-}
-
-func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoURI() {
-	f := happyFollowRequest()
-
-	f.URI = ""
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'FollowRequest.URI' Error:Field validation for 'URI' failed on the 'required' tag")
-
-	f.URI = "this-is-not-a-valid-url"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'FollowRequest.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-}
-
-func TestFollowRequestValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(FollowRequestValidateTestSuite))
-}
diff --git a/internal/validate/instance_test.go b/internal/validate/instance_test.go
deleted file mode 100644
index 38c68a616..000000000
--- a/internal/validate/instance_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyInstance() *gtsmodel.Instance {
-	return &gtsmodel.Instance{
-		ID:                     "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:              time.Now(),
-		UpdatedAt:              time.Now(),
-		Domain:                 "example.org",
-		Title:                  "Example Instance",
-		URI:                    "https://example.org",
-		SuspendedAt:            time.Time{},
-		DomainBlockID:          "",
-		DomainBlock:            nil,
-		ShortDescription:       "This is a description for the example/testing instance.",
-		Description:            "This is a way longer description for the example/testing instance!",
-		Terms:                  "Don't be a knobhead.",
-		ContactEmail:           "admin@example.org",
-		ContactAccountUsername: "admin",
-		ContactAccountID:       "01FEE20H5QWHJDEXAEE9G96PR0",
-		ContactAccount:         nil,
-		Reputation:             420,
-		Version:                "gotosocial 0.1.0",
-	}
-}
-
-type InstanceValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceHappyPath() {
-	// no problem here
-	m := happyInstance()
-	err := validate.Struct(*m)
-	suite.NoError(err)
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceBadID() {
-	m := happyInstance()
-
-	m.ID = ""
-	err := validate.Struct(*m)
-	suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(*m)
-	suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceAccountURI() {
-	i := happyInstance()
-
-	i.URI = ""
-	err := validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'required' tag")
-
-	i.URI = "---------------------------"
-	err = validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceDodgyAccountID() {
-	i := happyInstance()
-
-	i.ContactAccountID = "9HZJ76B6VXSKF"
-	err := validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag")
-
-	i.ContactAccountID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
-	err = validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag")
-
-	i.ContactAccountID = ""
-	err = validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'required_with' tag")
-
-	i.ContactAccountUsername = ""
-	err = validate.Struct(i)
-	suite.NoError(err)
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceDomain() {
-	i := happyInstance()
-
-	i.Domain = "poopoo"
-	err := validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-
-	i.Domain = ""
-	err = validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
-
-	i.Domain = "https://aaaaaaaaaaaaah.org"
-	err = validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceContactEmail() {
-	i := happyInstance()
-
-	i.ContactEmail = "poopoo"
-	err := validate.Struct(i)
-	suite.EqualError(err, "Key: 'Instance.ContactEmail' Error:Field validation for 'ContactEmail' failed on the 'email' tag")
-
-	i.ContactEmail = ""
-	err = validate.Struct(i)
-	suite.NoError(err)
-}
-
-func (suite *InstanceValidateTestSuite) TestValidateInstanceNoCreatedAt() {
-	i := happyInstance()
-
-	i.CreatedAt = time.Time{}
-	err := validate.Struct(i)
-	suite.NoError(err)
-}
-
-func TestInstanceValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(InstanceValidateTestSuite))
-}
diff --git a/internal/validate/mediaattachment_test.go b/internal/validate/mediaattachment_test.go
deleted file mode 100644
index 1021319f2..000000000
--- a/internal/validate/mediaattachment_test.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"os"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-	"github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-func happyMediaAttachment() *gtsmodel.MediaAttachment {
-	// the file validator actually runs os.Stat on given paths, so we need to just create small
-	// temp files for both the main attachment file and the thumbnail
-
-	mainFile, err := os.CreateTemp("", "gts_test_mainfile")
-	if err != nil {
-		panic(err)
-	}
-	if _, err := mainFile.WriteString("main"); err != nil {
-		panic(err)
-	}
-	mainPath := mainFile.Name()
-	if err := mainFile.Close(); err != nil {
-		panic(err)
-	}
-
-	thumbnailFile, err := os.CreateTemp("", "gts_test_thumbnail")
-	if err != nil {
-		panic(err)
-	}
-	if _, err := thumbnailFile.WriteString("thumbnail"); err != nil {
-		panic(err)
-	}
-	thumbnailPath := thumbnailFile.Name()
-	if err := thumbnailFile.Close(); err != nil {
-		panic(err)
-	}
-
-	return &gtsmodel.MediaAttachment{
-		ID:        "01F8MH6NEM8D7527KZAECTCR76",
-		CreatedAt: time.Now().Add(-71 * time.Hour),
-		UpdatedAt: time.Now().Add(-71 * time.Hour),
-		StatusID:  "01F8MH75CBF9JFX4ZAD54N0W0R",
-		URL:       "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
-		RemoteURL: "",
-		Type:      gtsmodel.FileTypeImage,
-		FileMeta: gtsmodel.FileMeta{
-			Original: gtsmodel.Original{
-				Width:  1200,
-				Height: 630,
-				Size:   756000,
-				Aspect: 1.9047619047619047,
-			},
-			Small: gtsmodel.Small{
-				Width:  256,
-				Height: 134,
-				Size:   34304,
-				Aspect: 1.9104477611940298,
-			},
-		},
-		AccountID:         "01F8MH17FWEB39HZJ76B6VXSKF",
-		Description:       "Black and white image of some 50's style text saying: Welcome On Board",
-		ScheduledStatusID: "",
-		Blurhash:          "LNJRdVM{00Rj%Mayt7j[4nWBofRj",
-		Processing:        2,
-		File: gtsmodel.File{
-			Path:        mainPath,
-			ContentType: "image/jpeg",
-			FileSize:    62529,
-			UpdatedAt:   time.Now().Add(-71 * time.Hour),
-		},
-		Thumbnail: gtsmodel.Thumbnail{
-			Path:        thumbnailPath,
-			ContentType: "image/jpeg",
-			FileSize:    6872,
-			UpdatedAt:   time.Now().Add(-71 * time.Hour),
-			URL:         "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpg",
-			RemoteURL:   "",
-		},
-		Avatar: testrig.FalseBool(),
-		Header: testrig.FalseBool(),
-	}
-}
-
-type MediaAttachmentValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentHappyPath() {
-	// no problem here
-	m := happyMediaAttachment()
-	err := validate.Struct(m)
-	suite.NoError(err)
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadFilePaths() {
-	m := happyMediaAttachment()
-
-	m.File.Path = "/tmp/nonexistent/file/for/gotosocial/test"
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag")
-
-	m.File.Path = ""
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'required' tag")
-
-	m.File.Path = "???????????thisnot a valid path####"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag")
-
-	m.Thumbnail.Path = "/tmp/nonexistent/file/for/gotosocial/test"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag\nKey: 'MediaAttachment.Thumbnail.Path' Error:Field validation for 'Path' failed on the 'file' tag")
-
-	m.Thumbnail.Path = ""
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag\nKey: 'MediaAttachment.Thumbnail.Path' Error:Field validation for 'Path' failed on the 'required' tag")
-
-	m.Thumbnail.Path = "???????????thisnot a valid path####"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag\nKey: 'MediaAttachment.Thumbnail.Path' Error:Field validation for 'Path' failed on the 'file' tag")
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadType() {
-	m := happyMediaAttachment()
-
-	m.Type = ""
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.Type' Error:Field validation for 'Type' failed on the 'oneof' tag")
-
-	m.Type = "Not Supported"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.Type' Error:Field validation for 'Type' failed on the 'oneof' tag")
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadFileMeta() {
-	m := happyMediaAttachment()
-
-	m.FileMeta.Original.Aspect = 0
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Original.Aspect' Error:Field validation for 'Aspect' failed on the 'required_with' tag")
-
-	m.FileMeta.Original.Height = 0
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Original.Height' Error:Field validation for 'Height' failed on the 'required_with' tag\nKey: 'MediaAttachment.FileMeta.Original.Aspect' Error:Field validation for 'Aspect' failed on the 'required_with' tag")
-
-	m.FileMeta.Original = gtsmodel.Original{}
-	err = validate.Struct(m)
-	suite.NoError(err)
-
-	m.FileMeta.Focus.X = 3.6
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Focus.X' Error:Field validation for 'X' failed on the 'max' tag")
-
-	m.FileMeta.Focus.Y = -50
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Focus.X' Error:Field validation for 'X' failed on the 'max' tag\nKey: 'MediaAttachment.FileMeta.Focus.Y' Error:Field validation for 'Y' failed on the 'min' tag")
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadURLCombos() {
-	m := happyMediaAttachment()
-
-	m.URL = "aaaaaaaaaa"
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.URL' Error:Field validation for 'URL' failed on the 'url' tag")
-
-	m.URL = ""
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.URL' Error:Field validation for 'URL' failed on the 'required_without' tag\nKey: 'MediaAttachment.RemoteURL' Error:Field validation for 'RemoteURL' failed on the 'required_without' tag")
-
-	m.RemoteURL = "oooooooooo"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.RemoteURL' Error:Field validation for 'RemoteURL' failed on the 'url' tag")
-
-	m.RemoteURL = "https://a-valid-url.gay"
-	err = validate.Struct(m)
-	suite.NoError(err)
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBlurhash() {
-	m := happyMediaAttachment()
-
-	m.Blurhash = ""
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.Blurhash' Error:Field validation for 'Blurhash' failed on the 'required_if' tag")
-
-	m.Type = gtsmodel.FileTypeAudio
-	err = validate.Struct(m)
-	suite.NoError(err)
-
-	m.Blurhash = "some_blurhash"
-	err = validate.Struct(m)
-	suite.NoError(err)
-}
-
-func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentProcessing() {
-	m := happyMediaAttachment()
-
-	m.Processing = 420
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.Processing' Error:Field validation for 'Processing' failed on the 'oneof' tag")
-
-	m.Processing = -5
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'MediaAttachment.Processing' Error:Field validation for 'Processing' failed on the 'oneof' tag")
-}
-
-func TestMediaAttachmentValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(MediaAttachmentValidateTestSuite))
-}
diff --git a/internal/validate/mention_test.go b/internal/validate/mention_test.go
deleted file mode 100644
index 52513bd8c..000000000
--- a/internal/validate/mention_test.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyMention() *gtsmodel.Mention {
-	return &gtsmodel.Mention{
-		ID:               "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:        time.Now(),
-		UpdatedAt:        time.Now(),
-		OriginAccountID:  "01FE96MAE58MXCE5C4SSMEMCEK",
-		OriginAccountURI: "https://some-instance/accounts/bleepbloop",
-		OriginAccount:    nil,
-		TargetAccountID:  "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:    nil,
-		StatusID:         "01FE96NBPNJNY26730FT6GZTFE",
-		Status:           nil,
-	}
-}
-
-type MentionValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *MentionValidateTestSuite) TestValidateMentionHappyPath() {
-	// no problem here
-	m := happyMention()
-	err := validate.Struct(m)
-	suite.NoError(err)
-}
-
-func (suite *MentionValidateTestSuite) TestValidateMentionBadID() {
-	m := happyMention()
-
-	m.ID = ""
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *MentionValidateTestSuite) TestValidateMentionAccountURI() {
-	m := happyMention()
-
-	m.OriginAccountURI = ""
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag")
-
-	m.OriginAccountURI = "---------------------------"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag")
-}
-
-func (suite *MentionValidateTestSuite) TestValidateMentionDodgyStatusID() {
-	m := happyMention()
-
-	m.StatusID = "9HZJ76B6VXSKF"
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-
-	m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-}
-
-func (suite *MentionValidateTestSuite) TestValidateMentionNoCreatedAt() {
-	m := happyMention()
-
-	m.CreatedAt = time.Time{}
-	err := validate.Struct(m)
-	suite.NoError(err)
-}
-
-func TestMentionValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(MentionValidateTestSuite))
-}
diff --git a/internal/validate/notification_test.go b/internal/validate/notification_test.go
deleted file mode 100644
index a76b8bf58..000000000
--- a/internal/validate/notification_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyNotification() *gtsmodel.Notification {
-	return &gtsmodel.Notification{
-		ID:               "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:        time.Now(),
-		NotificationType: gtsmodel.NotificationFave,
-		OriginAccountID:  "01FE96MAE58MXCE5C4SSMEMCEK",
-		OriginAccount:    nil,
-		TargetAccountID:  "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:    nil,
-		StatusID:         "01FE96NBPNJNY26730FT6GZTFE",
-		Status:           nil,
-	}
-}
-
-type NotificationValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *NotificationValidateTestSuite) TestValidateNotificationHappyPath() {
-	// no problem here
-	n := happyNotification()
-	err := validate.Struct(n)
-	suite.NoError(err)
-}
-
-func (suite *NotificationValidateTestSuite) TestValidateNotificationBadID() {
-	n := happyNotification()
-
-	n.ID = ""
-	err := validate.Struct(n)
-	suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	n.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(n)
-	suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *NotificationValidateTestSuite) TestValidateNotificationStatusID() {
-	n := happyNotification()
-
-	n.StatusID = ""
-	err := validate.Struct(n)
-	suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'required_if' tag")
-
-	n.StatusID = "9HZJ76B6VXSKF"
-	err = validate.Struct(n)
-	suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-
-	n.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
-	err = validate.Struct(n)
-	suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-
-	n.StatusID = ""
-	n.NotificationType = gtsmodel.NotificationFollowRequest
-	err = validate.Struct(n)
-	suite.NoError(err)
-}
-
-func (suite *NotificationValidateTestSuite) TestValidateNotificationNoCreatedAt() {
-	n := happyNotification()
-
-	n.CreatedAt = time.Time{}
-	err := validate.Struct(n)
-	suite.NoError(err)
-}
-
-func TestNotificationValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(NotificationValidateTestSuite))
-}
diff --git a/internal/validate/routersession_test.go b/internal/validate/routersession_test.go
deleted file mode 100644
index f9dc49e40..000000000
--- a/internal/validate/routersession_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyRouterSession() *gtsmodel.RouterSession {
-	return &gtsmodel.RouterSession{
-		ID:    "01FE91RJR88PSEEE30EV35QR8N",
-		Auth:  []byte("12345678901234567890123456789012"),
-		Crypt: []byte("12345678901234567890123456789012"),
-	}
-}
-
-type RouterSessionValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionHappyPath() {
-	// no problem here
-	r := happyRouterSession()
-	err := validate.Struct(r)
-	suite.NoError(err)
-}
-
-func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionAuth() {
-	r := happyRouterSession()
-
-	// remove auth struct
-	r.Auth = nil
-	err := validate.Struct(r)
-	suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'required' tag")
-
-	// auth bytes too long
-	r.Auth = []byte("1234567890123456789012345678901234567890")
-	err = validate.Struct(r)
-	suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'len' tag")
-
-	// auth bytes too short
-	r.Auth = []byte("12345678901")
-	err = validate.Struct(r)
-	suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'len' tag")
-}
-
-func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionCrypt() {
-	r := happyRouterSession()
-
-	// remove crypt struct
-	r.Crypt = nil
-	err := validate.Struct(r)
-	suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'required' tag")
-
-	// crypt bytes too long
-	r.Crypt = []byte("1234567890123456789012345678901234567890")
-	err = validate.Struct(r)
-	suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'len' tag")
-
-	// crypt bytes too short
-	r.Crypt = []byte("12345678901")
-	err = validate.Struct(r)
-	suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'len' tag")
-}
-
-func TestRouterSessionValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(RouterSessionValidateTestSuite))
-}
diff --git a/internal/validate/status_test.go b/internal/validate/status_test.go
deleted file mode 100644
index db0cd8fda..000000000
--- a/internal/validate/status_test.go
+++ /dev/null
@@ -1,160 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/ap"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-	"github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-func happyStatus() *gtsmodel.Status {
-	return &gtsmodel.Status{
-		ID:                       "01FEBBH6NYDG87NK6A6EC543ED",
-		CreatedAt:                time.Now(),
-		UpdatedAt:                time.Now(),
-		URI:                      "https://example.org/users/test_user/statuses/01FEBBH6NYDG87NK6A6EC543ED",
-		URL:                      "https://example.org/@test_user/01FEBBH6NYDG87NK6A6EC543ED",
-		Content:                  "<p>Test status! #hello</p>",
-		AttachmentIDs:            []string{"01FEBBKZBY9H5FEP3PHVVAAGN1", "01FEBBM7S2R4WT6WWW22KN1PWE"},
-		Attachments:              nil,
-		TagIDs:                   []string{"01FEBBNBMBSN1FESMZ1TCXNWYP"},
-		Tags:                     nil,
-		MentionIDs:               nil,
-		Mentions:                 nil,
-		EmojiIDs:                 nil,
-		Emojis:                   nil,
-		Local:                    testrig.TrueBool(),
-		AccountID:                "01FEBBQ4KEP3824WW61MF52638",
-		Account:                  nil,
-		AccountURI:               "https://example.org/users/test_user",
-		InReplyToID:              "",
-		InReplyToURI:             "",
-		InReplyToAccountID:       "",
-		InReplyTo:                nil,
-		InReplyToAccount:         nil,
-		BoostOfID:                "",
-		BoostOfAccountID:         "",
-		BoostOf:                  nil,
-		BoostOfAccount:           nil,
-		ContentWarning:           "hello world test post",
-		Visibility:               gtsmodel.VisibilityPublic,
-		Sensitive:                testrig.FalseBool(),
-		Language:                 "en",
-		CreatedWithApplicationID: "01FEBBZHF4GFVRXSJVXD0JTZZ2",
-		CreatedWithApplication:   nil,
-		Federated:                testrig.TrueBool(),
-		Boostable:                testrig.TrueBool(),
-		Replyable:                testrig.TrueBool(),
-		Likeable:                 testrig.TrueBool(),
-		ActivityStreamsType:      ap.ObjectNote,
-		Text:                     "Test status! #hello",
-	}
-}
-
-type StatusValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *StatusValidateTestSuite) TestValidateStatusHappyPath() {
-	// no problem here
-	s := happyStatus()
-	err := validate.Struct(s)
-	suite.NoError(err)
-}
-
-func (suite *StatusValidateTestSuite) TestValidateStatusBadID() {
-	s := happyStatus()
-
-	s.ID = ""
-	err := validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	s.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusValidateTestSuite) TestValidateStatusAttachmentIDs() {
-	s := happyStatus()
-
-	s.AttachmentIDs[0] = ""
-	err := validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag")
-
-	s.AttachmentIDs[0] = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag")
-
-	s.AttachmentIDs[1] = ""
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag\nKey: 'Status.AttachmentIDs[1]' Error:Field validation for 'AttachmentIDs[1]' failed on the 'ulid' tag")
-
-	s.AttachmentIDs = []string{}
-	err = validate.Struct(s)
-	suite.NoError(err)
-
-	s.AttachmentIDs = nil
-	err = validate.Struct(s)
-	suite.NoError(err)
-}
-
-func (suite *StatusValidateTestSuite) TestStatusApplicationID() {
-	s := happyStatus()
-
-	s.CreatedWithApplicationID = ""
-	err := validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.CreatedWithApplicationID' Error:Field validation for 'CreatedWithApplicationID' failed on the 'required_if' tag")
-
-	s.Local = testrig.FalseBool()
-	err = validate.Struct(s)
-	suite.NoError(err)
-}
-
-func (suite *StatusValidateTestSuite) TestValidateStatusReplyFields() {
-	s := happyStatus()
-
-	s.InReplyToAccountID = "01FEBCTP6DN7961PN81C3DVM4N                         "
-	err := validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'required_with' tag\nKey: 'Status.InReplyToURI' Error:Field validation for 'InReplyToURI' failed on the 'required_with' tag\nKey: 'Status.InReplyToAccountID' Error:Field validation for 'InReplyToAccountID' failed on the 'ulid' tag")
-
-	s.InReplyToAccountID = "01FEBCTP6DN7961PN81C3DVM4N"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'required_with' tag\nKey: 'Status.InReplyToURI' Error:Field validation for 'InReplyToURI' failed on the 'required_with' tag")
-
-	s.InReplyToURI = "https://example.org/users/mmbop/statuses/aaaaaaaa"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'required_with' tag")
-
-	s.InReplyToID = "not a valid ulid"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'ulid' tag")
-
-	s.InReplyToID = "01FEBD07E72DEY6YB9K10ZA6ST"
-	err = validate.Struct(s)
-	suite.NoError(err)
-}
-
-func TestStatusValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(StatusValidateTestSuite))
-}
diff --git a/internal/validate/statusbookmark_test.go b/internal/validate/statusbookmark_test.go
deleted file mode 100644
index 3be9e56ed..000000000
--- a/internal/validate/statusbookmark_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyStatusBookmark() *gtsmodel.StatusBookmark {
-	return &gtsmodel.StatusBookmark{
-		ID:              "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:       time.Now(),
-		AccountID:       "01FE96MAE58MXCE5C4SSMEMCEK",
-		Account:         nil,
-		TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:   nil,
-		StatusID:        "01FE96NBPNJNY26730FT6GZTFE",
-		Status:          nil,
-	}
-}
-
-type StatusBookmarkValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkHappyPath() {
-	// no problem here
-	s := happyStatusBookmark()
-	err := validate.Struct(s)
-	suite.NoError(err)
-}
-
-func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkBadID() {
-	s := happyStatusBookmark()
-
-	s.ID = ""
-	err := validate.Struct(s)
-	suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	s.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkDodgyStatusID() {
-	s := happyStatusBookmark()
-
-	s.StatusID = "9HZJ76B6VXSKF"
-	err := validate.Struct(s)
-	suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-
-	s.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
-	err = validate.Struct(s)
-	suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkNoCreatedAt() {
-	s := happyStatusBookmark()
-
-	s.CreatedAt = time.Time{}
-	err := validate.Struct(s)
-	suite.NoError(err)
-}
-
-func TestStatusBookmarkValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(StatusBookmarkValidateTestSuite))
-}
diff --git a/internal/validate/statusfave_test.go b/internal/validate/statusfave_test.go
deleted file mode 100644
index e864e39f2..000000000
--- a/internal/validate/statusfave_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyStatusFave() *gtsmodel.StatusFave {
-	return &gtsmodel.StatusFave{
-		ID:              "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:       time.Now(),
-		AccountID:       "01FE96MAE58MXCE5C4SSMEMCEK",
-		Account:         nil,
-		TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:   nil,
-		StatusID:        "01FE96NBPNJNY26730FT6GZTFE",
-		Status:          nil,
-		URI:             "https://example.org/users/user1/activity/faves/01FE91RJR88PSEEE30EV35QR8N",
-	}
-}
-
-type StatusFaveValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveHappyPath() {
-	// no problem here
-	f := happyStatusFave()
-	err := validate.Struct(f)
-	suite.NoError(err)
-}
-
-func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveBadID() {
-	f := happyStatusFave()
-
-	f.ID = ""
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveDodgyStatusID() {
-	f := happyStatusFave()
-
-	f.StatusID = "9HZJ76B6VXSKF"
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-
-	f.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoCreatedAt() {
-	f := happyStatusFave()
-
-	f.CreatedAt = time.Time{}
-	err := validate.Struct(f)
-	suite.NoError(err)
-}
-
-func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoURI() {
-	f := happyStatusFave()
-
-	f.URI = ""
-	err := validate.Struct(f)
-	suite.EqualError(err, "Key: 'StatusFave.URI' Error:Field validation for 'URI' failed on the 'required' tag")
-
-	f.URI = "this-is-not-a-valid-url"
-	err = validate.Struct(f)
-	suite.EqualError(err, "Key: 'StatusFave.URI' Error:Field validation for 'URI' failed on the 'url' tag")
-}
-
-func TestStatusFaveValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(StatusFaveValidateTestSuite))
-}
diff --git a/internal/validate/statusmute_test.go b/internal/validate/statusmute_test.go
deleted file mode 100644
index 20358bb23..000000000
--- a/internal/validate/statusmute_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyStatusMute() *gtsmodel.StatusMute {
-	return &gtsmodel.StatusMute{
-		ID:              "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:       time.Now(),
-		AccountID:       "01FE96MAE58MXCE5C4SSMEMCEK",
-		Account:         nil,
-		TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
-		TargetAccount:   nil,
-		StatusID:        "01FE96NBPNJNY26730FT6GZTFE",
-		Status:          nil,
-	}
-}
-
-type StatusMuteValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteHappyPath() {
-	// no problem here
-	m := happyStatusMute()
-	err := validate.Struct(m)
-	suite.NoError(err)
-}
-
-func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteBadID() {
-	m := happyStatusMute()
-
-	m.ID = ""
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteDodgyStatusID() {
-	m := happyStatusMute()
-
-	m.StatusID = "9HZJ76B6VXSKF"
-	err := validate.Struct(m)
-	suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-
-	m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
-	err = validate.Struct(m)
-	suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
-}
-
-func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteNoCreatedAt() {
-	m := happyStatusMute()
-
-	m.CreatedAt = time.Time{}
-	err := validate.Struct(m)
-	suite.NoError(err)
-}
-
-func TestStatusMuteValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(StatusMuteValidateTestSuite))
-}
diff --git a/internal/validate/structvalidation.go b/internal/validate/structvalidation.go
deleted file mode 100644
index 573e79fd2..000000000
--- a/internal/validate/structvalidation.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate
-
-import (
-	"reflect"
-
-	"github.com/go-playground/validator/v10"
-	"github.com/superseriousbusiness/gotosocial/internal/regexes"
-)
-
-var v *validator.Validate
-
-func ulidValidator(fl validator.FieldLevel) bool {
-	field := fl.Field()
-
-	switch field.Kind() {
-	case reflect.String:
-		return regexes.ULID.MatchString(field.String())
-	default:
-		return false
-	}
-}
-
-func init() {
-	v = validator.New()
-	if err := v.RegisterValidation("ulid", ulidValidator); err != nil {
-		panic(err)
-	}
-}
-
-// Struct validates the passed struct, returning validator.ValidationErrors if invalid, or nil if OK.
-func Struct(s interface{}) error {
-	return processValidationError(v.Struct(s))
-}
-
-func processValidationError(err error) error {
-	if err == nil {
-		return nil
-	}
-
-	if ive, ok := err.(*validator.InvalidValidationError); ok {
-		panic(ive)
-	}
-
-	valErr, ok := err.(validator.ValidationErrors)
-	if !ok {
-		panic("*validator.InvalidValidationError could not be coerced to validator.ValidationErrors")
-	}
-
-	return valErr
-}
diff --git a/internal/validate/structvalidation_test.go b/internal/validate/structvalidation_test.go
deleted file mode 100644
index f8ad514cf..000000000
--- a/internal/validate/structvalidation_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-type ValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *ValidateTestSuite) TestValidateNilPointer() {
-	var nilUser *gtsmodel.User
-	suite.Panics(func() {
-		validate.Struct(nilUser)
-	})
-}
-
-func (suite *ValidateTestSuite) TestValidatePointer() {
-	user := &gtsmodel.User{}
-	err := validate.Struct(user)
-	suite.EqualError(err, "Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag\nKey: 'User.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag\nKey: 'User.EncryptedPassword' Error:Field validation for 'EncryptedPassword' failed on the 'required' tag\nKey: 'User.UnconfirmedEmail' Error:Field validation for 'UnconfirmedEmail' failed on the 'required_without' tag")
-}
-
-func (suite *ValidateTestSuite) TestValidateNil() {
-	suite.Panics(func() {
-		validate.Struct(nil)
-	})
-}
-
-func (suite *ValidateTestSuite) TestValidateWeirdULID() {
-	type a struct {
-		ID bool `validate:"required,ulid"`
-	}
-
-	err := validate.Struct(a{ID: true})
-	suite.Error(err)
-}
-
-func (suite *ValidateTestSuite) TestValidateNotStruct() {
-	type aaaaaaa string
-	aaaaaa := aaaaaaa("aaaa")
-	suite.Panics(func() {
-		validate.Struct(aaaaaa)
-	})
-}
-
-func TestValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(ValidateTestSuite))
-}
diff --git a/internal/validate/token_test.go b/internal/validate/token_test.go
deleted file mode 100644
index 2ff0e0721..000000000
--- a/internal/validate/token_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-)
-
-func happyToken() *gtsmodel.Token {
-	return &gtsmodel.Token{
-		ID:          "01FE91RJR88PSEEE30EV35QR8N",
-		CreatedAt:   time.Now(),
-		UpdatedAt:   time.Now(),
-		ClientID:    "01FEEDMF6C0QD589MRK7919Z0R",
-		UserID:      "01FEK0BFJKYXB4Y51RBQ7P5P79",
-		RedirectURI: "oauth2redirect://com.keylesspalace.tusky/",
-		Scope:       "read write follow",
-	}
-}
-
-type TokenValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *TokenValidateTestSuite) TestValidateTokenHappyPath() {
-	// no problem here
-	t := happyToken()
-	err := validate.Struct(t)
-	suite.NoError(err)
-}
-
-func (suite *TokenValidateTestSuite) TestValidateTokenBadID() {
-	t := happyToken()
-
-	t.ID = ""
-	err := validate.Struct(t)
-	suite.EqualError(err, "Key: 'Token.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-
-	t.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
-	err = validate.Struct(t)
-	suite.EqualError(err, "Key: 'Token.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
-}
-
-func (suite *TokenValidateTestSuite) TestValidateTokenNoCreatedAt() {
-	t := happyToken()
-
-	t.CreatedAt = time.Time{}
-	err := validate.Struct(t)
-	suite.NoError(err)
-}
-
-func (suite *TokenValidateTestSuite) TestValidateTokenRedirectURI() {
-	t := happyToken()
-
-	t.RedirectURI = "invalid-uri"
-	err := validate.Struct(t)
-	suite.EqualError(err, "Key: 'Token.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'uri' tag")
-
-	t.RedirectURI = ""
-	err = validate.Struct(t)
-	suite.EqualError(err, "Key: 'Token.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'required' tag")
-
-	t.RedirectURI = "urn:ietf:wg:oauth:2.0:oob"
-	err = validate.Struct(t)
-	suite.NoError(err)
-}
-
-func (suite *TokenValidateTestSuite) TestValidateTokenScope() {
-	t := happyToken()
-
-	t.Scope = ""
-	err := validate.Struct(t)
-	suite.EqualError(err, "Key: 'Token.Scope' Error:Field validation for 'Scope' failed on the 'required' tag")
-}
-
-func TestTokenValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(TokenValidateTestSuite))
-}
diff --git a/internal/validate/user_test.go b/internal/validate/user_test.go
deleted file mode 100644
index f61ff6e2f..000000000
--- a/internal/validate/user_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-// GoToSocial
-// Copyright (C) GoToSocial Authors admin@gotosocial.org
-// SPDX-License-Identifier: AGPL-3.0-or-later
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-package validate_test
-
-import (
-	"net"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/suite"
-	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-	"github.com/superseriousbusiness/gotosocial/internal/validate"
-	"github.com/superseriousbusiness/gotosocial/testrig"
-)
-
-func happyUser() *gtsmodel.User {
-	return &gtsmodel.User{
-		ID:                     "01FE8TTK9F34BR0KG7639AJQTX",
-		Email:                  "whatever@example.org",
-		AccountID:              "01FE8TWA7CN8J7237K5DFS1RY5",
-		Account:                nil,
-		EncryptedPassword:      "$2y$10$tkRapNGW.RWkEuCMWdgArunABFvsPGRvFQY3OibfSJo0RDL3z8WfC",
-		CreatedAt:              time.Now(),
-		UpdatedAt:              time.Now(),
-		SignUpIP:               net.ParseIP("128.64.32.16"),
-		CurrentSignInAt:        time.Now(),
-		CurrentSignInIP:        net.ParseIP("128.64.32.16"),
-		LastSignInAt:           time.Now(),
-		LastSignInIP:           net.ParseIP("128.64.32.16"),
-		SignInCount:            0,
-		InviteID:               "",
-		ChosenLanguages:        []string{},
-		FilteredLanguages:      []string{},
-		Locale:                 "en",
-		CreatedByApplicationID: "01FE8Y5EHMWCA1MHMTNHRVZ1X4",
-		CreatedByApplication:   nil,
-		LastEmailedAt:          time.Now(),
-		ConfirmationToken:      "",
-		ConfirmedAt:            time.Now(),
-		ConfirmationSentAt:     time.Time{},
-		UnconfirmedEmail:       "",
-		Moderator:              testrig.FalseBool(),
-		Admin:                  testrig.FalseBool(),
-		Disabled:               testrig.FalseBool(),
-		Approved:               testrig.TrueBool(),
-	}
-}
-
-type UserValidateTestSuite struct {
-	suite.Suite
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserHappyPath() {
-	// no problem here
-	u := happyUser()
-	err := validate.Struct(u)
-	suite.NoError(err)
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserNoID() {
-	// user has no id set
-	u := happyUser()
-	u.ID = ""
-
-	err := validate.Struct(u)
-	suite.EqualError(err, "Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag")
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserNoEmail() {
-	// user has no email or unconfirmed email set
-	u := happyUser()
-	u.Email = ""
-
-	err := validate.Struct(u)
-	suite.EqualError(err, "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required_with' tag\nKey: 'User.UnconfirmedEmail' Error:Field validation for 'UnconfirmedEmail' failed on the 'required_without' tag")
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserOnlyUnconfirmedEmail() {
-	// user has only UnconfirmedEmail but ConfirmedAt is set
-	u := happyUser()
-	u.Email = ""
-	u.UnconfirmedEmail = "whatever@example.org"
-
-	err := validate.Struct(u)
-	suite.EqualError(err, "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required_with' tag")
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserOnlyUnconfirmedEmailOK() {
-	// user has only UnconfirmedEmail and ConfirmedAt is not set
-	u := happyUser()
-	u.Email = ""
-	u.UnconfirmedEmail = "whatever@example.org"
-	u.ConfirmedAt = time.Time{}
-
-	err := validate.Struct(u)
-	suite.NoError(err)
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserNoConfirmedAt() {
-	// user has Email but no ConfirmedAt
-	u := happyUser()
-	u.ConfirmedAt = time.Time{}
-
-	err := validate.Struct(u)
-	suite.EqualError(err, "Key: 'User.ConfirmedAt' Error:Field validation for 'ConfirmedAt' failed on the 'required_with' tag")
-}
-
-func (suite *UserValidateTestSuite) TestValidateUserUnlikelySignInCount() {
-	// user has Email but no ConfirmedAt
-	u := happyUser()
-	u.SignInCount = -69
-
-	err := validate.Struct(u)
-	suite.EqualError(err, "Key: 'User.SignInCount' Error:Field validation for 'SignInCount' failed on the 'min' tag")
-}
-
-func TestUserValidateTestSuite(t *testing.T) {
-	suite.Run(t, new(UserValidateTestSuite))
-}