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:
parent
0639cde4f2
commit
714f528641
27 changed files with 983 additions and 0 deletions
64
storage-new/gormLogger.go
Normal file
64
storage-new/gormLogger.go
Normal 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
102
storage-new/migrations.go
Normal 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
|
||||
}
|
12
storage-new/models/Emote.go
Normal file
12
storage-new/models/Emote.go
Normal 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
|
||||
}
|
29
storage-new/models/MediaMetadata.go
Normal file
29
storage-new/models/MediaMetadata.go
Normal 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
|
||||
}
|
33
storage-new/models/Note.go
Normal file
33
storage-new/models/Note.go
Normal 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
|
||||
}
|
28
storage-new/models/NoteAccessLevelType.go
Normal file
28
storage-new/models/NoteAccessLevelType.go
Normal 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
|
||||
}
|
6
storage-new/models/NoteAttachments.go
Normal file
6
storage-new/models/NoteAttachments.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type NoteToAttachment struct {
|
||||
UserId string
|
||||
AttachmentId string
|
||||
}
|
6
storage-new/models/NoteEmotes.go
Normal file
6
storage-new/models/NoteEmotes.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type NoteToEmote struct {
|
||||
UserId string
|
||||
EmoteId string
|
||||
}
|
6
storage-new/models/NotePings.go
Normal file
6
storage-new/models/NotePings.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type NoteToPing struct {
|
||||
UserId string
|
||||
PingTargetId string
|
||||
}
|
6
storage-new/models/NoteTags.go
Normal file
6
storage-new/models/NoteTags.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type NoteTag struct {
|
||||
UserId string
|
||||
Tag string
|
||||
}
|
10
storage-new/models/Reaction.go
Normal file
10
storage-new/models/Reaction.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Reaction struct {
|
||||
gorm.Model
|
||||
NoteId string
|
||||
ReactorId string
|
||||
EmoteId uint
|
||||
}
|
12
storage-new/models/RemoteServer.go
Normal file
12
storage-new/models/RemoteServer.go
Normal 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
|
||||
}
|
37
storage-new/models/RemoteServerSoftwareType.go
Normal file
37
storage-new/models/RemoteServerSoftwareType.go
Normal 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
182
storage-new/models/Role.go
Normal 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
|
||||
*/
|
240
storage-new/models/RolesDefaults.go
Normal file
240
storage-new/models/RolesDefaults.go
Normal 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,
|
||||
}
|
53
storage-new/models/User.go
Normal file
53
storage-new/models/User.go
Normal 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
|
||||
}
|
7
storage-new/models/UserAuthentication.go
Normal file
7
storage-new/models/UserAuthentication.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type UserAuthMethod struct {
|
||||
UserId string
|
||||
AuthMethod AuthenticationMethodType `gorm:"type:auth_method_type"`
|
||||
Token []byte
|
||||
}
|
26
storage-new/models/UserAuthenticationMethod.go
Normal file
26
storage-new/models/UserAuthenticationMethod.go
Normal 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
|
||||
}
|
27
storage-new/models/UserBeingType.go
Normal file
27
storage-new/models/UserBeingType.go
Normal 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
|
||||
}
|
6
storage-new/models/UserBeings.go
Normal file
6
storage-new/models/UserBeings.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type UserBeings struct {
|
||||
UserId string
|
||||
Being BeingType `gorm:"type:being_type"`
|
||||
}
|
19
storage-new/models/UserInfoField.go
Normal file
19
storage-new/models/UserInfoField.go
Normal 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
|
||||
}
|
7
storage-new/models/UserRelation.go
Normal file
7
storage-new/models/UserRelation.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package models
|
||||
|
||||
type UserRelation struct {
|
||||
UserId string
|
||||
TargetUserId string
|
||||
Relation RelationType `gorm:"type:relation_type"`
|
||||
}
|
31
storage-new/models/UserRelationType.go
Normal file
31
storage-new/models/UserRelationType.go
Normal 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
|
||||
}
|
21
storage-new/models/UserRemote.go
Normal file
21
storage-new/models/UserRemote.go
Normal 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
|
||||
}
|
6
storage-new/models/UserRoles.go
Normal file
6
storage-new/models/UserRoles.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type UserRole struct {
|
||||
UserId string
|
||||
RoleId uint
|
||||
}
|
6
storage-new/models/UserTags.go
Normal file
6
storage-new/models/UserTags.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type UserTag struct {
|
||||
UserId string
|
||||
Tag string
|
||||
}
|
1
storage-new/storage.go
Normal file
1
storage-new/storage.go
Normal file
|
@ -0,0 +1 @@
|
|||
package storage
|
Loading…
Reference in a new issue