Rework backing storage system
Some checks are pending
/ test (push) Waiting to run

Step one: Copy the struct definitions over into a new, dedicated
submodule
Step two: Make a generator script
Step three: Define helper functions for various queries
This commit is contained in:
Melody Becker 2025-03-20 21:39:10 +01:00
parent 0639cde4f2
commit 714f528641
No known key found for this signature in database
27 changed files with 983 additions and 0 deletions

64
storage-new/gormLogger.go Normal file
View file

@ -0,0 +1,64 @@
package storage
import (
"context"
"time"
"github.com/rs/zerolog"
"gorm.io/gorm/logger"
)
type gormLogger struct {
logger zerolog.Logger
}
func newGormLogger(zerologger zerolog.Logger) *gormLogger {
return &gormLogger{zerologger}
}
func (g *gormLogger) LogMode(newLevel logger.LogLevel) logger.Interface {
switch newLevel {
case logger.Error:
g.logger = g.logger.Level(zerolog.ErrorLevel)
case logger.Warn:
g.logger = g.logger.Level(zerolog.WarnLevel)
case logger.Info:
g.logger = g.logger.Level(zerolog.InfoLevel)
case logger.Silent:
g.logger = g.logger.Level(zerolog.Disabled)
}
return g
}
func (g *gormLogger) Info(ctx context.Context, format string, args ...interface{}) {
g.logger.Info().Ctx(ctx).Msgf(format, args...)
}
func (g *gormLogger) Warn(ctx context.Context, format string, args ...interface{}) {
g.logger.Warn().Ctx(ctx).Msgf(format, args...)
}
func (g *gormLogger) Error(ctx context.Context, format string, args ...interface{}) {
g.logger.Error().Ctx(ctx).Msgf(format, args...)
}
func (g *gormLogger) Trace(
ctx context.Context,
begin time.Time,
fc func() (sql string, rowsAffected int64),
err error,
) {
sql, rowsAffected := fc()
g.logger.Trace().
Ctx(ctx).
Time("gorm-begin", begin).
Err(err).
Str("gorm-query", sql).
Int64("gorm-rows-affected", rowsAffected).
Send()
}
func (g *gormLogger) OverwriteLoggingLevel(new zerolog.Level) {
g.logger = g.logger.Level(new)
}
func (g *gormLogger) OverwriteLogger(new zerolog.Logger) {
g.logger = new
}

102
storage-new/migrations.go Normal file
View file

@ -0,0 +1,102 @@
package storage
import (
"fmt"
"strings"
"git.mstar.dev/mstar/goutils/sliceutils"
"gorm.io/gorm"
"git.mstar.dev/mstar/linstrom/storage-new/models"
)
func migrateTypes(db *gorm.DB) error {
if err := db.AutoMigrate(
&models.Emote{},
&models.MediaMetadata{},
&models.Note{},
&models.NoteToAttachment{},
&models.NoteToEmote{},
&models.NoteToPing{},
&models.NoteTag{},
&models.Reaction{},
&models.RemoteServer{},
&models.Role{},
&models.User{},
&models.UserAuthMethod{},
&models.UserBeings{},
&models.UserInfoField{},
&models.UserRelation{},
&models.UserRemoteLinks{},
&models.UserRole{},
&models.UserTag{},
); err != nil {
return fmt.Errorf("storage: automigrate structs: %w", err)
}
return nil
}
// Ensure the being enum exists for the user
func createBeingType(db *gorm.DB) error {
return migrateEnum(
db,
"being_type",
sliceutils.Map(models.AllBeings, func(t models.BeingType) string { return string(t) }),
)
}
func createAccountRelationType(db *gorm.DB) error {
return migrateEnum(
db,
"relation_type",
sliceutils.Map(
models.AllRelations,
func(t models.RelationType) string { return string(t) },
),
)
}
func createAccountAuthMethodType(db *gorm.DB) error {
return migrateEnum(
db,
"auth_method_type",
sliceutils.Map(
models.AllAuthMethods,
func(t models.AuthenticationMethodType) string { return string(t) },
),
)
}
func createRemoteServerSoftwareType(db *gorm.DB) error {
return migrateEnum(
db,
"server_software_type",
sliceutils.Map(
models.AllServerSoftwareTypes,
func(t models.ServerSoftwareType) string { return string(t) },
),
)
}
// Helper function for ensuring the existence of an enum with the given values
func migrateEnum(db *gorm.DB, name string, values []string) error {
if err := db.Exec("DROP TYPE IF EXISTS " + name).Error; err != nil {
return fmt.Errorf("storage: migrate %s: %w", name, err)
}
queryBuilder := strings.Builder{}
queryBuilder.WriteString("CREATE TYPE")
queryBuilder.WriteString(name)
queryBuilder.WriteString("AS ENUM (")
blen := len(values)
for i, btype := range values {
queryBuilder.WriteString("'" + string(btype) + "'")
// Append comma everywhere except last entry
if i+1 < blen {
queryBuilder.WriteString(",")
}
}
if err := db.Exec(queryBuilder.String()).Error; err != nil {
return fmt.Errorf("storage: migrate %s: %w", name, err)
}
return nil
}

