Merge branch 'main' of git.mstar.dev:mstar/linstrom
Some checks failed
/ test (push) Has been cancelled
Some checks failed
/ test (push) Has been cancelled
This commit is contained in:
commit
2c57f668e0
33 changed files with 1186 additions and 51 deletions
|
@ -1,28 +1,50 @@
|
||||||
# Contribution Guide
|
# Contribution Guide
|
||||||
|
|
||||||
Thank you for your interest in contributing to Linstrom! All contributors are welcome, regardless of their level of experience.
|
Thank you for your interest in contributing to Linstrom!
|
||||||
|
All contributors are welcome, regardless of their level of experience.
|
||||||
|
|
||||||
|
Unless it's primarely AI generated. Then you're going to be blocked immediately
|
||||||
|
and publicly shamed.
|
||||||
|
|
||||||
## Bug Reports
|
## Bug Reports
|
||||||
|
|
||||||
Use the [bug report issue template](https://git.mstar.dev/mstar/linstrom/issues/new?template=bug-report.md) to file a bug report. Please include a detailed description of the events leading up to the problem, your system configuration, and the program logs. If you're able to reproduce the bug reliably, attaching a debugger to the program, triggering it, and uploading the results would be very helpful.
|
Use the [bug report issue template](https://git.mstar.dev/mstar/linstrom/issues/new?template=bug-report.md)
|
||||||
|
to file a bug report. Please include a detailed description of the events
|
||||||
|
leading up to the problem, your system configuration, and the program logs.
|
||||||
|
If you're able to reproduce the bug reliably, attaching a debugger to the program,
|
||||||
|
triggering it, and uploading the results would be very helpful.
|
||||||
|
|
||||||
This section *should* tell you how to find your logs, attach the debugger, and do whatever else you need for a detailed bug report. But nobody filled it out. Attach a picture of Goatse to your bug reports until we fix this.
|
This section _should_ tell you how to find your logs, attach the debugger,
|
||||||
|
and do whatever else you need for a detailed bug report. But nobody filled it out.
|
||||||
|
Attach a picture of Goatse to your bug reports until we fix this.
|
||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
Use the [feature request issue template](https://git.mstar.dev/mstar/linstrom/issues/new?template=suggestion.md) to suggest new features. Please note that we haven't replaced this placeholder text with the actual criteria we're looking for, which means you should spam us with utterly nonsensical ideas.
|
Use the [feature request issue template](https://git.mstar.dev/mstar/linstrom/issues/new?template=suggestion.md)
|
||||||
|
to suggest new features. Please note that we haven't replaced this placeholder
|
||||||
|
text with the actual criteria we're looking for, which means
|
||||||
|
you should spam us with utterly nonsensical ideas.
|
||||||
|
|
||||||
## Submitting Translations
|
## Submitting Translations
|
||||||
|
|
||||||
Translation files are part of the project codebase, so you'll have to fork the repository and file a pull request (see [Contributing Code](CONTRIBUTING.md#contributing-code) below). You don't need any programming knowledge to edit the translation files, though.
|
Translation files are part of the project codebase, so you'll have to
|
||||||
|
fork the repository and file a pull request
|
||||||
|
(see [Contributing Code](CONTRIBUTING.md#contributing-code) below).
|
||||||
|
You don't need any programming knowledge to edit the translation files, though.
|
||||||
|
|
||||||
This should have been removed and replaced with a quick overview of where the files are and what translators need to do in order to edit them. Nobody did that, so think of this as a free pass to scream profanities into the issue tracker in your native language.
|
This should have been removed and replaced with a quick overview
|
||||||
|
of where the files are and what translators need to do in order to edit them.
|
||||||
|
Nobody did that, so think of this as a free pass to scream profanities
|
||||||
|
into the issue tracker in your native language.
|
||||||
|
|
||||||
## Contributing Code
|
## Contributing Code
|
||||||
|
|
||||||
### Forking
|
### Forking
|
||||||
|
|
||||||
If you'd like to have a go at writing some code for Linstrom, fork the repository, then create a new branch with a name that describes the changes you're making. If there's a [relevant issue](https://git.mstar.dev/mstar/linstrom/issues), include the issue number in the branch name:
|
If you'd like to have a go at writing some code for Linstrom,
|
||||||
|
fork the repository, then create a new branch with a name that
|
||||||
|
describes the changes you're making. If there's a [relevant issue](https://git.mstar.dev/mstar/linstrom/issues),
|
||||||
|
include the issue number in the branch name:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git checkout -b 1337-prevent-computer-from-exploding
|
git checkout -b 1337-prevent-computer-from-exploding
|
||||||
|
@ -30,10 +52,14 @@ git checkout -b 1337-prevent-computer-from-exploding
|
||||||
|
|
||||||
### Development Environment
|
### Development Environment
|
||||||
|
|
||||||
The project utilises Go (version 1.23+) for almost everything and node/npm (version 18+) for building the frontend.
|
The project utilises Go (version 1.23+) for almost everything and
|
||||||
The go side also makes use of `go generate` for multiple things, primarely in the storage module
|
node/npm (version 18+) for building the frontend.
|
||||||
|
The go side also makes use of `go generate` for multiple things,
|
||||||
|
primarely in the storage module
|
||||||
|
|
||||||
We don't have a development environment, because nobody bothered to fill this out. Please add a new build system to the project specifically for your modifications. Bonus points if it's entirely nonsensical, like `npm` in a C project.
|
We don't have a development environment, because nobody bothered to fill this out.
|
||||||
|
Please add a new build system to the project specifically for your modifications.
|
||||||
|
Bonus points if it's entirely nonsensical, like `npm` in a C project.
|
||||||
|
|
||||||
### Code Style
|
### Code Style
|
||||||
|
|
||||||
|
@ -43,15 +69,18 @@ For anything node: uhhh, yolo, idk yet
|
||||||
|
|
||||||
### Pull Requests
|
### Pull Requests
|
||||||
|
|
||||||
Once your modifications are complete, you'll want to fetch the latest changes from this repository, rebase your branch, and publish your changes:
|
Once your modifications are complete, you'll want to fetch the latest changes
|
||||||
|
from this repository, rebase your branch, and publish your changes:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git remote add upstream https://git.mstar.dev/mstar/linstrom.git
|
git remote add upstream https://git.mstar.dev/mstar/linstrom.git
|
||||||
git checkout main
|
git checkout main
|
||||||
git pull upstream main
|
git pull upstream main
|
||||||
git checkout 1337-prevent-computer-from-exploding
|
git checkout 1337-prevent-computer-from-exploding
|
||||||
git rebase master
|
git rebase main
|
||||||
git push --set-upstream origin 1337-prevent-computer-from-exploding
|
git push --set-upstream origin 1337-prevent-computer-from-exploding
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, you can [create a pull request](https://git.mstar.dev/mstar/linstrom/pulls). It might not get approved, or you might have to make some additional changes to your code - but don't give up!
|
Finally, you can [create a pull request](https://git.mstar.dev/mstar/linstrom/pulls).
|
||||||
|
It might not get approved, or you might have to make some additional changes
|
||||||
|
to your code - but don't give up!
|
||||||
|
|
22
README.md
22
README.md
|
@ -1,5 +1,8 @@
|
||||||
Linstrom is a new social media server with the focus on providing users, moderators and admins alike
|
# Linstrom
|
||||||
simple, yet powerful features to moderate the experience.
|
|
||||||
|
Linstrom is a new social media server with the focus on providing
|
||||||
|
users, moderators and admins alike simple, yet powerful features
|
||||||
|
to moderate the experience.
|
||||||
|
|
||||||
## Federation
|
## Federation
|
||||||
|
|
||||||
|
@ -20,3 +23,18 @@ And they all have different woes with their software.
|
||||||
I want to try and make a server that combines the strengths of various
|
I want to try and make a server that combines the strengths of various
|
||||||
project's tooling while also hopefully solving
|
project's tooling while also hopefully solving
|
||||||
some of the weaknesses.
|
some of the weaknesses.
|
||||||
|
|
||||||
|
## Permission system
|
||||||
|
|
||||||
|
All permissions operate on a role based system, similar to what Discord offers.
|
||||||
|
Accounts in power (admins and moderators) are only able to manipulate other accounts
|
||||||
|
via this role system. This prevents moderators from directly manipulating accounts,
|
||||||
|
such as changing their username, info fields or otherwise, while still providing
|
||||||
|
ample control over the activities these accounts can perform.
|
||||||
|
|
||||||
|
These same roles, while most powerful for local accounts, also apply to remote accounts
|
||||||
|
as much as realisticly possible. Preventing a login on a remote server for example
|
||||||
|
might not be possible, but blocking all inbound traffic from that account is.
|
||||||
|
|
||||||
|
However, roles are not only for moderators and admins to use.
|
||||||
|
Normal accounts can also make use of them, all be it in a more limited way.
|
||||||
|
|
17
auth/auth.go
Normal file
17
auth/auth.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.mstar.dev/mstar/linstrom/storage"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authentication struct {
|
||||||
|
// For when in-depth access is needed
|
||||||
|
db *gorm.DB
|
||||||
|
// Primary method to acquire account data
|
||||||
|
store *storage.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuth(db *gorm.DB, store *storage.Storage) *Authentication {
|
||||||
|
return &Authentication{db, store}
|
||||||
|
}
|
118
auth/checks.go
Normal file
118
auth/checks.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
|
"git.mstar.dev/mstar/linstrom/storage"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Can actorId read the account with targetId?
|
||||||
|
func (a *Authentication) CanReadAccount(actorId *string, targetId string) bool {
|
||||||
|
targetAccount, err := a.store.FindAccountById(targetId)
|
||||||
|
if err != nil {
|
||||||
|
if err == storage.ErrEntryNotFound {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("account-id", targetId).
|
||||||
|
Msg("Failed to receive account for permission check")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if actorId == nil {
|
||||||
|
// TODO: Decide if roles should have a field to declare an account as follow only/hidden
|
||||||
|
// and then check for that flag here
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
roles, err := a.store.FindRolesByNames(targetAccount.Roles)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Strs("role-names", targetAccount.Roles).
|
||||||
|
Msg("Failed to get roles for target account")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
combined := storage.CollapseRolesIntoOne(roles...)
|
||||||
|
if sliceutils.Contains(combined.BlockedUsers, *actorId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can actorId edit the account with targetId?
|
||||||
|
// If actorId is nil, it is assumed to be an anonymous user trying to edit the target account
|
||||||
|
// if targetId is nil, it is assumed that the actor is editing themselves
|
||||||
|
func (a *Authentication) CanEditAccount(actorId *string, targetId *string) bool {
|
||||||
|
// FIXME: This entire function feels wrong, idk
|
||||||
|
// Only the owner of an account should be able to edit said account's data
|
||||||
|
// But how do moderation actions play with this? Do they count as edit or as something separate?
|
||||||
|
if actorId == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if targetId == nil {
|
||||||
|
targetId = actorId
|
||||||
|
}
|
||||||
|
targetAccount, err := a.store.FindAccountById(*targetId)
|
||||||
|
if err != nil {
|
||||||
|
if err != storage.ErrEntryNotFound {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("target-id", *targetId).
|
||||||
|
Msg("Failed to receive account for permission checks")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if targetId == actorId {
|
||||||
|
targetRoles, err := a.store.FindRolesByNames(targetAccount.Roles)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Strs("role-names", targetAccount.Roles).
|
||||||
|
Msg("Failed to get roles from storage")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
combined := storage.CollapseRolesIntoOne(targetRoles...)
|
||||||
|
return *combined.CanLogin
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can actorId delete the account with targetId?
|
||||||
|
// If actorId is nil, it is assumed to be an anonymous user trying to delete the target account
|
||||||
|
// if targetId is nil, it is assumed that the actor is deleting themselves
|
||||||
|
func (a *Authentication) CanDeleteAccount(actorId *string, targetId *string) bool {
|
||||||
|
if actorId == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
acc, err := a.store.FindAccountById(*actorId)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Logging
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
roles, err := a.store.FindRolesByNames(acc.Roles)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Logging
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
collapsed := storage.CollapseRolesIntoOne(roles...)
|
||||||
|
if targetId == nil {
|
||||||
|
return *collapsed.CanLogin
|
||||||
|
} else {
|
||||||
|
return *collapsed.CanDeleteAccounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can actorId create a new post at all?
|
||||||
|
// Specific restrictions regarding the content are not checked
|
||||||
|
func (a *Authentication) CanCreatePost(actorId string) bool { return true }
|
||||||
|
|
||||||
|
// Ensures that a given post conforms with all roles attached to the author account.
|
||||||
|
// Returns the conforming note (or nil of it can't be changed to conform)
|
||||||
|
// and whether the note was changed
|
||||||
|
func (a *Authentication) EnsureNoteConformsWithRoles(note *storage.Note) (*storage.Note, bool) {
|
||||||
|
return note, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the given note conform with the roles attached to the author account?
|
||||||
|
func (a *Authentication) DoesNoteConform(note *storage.Note) bool { return true }
|
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
|
|
@ -1,9 +1,9 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||||
"git.mstar.dev/mstar/linstrom/util"
|
"git.mstar.dev/mstar/linstrom/util"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,6 +80,9 @@ type Role struct {
|
||||||
// Internal ids of accounts blocked by this role
|
// Internal ids of accounts blocked by this role
|
||||||
BlockedUsers []string `gorm:"type:bytes;serializer:gob"` // Local
|
BlockedUsers []string `gorm:"type:bytes;serializer:gob"` // Local
|
||||||
CanSubmitReports *bool // Local & remote
|
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
|
CanLogin *bool // Local
|
||||||
|
|
||||||
CanMentionOthers *bool // Local & remote
|
CanMentionOthers *bool // Local & remote
|
||||||
|
@ -125,6 +128,7 @@ type Role struct {
|
||||||
CanManageAvatarDecorations *bool // Local
|
CanManageAvatarDecorations *bool // Local
|
||||||
CanManageAds *bool // Local
|
CanManageAds *bool // Local
|
||||||
CanSendAnnouncements *bool // Local
|
CanSendAnnouncements *bool // Local
|
||||||
|
CanDeleteAccounts *bool // Local
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package storage
|
|
||||||
|
|
||||||
type AccountRestriction int64
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Account has no restrictions applied
|
|
||||||
ACCOUNT_RESTRICTION_NONE = AccountRestriction(0)
|
|
||||||
// All messages of the account get a content warning applied if none is set
|
|
||||||
// Warning could be something like "Message auto-CWd by server"
|
|
||||||
ACCOUNT_RESTRICTION_AUTO_CW = AccountRestriction(1 << iota)
|
|
||||||
// Disable accessing the account via login or access token
|
|
||||||
ACCOUNT_RESTRICTION_DISABLE_LOGIN
|
|
||||||
// Disable sending activities to other servers if the account is local
|
|
||||||
// Or reject all activities if the account is remote
|
|
||||||
ACCOUNT_RESTRICTION_NO_FEDERATION
|
|
||||||
// Disallow sending direct messages from that account
|
|
||||||
ACCOUNT_RESTRICTION_NO_DMS
|
|
||||||
// Disallow sending follower only messages from that account
|
|
||||||
ACCOUNT_RESTRICTION_NO_FOLLOWER_POSTS
|
|
||||||
// Disable outbound follow requests (restricted account can't send follow requests)
|
|
||||||
ACCOUNT_RESTRICTION_DISABLE_OUTBOUND_FOLLOWS
|
|
||||||
// Disable inbound follow requests (all follow requests to that account are automatically rejected)
|
|
||||||
ACCOUNT_RESTRICTION_DISABLE_INBOUND_FOLLOWS
|
|
||||||
// Forces all posts by that account to be follower only
|
|
||||||
ACCOUNT_RESTRICTION_FORCE_FOLLOWERS_ONLY
|
|
||||||
// Disable all outbound activities of an account.
|
|
||||||
// Includes sending, updating or deleting own notes
|
|
||||||
// as well as boosting or reacting to any notes
|
|
||||||
ACCOUNT_RESTRICTION_DISABLE_ACTIVITIES
|
|
||||||
// Account can only be viewed while logged in or via verified requests to the AP endpoints
|
|
||||||
ACCOUNT_RESTRICTIONS_NO_PUBLIC_ACCESS
|
|
||||||
// Force tag all media the account posts as sensitive
|
|
||||||
ACCOUNT_RESTRICTIONS_FORCE_MEDIA_SENSITIVE
|
|
||||||
)
|
|
Loading…
Reference in a new issue