View file

@ -0,0 +1,12 @@
package models
import "gorm.io/gorm"
type Emote struct {
gorm.Model
// Metadata MediaMetadata // `gorm:"foreignKey:MetadataId"`
MetadataId string
Name string
// Server RemoteServer // `gorm:"foreignKey:ServerId;references:ID"`
ServerId uint
}

View file

@ -0,0 +1,29 @@
package models
import (
"time"
"gorm.io/gorm"
)
type MediaMetadata struct {
ID string `gorm:"primarykey"` // The unique ID of this media file
CreatedAt time.Time // When this entry was created
UpdatedAt time.Time // When this entry was last updated
// When this entry was deleted (for soft deletions)
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
// If not null, this entry is marked as deleted
DeletedAt gorm.DeletedAt `gorm:"index"`
OwnedBy string // Account id this media belongs to
Remote bool // whether the attachment is a remote one
// Where the media is stored. Url
Location string
Type string // What media type this is following mime types, eg image/png
// Name of the file
// Could be <emote-name>.png, <server-name>.webp for example. Or the name the file was uploaded with
Name string
// Alternative description of the media file's content
AltText string
// Whether the media is to be blurred by default
Blurred bool
}

View file

@ -0,0 +1,33 @@
package models
import (
"time"
"gorm.io/gorm"
)
// Data defined in extra structs:
// - Attachments: models.NoteToAttachment
// - Emotes: models.NoteToEmote
// - Pings: models.NoteToPing
// - Tags: models.NoteTag
type Note struct {
ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations
CreatedAt time.Time // When this entry was created
UpdatedAt time.Time // When this entry was last updated
// When this entry was deleted (for soft deletions)
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
// If not null, this entry is marked as deleted
DeletedAt gorm.DeletedAt `gorm:"index"`
// Creator Account // `gorm:"foreignKey:CreatorId;references:ID"` // Account that created the post
CreatorId string
Remote bool // Whether the note is originally a remote one and just "cached"
// Raw content of the note. So without additional formatting applied
// Might already have formatting applied beforehand from the origin server
RawContent string
ContentWarning *string // Content warnings of the note, if it contains any
RepliesTo *string // Url of the message this replies to
Quotes *string // url of the message this note quotes
AccessLevel NoteAccessLevel // Where to send this message to (public, home, followers, dm)
OriginServer string // Url of the origin server. Also the primary key for those
}

View file

@ -0,0 +1,28 @@
package models
import "database/sql/driver"
type NoteAccessLevel uint8
const (
// The note is intended for the public
NOTE_TARGET_PUBLIC NoteAccessLevel = 0
// The note is intended only for the home screen
// not really any idea what the difference is compared to public
// Maybe home notes don't show up on the server feed but still for everyone's home feed if it reaches them via follow or boost
NOTE_TARGET_HOME NoteAccessLevel = 1 << iota
// The note is intended only for followers
NOTE_TARGET_FOLLOWERS
// The note is intended only for a DM to one or more targets
NOTE_TARGET_DM
)
// Converts the NoteTarget value into a type the DB can use
func (n *NoteAccessLevel) Value() (driver.Value, error) {
return n, nil
}
func (ct *NoteAccessLevel) Scan(value any) error {
*ct = NoteAccessLevel(value.(uint8))
return nil
}

View file

@ -0,0 +1,6 @@
package models
type NoteToAttachment struct {
UserId string
AttachmentId string
}

View file

@ -0,0 +1,6 @@
package models
type NoteToEmote struct {
UserId string
EmoteId string
}

View file

@ -0,0 +1,6 @@
package models
type NoteToPing struct {
UserId string
PingTargetId string
}

View file

@ -0,0 +1,6 @@
package models
type NoteTag struct {
UserId string
Tag string
}

View file

@ -0,0 +1,10 @@
package models
import "gorm.io/gorm"
type Reaction struct {
gorm.Model
NoteId string
ReactorId string
EmoteId uint
}

View file

@ -0,0 +1,12 @@
package models
import "gorm.io/gorm"
type RemoteServer struct {
gorm.Model
ServerType ServerSoftwareType // What software the server is running. Useful for formatting
Domain string // `gorm:"primaryKey"` // Domain the server exists under. Additional primary key
Name string // What the server wants to be known as (usually same as url)
Icon string // ID of a media file
IsSelf bool // Whether this server is yours truly
}

View file

@ -0,0 +1,37 @@
package models
import (
"database/sql/driver"
)
type ServerSoftwareType string
const (
// Includes forks like glitch-soc, etc
ServerSoftwareMastodon = ServerSoftwareType("Mastodon")
// Includes forks like Ice Shrimp, Sharkey, Cutiekey, etc
ServerSoftwareMisskey = ServerSoftwareType("Misskey")
// Includes Akkoma
ServerSoftwarePlemora = ServerSoftwareType("Plemora")
// Wafrn is a new entry
ServerSoftwareWafrn = ServerSoftwareType("Wafrn")
// And of course, yours truly
ServerSoftwareLinstrom = ServerSoftwareType("Linstrom")
)
var AllServerSoftwareTypes = []ServerSoftwareType{
ServerSoftwareMastodon,
ServerSoftwareMisskey,
ServerSoftwarePlemora,
ServerSoftwareWafrn,
ServerSoftwareLinstrom,
}
func (r *ServerSoftwareType) Value() (driver.Value, error) {
return r, nil
}
func (ct *ServerSoftwareType) Scan(value any) error {
*ct = ServerSoftwareType(value.(string))
return nil
}

182
storage-new/models/Role.go Normal file
View file

@ -0,0 +1,182 @@
package models
import (
"gorm.io/gorm"
)
// Could I collapse all these go:generate command into more condensed ones?
// Yes
// Will I do that?
// No
// This is explicit in what is being done. And easier to understand
//go:generate go build -o RolesGenerator ../cmd/RolesGenerator/main.go
//go:generate ./RolesGenerator -input=roles.go -output=rolesUtil_generated.go
//go:generate rm RolesGenerator
//go:generate go build -o ApiGenerator ../cmd/RolesApiTypeGenerator/main.go
//go:generate ./ApiGenerator -input=roles.go -output=../server/apiLinstromTypes_generated.go
//go:generate rm ApiGenerator
//go:generate go build -o HelperGenerator ../cmd/RolesApiConverter/main.go
//go:generate ./HelperGenerator -input=roles.go -output=../server/apiLinstromTypeHelpers_generated.go
//go:generate rm HelperGenerator
//go:generate go build -o FrontendGenerator ../cmd/RolesFrontendGenerator/main.go
//go:generate ./FrontendGenerator -input=roles.go -output=../frontend-reactive/app/models/role.ts
//go:generate rm FrontendGenerator
// A role is, in concept, similar to how Discord handles roles
// Some permission can be either disallowed (&false), don't care (nil) or allowed (&true)
// Don't care just says to use the value from the next lower role where it is set
// Blocks are part of the user relations
type Role struct {
// TODO: More control options
// Extend upon whatever Masto, Akkoma and Misskey have
// Lots of details please
// --- Role metadata ---
// Include full db model stuff
gorm.Model
// Name of the role
Name string `gorm:"primaryKey;unique"`
// Priority of the role
// Lower priority gets applied first and thus overwritten by higher priority ones
// If two roles have the same priority, the order is undetermined and may be random
// Default priority for new roles is 1 to always overwrite default user
// And full admin has max priority possible
Priority uint32
// Whether this role is for a for a single user only (like custom, per user permissions in Discord)
// If yes, Name will be the id of the user in question
IsUserRole bool
// Whether this role is one built into Linstrom from the start or not
// Note: Built-in roles can't be modified
IsBuiltIn bool
// --- User permissions ---
CanSendMedia *bool // Local & remote
CanSendCustomEmotes *bool // Local & remote
CanSendCustomReactions *bool // Local & remote
CanSendPublicNotes *bool // Local & remote
CanSendLocalNotes *bool // Local & remote
CanSendFollowerOnlyNotes *bool // Local & remote
CanSendPrivateNotes *bool // Local & remote
CanSendReplies *bool // Local & remote
CanQuote *bool // Local only
CanBoost *bool // Local only
CanIncludeLinks *bool // Local & remote
CanIncludeSurvey *bool // Local
CanFederateFedi *bool // Local & remote
CanFederateBsky *bool // Local
CanChangeDisplayName *bool // Local
CanSubmitReports *bool // Local & remote
// If disabled, an account can no longer be interacted with. The owner can no longer change anything about it
// And the UI will show a notice (and maybe include that info in the AP data too)
// Only moderators and admins will be able to edit the account's roles
CanLogin *bool // Local
CanMentionOthers *bool // Local & remote
HasMentionCountLimit *bool // Local & remote
MentionLimit *uint32 // Local & remote
// CanViewBoosts *bool
// CanViewQuotes *bool
// CanViewMedia *bool
// CanViewCustomEmotes *bool
// --- Automod ---
AutoNsfwMedia *bool // Local & remote
AutoCwPosts *bool // Local & remote
AutoCwPostsText *string // Local & remote
ScanCreatedPublicNotes *bool // Local & remote
ScanCreatedLocalNotes *bool // Local & remote
ScanCreatedFollowerOnlyNotes *bool // Local & remote
ScanCreatedPrivateNotes *bool // Local & remote
// Blocks all interactions and federation between users with the role and all included ids/handles
// TODO: Decide whether this is a list of handles or of account ids
// Handles would increase the load due to having to search for them first
// while ids would require to store every single account mentioned
// which could cause escalating storage costs
DisallowInteractionsWith []string `gorm:"type:bytes;serializer:gob"` // Local & remote
WithholdNotesForManualApproval *bool // Local & remote
WithholdNotesBasedOnRegex *bool // Local & remote
WithholdNotesRegexes []string `gorm:"type:bytes;serializer:gob"` // Local & remote
// --- Admin perms ---
// If set, counts as all permissions being set as given and all restrictions being disabled
FullAdmin *bool // Local
CanAffectOtherAdmins *bool // Local
CanDeleteNotes *bool // Local
CanConfirmWithheldNotes *bool // Local
CanAssignRoles *bool // Local
CanSupressInteractionsBetweenUsers *bool // Local
CanOverwriteDisplayNames *bool // Local
CanManageCustomEmotes *bool // Local
CanViewDeletedNotes *bool // Local
CanRecoverDeletedNotes *bool // Local
CanManageAvatarDecorations *bool // Local
CanManageAds *bool // Local
CanSendAnnouncements *bool // Local
CanDeleteAccounts *bool // Local
}
/*
Mastodon permissions (highest permission to lowest):
- Admin
- Devops
- View Audit log
- View Dashboard
- Manage Reports
- Manage Federation
- Manage Settings
- Manage Blocks
- Manage Taxonomies
- Manage Appeals
- Manage Users
- Manage Invites
- Manage Rules
- Manage Announcements
- Manage Custom Emojis
- Manage Webhooks
- Invite Users
- Manage Roles
- Manage User Access
- Delete User Data
*/
/*
Misskey "permissions" (no order):
- Global timeline available (interact with global timeline I think)
- Local timeline available (same as global, but for local)
- b-something timeline available
- Can send public notes
- How many mentions a note can have
- Can invite others
- How many invites can be sent
- InviteLimitCycle (whatever that means)
- Invite Expiration time (duration of how long invites stay valid I think)
- Manage custom emojis
- Manage custom avatar decorations
- Seach for notes
- Use translator
- Hide ads from self
- How much storage space the user has
- Whether to mark all posts from account as nsfw
- max number of pinned messages
- max number of antennas
- max number of muted words
- max number of webhooks
- max number of clips
- max number of notes contained in a clip (? I think. Don't know enough about clips)
- Max number of lists of users
- max number of users in a user list
- rate limit multiplier
- max number of applied avatar decorations
*/

View file

@ -0,0 +1,240 @@
package models
import (
"math"
"git.mstar.dev/mstar/goutils/other"
)
// Default role every user has. Defines sane defaults for a normal user
// Will get overwritten by just about every other role due to every other role having higher priority
var DefaultUserRole = Role{
Name: "Default",
Priority: 0,
IsUserRole: false,
IsBuiltIn: true,
CanSendMedia: other.IntoPointer(true),
CanSendCustomEmotes: other.IntoPointer(true),
CanSendCustomReactions: other.IntoPointer(true),
CanSendPublicNotes: other.IntoPointer(true),
CanSendLocalNotes: other.IntoPointer(true),
CanSendFollowerOnlyNotes: other.IntoPointer(true),
CanSendPrivateNotes: other.IntoPointer(true),
CanSendReplies: other.IntoPointer(true),
CanQuote: other.IntoPointer(true),
CanBoost: other.IntoPointer(true),
CanIncludeLinks: other.IntoPointer(true),
CanIncludeSurvey: other.IntoPointer(true),
CanFederateFedi: other.IntoPointer(true),
CanFederateBsky: other.IntoPointer(true),
CanChangeDisplayName: other.IntoPointer(true),
CanSubmitReports: other.IntoPointer(true),
CanLogin: other.IntoPointer(true),
CanMentionOthers: other.IntoPointer(true),
HasMentionCountLimit: other.IntoPointer(false),
MentionLimit: other.IntoPointer(
uint32(math.MaxUint32),
), // Set this to max, even if not used due to *HasMentionCountLimit == false
AutoNsfwMedia: other.IntoPointer(false),
AutoCwPosts: other.IntoPointer(false),
AutoCwPostsText: nil,
WithholdNotesForManualApproval: other.IntoPointer(false),
ScanCreatedPublicNotes: other.IntoPointer(false),
ScanCreatedLocalNotes: other.IntoPointer(false),
ScanCreatedFollowerOnlyNotes: other.IntoPointer(false),
ScanCreatedPrivateNotes: other.IntoPointer(false),
DisallowInteractionsWith: []string{},
FullAdmin: other.IntoPointer(false),
CanAffectOtherAdmins: other.IntoPointer(false),
CanDeleteNotes: other.IntoPointer(false),
CanConfirmWithheldNotes: other.IntoPointer(false),
CanAssignRoles: other.IntoPointer(false),
CanSupressInteractionsBetweenUsers: other.IntoPointer(false),
CanOverwriteDisplayNames: other.IntoPointer(false),
CanManageCustomEmotes: other.IntoPointer(false),
CanViewDeletedNotes: other.IntoPointer(false),
CanRecoverDeletedNotes: other.IntoPointer(false),
CanManageAvatarDecorations: other.IntoPointer(false),
CanManageAds: other.IntoPointer(false),
CanSendAnnouncements: other.IntoPointer(false),
}
// Role providing maximum permissions
var FullAdminRole = Role{
Name: "fullAdmin",
Priority: math.MaxUint32,
IsUserRole: false,
IsBuiltIn: true,
CanSendMedia: other.IntoPointer(true),
CanSendCustomEmotes: other.IntoPointer(true),
CanSendCustomReactions: other.IntoPointer(true),
CanSendPublicNotes: other.IntoPointer(true),
CanSendLocalNotes: other.IntoPointer(true),
CanSendFollowerOnlyNotes: other.IntoPointer(true),
CanSendPrivateNotes: other.IntoPointer(true),
CanQuote: other.IntoPointer(true),
CanBoost: other.IntoPointer(true),
CanIncludeLinks: other.IntoPointer(true),
CanIncludeSurvey: other.IntoPointer(true),
CanChangeDisplayName: other.IntoPointer(true),
CanSubmitReports: other.IntoPointer(true),
CanLogin: other.IntoPointer(true),
CanMentionOthers: other.IntoPointer(true),
HasMentionCountLimit: other.IntoPointer(false),
MentionLimit: other.IntoPointer(
uint32(math.MaxUint32),
), // Set this to max, even if not used due to *HasMentionCountLimit == false
AutoNsfwMedia: other.IntoPointer(false),
AutoCwPosts: other.IntoPointer(false),
AutoCwPostsText: nil,
WithholdNotesForManualApproval: other.IntoPointer(false),
ScanCreatedPublicNotes: other.IntoPointer(false),
ScanCreatedLocalNotes: other.IntoPointer(false),
ScanCreatedFollowerOnlyNotes: other.IntoPointer(false),
ScanCreatedPrivateNotes: other.IntoPointer(false),
DisallowInteractionsWith: []string{},
FullAdmin: other.IntoPointer(true),
CanAffectOtherAdmins: other.IntoPointer(true),
CanDeleteNotes: other.IntoPointer(true),
CanConfirmWithheldNotes: other.IntoPointer(true),
CanAssignRoles: other.IntoPointer(true),
CanSupressInteractionsBetweenUsers: other.IntoPointer(true),
CanOverwriteDisplayNames: other.IntoPointer(true),
CanManageCustomEmotes: other.IntoPointer(true),
CanViewDeletedNotes: other.IntoPointer(true),
CanRecoverDeletedNotes: other.IntoPointer(true),
CanManageAvatarDecorations: other.IntoPointer(true),
CanManageAds: other.IntoPointer(true),
CanSendAnnouncements: other.IntoPointer(true),
}
// Role for totally freezing an account, blocking all activity from it
var AccountFreezeRole = Role{
Name: "accountFreeze",
Priority: math.MaxUint32 - 1,
IsUserRole: false,
IsBuiltIn: true,
CanSendMedia: other.IntoPointer(false),
CanSendCustomEmotes: other.IntoPointer(false),
CanSendCustomReactions: other.IntoPointer(false),
CanSendPublicNotes: other.IntoPointer(false),
CanSendLocalNotes: other.IntoPointer(false),
CanSendFollowerOnlyNotes: other.IntoPointer(false),
CanSendPrivateNotes: other.IntoPointer(false),
CanSendReplies: other.IntoPointer(false),
CanQuote: other.IntoPointer(false),
CanBoost: other.IntoPointer(false),
CanIncludeLinks: other.IntoPointer(false),
CanIncludeSurvey: other.IntoPointer(false),
CanFederateBsky: other.IntoPointer(false),
CanFederateFedi: other.IntoPointer(false),
CanChangeDisplayName: other.IntoPointer(false),
CanSubmitReports: other.IntoPointer(false),
CanLogin: other.IntoPointer(false),
CanMentionOthers: other.IntoPointer(false),
HasMentionCountLimit: other.IntoPointer(false),
MentionLimit: other.IntoPointer(
uint32(math.MaxUint32),
), // Set this to max, even if not used due to *HasMentionCountLimit == false
AutoNsfwMedia: other.IntoPointer(true),
AutoCwPosts: other.IntoPointer(false),
AutoCwPostsText: other.IntoPointer("Account frozen"),
WithholdNotesForManualApproval: other.IntoPointer(true),
ScanCreatedPublicNotes: other.IntoPointer(false),
ScanCreatedLocalNotes: other.IntoPointer(false),
ScanCreatedFollowerOnlyNotes: other.IntoPointer(false),
ScanCreatedPrivateNotes: other.IntoPointer(false),
DisallowInteractionsWith: []string{},
FullAdmin: other.IntoPointer(false),
CanAffectOtherAdmins: other.IntoPointer(false),
CanDeleteNotes: other.IntoPointer(false),
CanConfirmWithheldNotes: other.IntoPointer(false),
CanAssignRoles: other.IntoPointer(false),
CanSupressInteractionsBetweenUsers: other.IntoPointer(false),
CanOverwriteDisplayNames: other.IntoPointer(false),
CanManageCustomEmotes: other.IntoPointer(false),
CanViewDeletedNotes: other.IntoPointer(false),
CanRecoverDeletedNotes: other.IntoPointer(false),
CanManageAvatarDecorations: other.IntoPointer(false),
CanManageAds: other.IntoPointer(false),
CanSendAnnouncements: other.IntoPointer(false),
}
var ServerActorRole = Role{
Name: "ServerActor",
Priority: math.MaxUint32,
IsUserRole: true,
IsBuiltIn: true,
CanSendMedia: other.IntoPointer(true),
CanSendCustomEmotes: other.IntoPointer(true),
CanSendCustomReactions: other.IntoPointer(true),
CanSendPublicNotes: other.IntoPointer(true),
CanSendLocalNotes: other.IntoPointer(true),
CanSendFollowerOnlyNotes: other.IntoPointer(true),
CanSendPrivateNotes: other.IntoPointer(true),
CanQuote: other.IntoPointer(true),
CanBoost: other.IntoPointer(true),
CanIncludeLinks: other.IntoPointer(true),
CanIncludeSurvey: other.IntoPointer(true),
CanChangeDisplayName: other.IntoPointer(true),
CanSubmitReports: other.IntoPointer(true),
CanLogin: other.IntoPointer(true),
CanMentionOthers: other.IntoPointer(true),
HasMentionCountLimit: other.IntoPointer(false),
MentionLimit: other.IntoPointer(
uint32(math.MaxUint32),
), // Set this to max, even if not used due to *HasMentionCountLimit == false
AutoNsfwMedia: other.IntoPointer(false),
AutoCwPosts: other.IntoPointer(false),
AutoCwPostsText: nil,
WithholdNotesForManualApproval: other.IntoPointer(false),
ScanCreatedPublicNotes: other.IntoPointer(false),
ScanCreatedLocalNotes: other.IntoPointer(false),
ScanCreatedFollowerOnlyNotes: other.IntoPointer(false),
ScanCreatedPrivateNotes: other.IntoPointer(false),
DisallowInteractionsWith: []string{},
FullAdmin: other.IntoPointer(true),
CanAffectOtherAdmins: other.IntoPointer(true),
CanDeleteNotes: other.IntoPointer(true),
CanConfirmWithheldNotes: other.IntoPointer(true),
CanAssignRoles: other.IntoPointer(true),
CanSupressInteractionsBetweenUsers: other.IntoPointer(true),
CanOverwriteDisplayNames: other.IntoPointer(true),
CanManageCustomEmotes: other.IntoPointer(true),
CanViewDeletedNotes: other.IntoPointer(true),
CanRecoverDeletedNotes: other.IntoPointer(true),
CanManageAvatarDecorations: other.IntoPointer(true),
CanManageAds: other.IntoPointer(true),
CanSendAnnouncements: other.IntoPointer(true),
}
var allDefaultRoles = []*Role{
&DefaultUserRole,
&FullAdminRole,
&AccountFreezeRole,
&ServerActorRole,
}

View file

@ -0,0 +1,53 @@
package models
import (
"time"
"gorm.io/gorm"
)
// Data stored in external types:
// - Custom info fields
// - Being types
// - Tags
// - Relations
// - Pronouns
// - Roles
// - AP remote links
// - Auth methods and tokens (hashed pw, totp key, passkey id)
type User struct {
ID string `gorm:"primarykey"` // ID is a uuid for this account
// Username of the user (eg "max" if the full username is @max@example.com)
// Assume unchangable (once set by a user) to be kind to other implementations
// Would be an easy avenue to fuck with them though
Username string `gorm:"unique"`
CreatedAt time.Time // When this entry was created. Automatically set by gorm
// When this account was last updated. Will also be used for refreshing remote accounts. Automatically set by gorm
UpdatedAt time.Time
// When this entry was deleted (for soft deletions)
// Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to
// If not null, this entry is marked as deleted
DeletedAt gorm.DeletedAt `gorm:"index"`
// Server RemoteServer // `gorm:"foreignKey:ServerId;references:ID"` // The server this user is from
ServerId uint // Id of the server this user is from, needed for including RemoteServer
DisplayName string // The display name of the user. Can be different from the handle
Description string // The description of a user account
IsBot bool // Whether to mark this account as a script controlled one
Icon string // ID of a media file used as icon
Background *string // ID of a media file used as background image
Banner *string // ID of a media file used as banner
Indexable bool // Whether this account can be found by crawlers
PublicKey []byte // The public key of the account
// Whether this account restricts following
// If true, the owner must approve of a follow request first
RestrictedFollow bool
Location *string
Birthday *time.Time
// --- And internal account stuff ---
// Whether the account got verified and is allowed to be active
// For local accounts being active means being allowed to login and perform interactions
// For remote users, if an account is not verified, any interactions it sends are discarded
Verified bool
}

View file

@ -0,0 +1,7 @@
package models
type UserAuthMethod struct {
UserId string
AuthMethod AuthenticationMethodType `gorm:"type:auth_method_type"`
Token []byte
}

View file

@ -0,0 +1,26 @@
package models
import "database/sql/driver"
type AuthenticationMethodType string
const (
AuthMethodPassword AuthenticationMethodType = "password"
AuthMethodGAuth AuthenticationMethodType = "g-auth" // Google Authenticator / totp
AuthMethodMail AuthenticationMethodType = "mail"
AuthMethodPasskey2fa AuthenticationMethodType = "passkey-2fa" // Passkey used as 2fa factor
AuthMethodPasskey AuthenticationMethodType = "passkey" // Passkey as only auth key
)
var AllAuthMethods = []AuthenticationMethodType{
AuthMethodPassword, AuthMethodGAuth, AuthMethodMail, AuthMethodPasskey, AuthMethodPasskey2fa,
}
func (ct *AuthenticationMethodType) Scan(value any) error {
*ct = AuthenticationMethodType(value.([]byte))
return nil
}
func (ct AuthenticationMethodType) Value() (driver.Value, error) {
return string(ct), nil
}

View file

@ -0,0 +1,27 @@
package models
import (
"database/sql/driver"
)
type BeingType string
const (
BEING_HUMAN = BeingType("human")
BEING_CAT = BeingType("cat")
BEING_FOX = BeingType("fox")
BEING_DOG = BeingType("dog")
BEING_ROBOT = BeingType("robot")
BEING_DOLL = BeingType("doll")
)
var AllBeings = []BeingType{BEING_HUMAN, BEING_CAT, BEING_FOX, BEING_DOG, BEING_ROBOT, BEING_DOLL}
func (ct *BeingType) Scan(value any) error {
*ct = BeingType(value.([]byte))
return nil
}
func (ct BeingType) Value() (driver.Value, error) {
return string(ct), nil
}

View file

@ -0,0 +1,6 @@
package models
type UserBeings struct {
UserId string
Being BeingType `gorm:"type:being_type"`
}

View file

@ -0,0 +1,19 @@
package models
import (
"time"
"gorm.io/gorm"
)
type UserInfoField struct {
gorm.Model // Can actually just embed this as is here as those are not something directly exposed :3
Name string
Value string
LastUrlCheckDate *time.Time // Used if the value is an url to somewhere. Empty if value is not an url
// If the value is an url, this attribute indicates whether Linstrom was able to verify ownership
// of the provided url via the common method of
// "Does the target url contain a rel='me' link to the owner's account"
Confirmed bool
UserId string // Id of account this info field belongs to
}

View file

@ -0,0 +1,7 @@
package models
type UserRelation struct {
UserId string
TargetUserId string
Relation RelationType `gorm:"type:relation_type"`
}

View file

@ -0,0 +1,31 @@
package models
import "database/sql/driver"
type RelationType string
const (
RelationFollow RelationType = "follow"
RelationMute RelationType = "mute"
RelationNoBoosts RelationType = "no-boosts"
RelationBlock RelationType = "block"
RelationPreventFollow RelationType = "prevent-follow"
)
// var AllBeings = []BeingType{BEING_HUMAN, BEING_CAT, BEING_FOX, BEING_DOG, BEING_ROBOT, BEING_DOLL}
var AllRelations = []RelationType{
RelationFollow,
RelationMute,
RelationNoBoosts,
RelationBlock,
RelationPreventFollow,
}
func (ct *RelationType) Scan(value any) error {
*ct = RelationType(value.([]byte))
return nil
}
func (ct RelationType) Value() (driver.Value, error) {
return string(ct), nil
}

View file

@ -0,0 +1,21 @@
package models
import "gorm.io/gorm"
type UserRemoteLinks struct {
// ---- Section: gorm
// Sets this struct up as a value that an Account may have
gorm.Model
UserId string
// Just about every link here is optional to accomodate for servers with only minimal accounts
// Minimal being handle, ap link and inbox
ApLink string
ViewLink *string
FollowersLink *string
FollowingLink *string
InboxLink string
OutboxLink *string
FeaturedLink *string
FeaturedTagsLink *string
}

View file

@ -0,0 +1,6 @@
package models
type UserRole struct {
UserId string
RoleId uint
}

View file

@ -0,0 +1,6 @@
package models
type UserTag struct {
UserId string
Tag string
}

1
storage-new/storage.go Normal file
View file

@ -0,0 +1 @@
package storage