Compare commits
No commits in common. "954e4c5a345a603956b483790cdf149f1c19007e" and "befbc0794614c87389f19af78f940c99536b8124" have entirely different histories.
954e4c5a34
...
befbc07946
12 changed files with 51 additions and 410 deletions
5
go.mod
5
go.mod
|
@ -19,9 +19,9 @@ require (
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/minio/minio-go/v7 v7.0.80
|
github.com/minio/minio-go/v7 v7.0.80
|
||||||
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e
|
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e
|
||||||
github.com/pquerna/otp v1.4.0
|
|
||||||
github.com/redis/go-redis/v9 v9.0.2
|
github.com/redis/go-redis/v9 v9.0.2
|
||||||
github.com/rs/zerolog v1.33.0
|
github.com/rs/zerolog v1.33.0
|
||||||
|
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||||
gitlab.com/mstarongitlab/goap v1.1.0
|
gitlab.com/mstarongitlab/goap v1.1.0
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/image v0.20.0
|
golang.org/x/image v0.20.0
|
||||||
|
@ -29,6 +29,7 @@ require (
|
||||||
gorm.io/gen v0.3.26
|
gorm.io/gen v0.3.26
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
gorm.io/plugin/dbresolver v1.5.3
|
gorm.io/plugin/dbresolver v1.5.3
|
||||||
|
github.com/pquerna/otp v1.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -44,6 +45,7 @@ require (
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
|
github.com/go-test/deep v1.1.1 // indirect
|
||||||
github.com/go-webauthn/x v0.1.14 // indirect
|
github.com/go-webauthn/x v0.1.14 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
@ -77,6 +79,7 @@ require (
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/stretchr/testify v1.9.0 // indirect
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
github.com/tetratelabs/wazero v1.7.3 // indirect
|
github.com/tetratelabs/wazero v1.7.3 // indirect
|
||||||
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
gitlab.com/mstarongitlab/goutils v1.3.0 // indirect
|
gitlab.com/mstarongitlab/goutils v1.3.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -114,6 +114,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
|
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
|
github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc=
|
||||||
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
|
github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0=
|
||||||
github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
|
github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0=
|
||||||
|
@ -325,8 +327,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw=
|
github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw=
|
||||||
github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||||
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
|
||||||
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
||||||
|
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
15
goals.md
15
goals.md
|
@ -1,15 +1,10 @@
|
||||||
# Feature goals
|
|
||||||
|
|
||||||
## Easy
|
## Easy
|
||||||
|
|
||||||
- optional content filter with Microsoft's ai scan thing (user and server level)
|
- optional content filter with Microsoft's ai scan thing (user and server level)
|
||||||
- lockdown mode (all incoming stuff will be bonked immediately) (user and server)
|
- lockdown mode (all incoming stuff will be bonked immediately) (user and server)
|
||||||
- Post highlighting (opposite of muting) where if a post contains some
|
- Post highlighting (opposite of muting) where if a post contains some specific thing, it gets some highlight
|
||||||
specific thing, it gets some highlight
|
|
||||||
- Maybe even with different highlighting options
|
- Maybe even with different highlighting options
|
||||||
|
|
||||||
## Medium
|
## Medium
|
||||||
|
|
||||||
- optional automatic server screening
|
- optional automatic server screening
|
||||||
- metadata sharing (thing like link previews or blocklists)
|
- metadata sharing (thing like link previews or blocklists)
|
||||||
- asks (in some way that is compatible with wafrn hopefully)
|
- asks (in some way that is compatible with wafrn hopefully)
|
||||||
|
@ -17,18 +12,14 @@
|
||||||
- Database converter (Masto/Akoma/Mk -> Linstrom, maybe also other way around)
|
- Database converter (Masto/Akoma/Mk -> Linstrom, maybe also other way around)
|
||||||
|
|
||||||
## Hard
|
## Hard
|
||||||
|
|
||||||
- custom "ads" created and controlled by server admins
|
- custom "ads" created and controlled by server admins
|
||||||
- some sort of subscription/payment system (opt-in (you have to opt in to
|
- some sort of subscription/payment system (opt-in (you have to opt in to potentially see monetised stuff in the first place))
|
||||||
potentially see monetised stuff in the first place))
|
|
||||||
- extended account moderation (user and server)
|
- extended account moderation (user and server)
|
||||||
- custom api for working around AP being a pos:
|
- custom api for working around AP being a pos:
|
||||||
- includes messages always being encrypted
|
- includes messages always being encrypted
|
||||||
- bunch of other optimisations
|
- bunch of other optimisations
|
||||||
- Utilise `net/rpc`
|
|
||||||
|
|
||||||
## Variable difficutly
|
|
||||||
|
|
||||||
|
# Variable difficutly
|
||||||
- Multiple built-in frontends
|
- Multiple built-in frontends
|
||||||
- Primary using ember, focus on good looking and most feature complete
|
- Primary using ember, focus on good looking and most feature complete
|
||||||
- Modifyable using htmx (not sure on this one yet)
|
- Modifyable using htmx (not sure on this one yet)
|
||||||
|
|
8
main.go
8
main.go
|
@ -20,7 +20,6 @@ import (
|
||||||
storagenew "git.mstar.dev/mstar/linstrom/storage-new"
|
storagenew "git.mstar.dev/mstar/linstrom/storage-new"
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||||
"git.mstar.dev/mstar/linstrom/storage/cache"
|
"git.mstar.dev/mstar/linstrom/storage/cache"
|
||||||
webdebug "git.mstar.dev/mstar/linstrom/web/debug"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Add frontend overwrite
|
// TODO: Add frontend overwrite
|
||||||
|
@ -125,11 +124,4 @@ func newServer() {
|
||||||
if err = storagenew.InsertSelf(); err != nil {
|
if err = storagenew.InsertSelf(); err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to insert self properly")
|
log.Fatal().Err(err).Msg("Failed to insert self properly")
|
||||||
}
|
}
|
||||||
if *shared.FlagStartDebugServer {
|
|
||||||
log.Info().Msg("Starting debug server")
|
|
||||||
// TODO: Move into goroutine once public server also exists
|
|
||||||
if err = webdebug.New().Start(); err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Debug server failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,7 @@ var (
|
||||||
false,
|
false,
|
||||||
"If set, the server will only validate the config (or write the default one) and then quit",
|
"If set, the server will only validate the config (or write the default one) and then quit",
|
||||||
)
|
)
|
||||||
FlagStartNew *bool = flag.Bool("new", false, "Start the new system")
|
FlagStartNew *bool = flag.Bool("new", false, "Start the new system")
|
||||||
FlagStartDebugServer *bool = flag.Bool(
|
|
||||||
"debugserver",
|
|
||||||
false,
|
|
||||||
"Also start the local debugging server",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func flagUsage() {
|
func flagUsage() {
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package shared
|
|
||||||
|
|
||||||
type Clonable interface {
|
|
||||||
Clone() Clonable
|
|
||||||
}
|
|
||||||
|
|
||||||
type Santisable interface {
|
|
||||||
Sanitize()
|
|
||||||
}
|
|
|
@ -1457,20 +1457,17 @@ type IUserDo interface {
|
||||||
schema.Tabler
|
schema.Tabler
|
||||||
|
|
||||||
GetByUsername(username string) (result *models.User, err error)
|
GetByUsername(username string) (result *models.User, err error)
|
||||||
GetPagedTruePublic(pageNr uint) (result []models.User, err error)
|
|
||||||
GetPagedAllDeleted(pageNr uint) (result []models.User, err error)
|
|
||||||
GetPagedAllNonDeleted(pageNr uint) (result []models.User, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a user by a username
|
// Get a user by a username
|
||||||
//
|
//
|
||||||
// SELECT * FROM @@table WHERE username = @username AND deleted_at IS NULL LIMIT 1
|
// SELECT * FROM @@table WHERE username = @username LIMIT 1
|
||||||
func (u userDo) GetByUsername(username string) (result *models.User, err error) {
|
func (u userDo) GetByUsername(username string) (result *models.User, err error) {
|
||||||
var params []interface{}
|
var params []interface{}
|
||||||
|
|
||||||
var generateSQL strings.Builder
|
var generateSQL strings.Builder
|
||||||
params = append(params, username)
|
params = append(params, username)
|
||||||
generateSQL.WriteString("SELECT * FROM users WHERE username = ? AND deleted_at IS NULL LIMIT 1 ")
|
generateSQL.WriteString("SELECT * FROM users WHERE username = ? LIMIT 1 ")
|
||||||
|
|
||||||
var executeSQL *gorm.DB
|
var executeSQL *gorm.DB
|
||||||
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Take(&result) // ignore_security_alert
|
||||||
|
@ -1479,79 +1476,6 @@ func (u userDo) GetByUsername(username string) (result *models.User, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all true public accounts (verified & no restricted follow & indexable)
|
|
||||||
// in a paged manner, sorted by date saved
|
|
||||||
//
|
|
||||||
// SELECT * FROM @@table WHERE
|
|
||||||
//
|
|
||||||
// deleted_at IS NULL AND
|
|
||||||
// verified = true AND
|
|
||||||
// restricted_follow = false AND
|
|
||||||
// indexable = true
|
|
||||||
//
|
|
||||||
// ORDER BY created_at ASC
|
|
||||||
// LIMIT 50
|
|
||||||
// OFFSET @pageNr * 50
|
|
||||||
func (u userDo) GetPagedTruePublic(pageNr uint) (result []models.User, err error) {
|
|
||||||
var params []interface{}
|
|
||||||
|
|
||||||
var generateSQL strings.Builder
|
|
||||||
params = append(params, pageNr)
|
|
||||||
generateSQL.WriteString("SELECT * FROM users WHERE deleted_at IS NULL AND verified = true AND restricted_follow = false AND indexable = true ORDER BY created_at ASC LIMIT 50 OFFSET ? * 50 ")
|
|
||||||
|
|
||||||
var executeSQL *gorm.DB
|
|
||||||
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Find(&result) // ignore_security_alert
|
|
||||||
err = executeSQL.Error
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all deleted accounts in a paged manner, sorted by date saved
|
|
||||||
//
|
|
||||||
// SELECT * FROM @@table WHERE
|
|
||||||
//
|
|
||||||
// deleted_at IS NOT NULL AND
|
|
||||||
//
|
|
||||||
// ORDER BY created_at ASC
|
|
||||||
// LIMIT 50
|
|
||||||
// OFFSET @pageNr * 50
|
|
||||||
func (u userDo) GetPagedAllDeleted(pageNr uint) (result []models.User, err error) {
|
|
||||||
var params []interface{}
|
|
||||||
|
|
||||||
var generateSQL strings.Builder
|
|
||||||
params = append(params, pageNr)
|
|
||||||
generateSQL.WriteString("SELECT * FROM users WHERE deleted_at IS NOT NULL AND ORDER BY created_at ASC LIMIT 50 OFFSET ? * 50 ")
|
|
||||||
|
|
||||||
var executeSQL *gorm.DB
|
|
||||||
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Find(&result) // ignore_security_alert
|
|
||||||
err = executeSQL.Error
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all accounts that aren't deleted in a paged manner, sorted by date saved
|
|
||||||
//
|
|
||||||
// SELECT * FROM @@table WHERE
|
|
||||||
//
|
|
||||||
// deleted_at IS NULL
|
|
||||||
//
|
|
||||||
// ORDER BY created_at ASC
|
|
||||||
// LIMIT 50
|
|
||||||
// OFFSET @pageNr * 50
|
|
||||||
func (u userDo) GetPagedAllNonDeleted(pageNr uint) (result []models.User, err error) {
|
|
||||||
var params []interface{}
|
|
||||||
|
|
||||||
var generateSQL strings.Builder
|
|
||||||
params = append(params, pageNr)
|
|
||||||
generateSQL.WriteString("SELECT * FROM users WHERE deleted_at IS NULL ORDER BY created_at ASC LIMIT 50 OFFSET ? * 50 ")
|
|
||||||
|
|
||||||
var executeSQL *gorm.DB
|
|
||||||
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Find(&result) // ignore_security_alert
|
|
||||||
err = executeSQL.Error
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u userDo) Debug() IUserDo {
|
func (u userDo) Debug() IUserDo {
|
||||||
return u.withDO(u.DO.Debug())
|
return u.withDO(u.DO.Debug())
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,91 +25,63 @@ type User struct {
|
||||||
// identifier for users and other servers, especially when changing the username
|
// identifier for users and other servers, especially when changing the username
|
||||||
// (username != display name) might be a future feature
|
// (username != display name) might be a future feature
|
||||||
// Same also applies for other types that use a UUID as primary key
|
// Same also applies for other types that use a UUID as primary key
|
||||||
ID string `gorm:"primarykey;type:uuid;default:gen_random_uuid()" json:"id"`
|
ID string `gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
|
||||||
// Username of the user (eg "max" if the full username is @max@example.com)
|
// 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
|
// Assume unchangable (once set by a user) to be kind to other implementations
|
||||||
// Would be an easy avenue to fuck with them though
|
// Would be an easy avenue to fuck with them though
|
||||||
Username string `gorm:"unique" json:"username"`
|
Username string `gorm:"unique"`
|
||||||
CreatedAt time.Time ` json:"created_at"` // When this entry was created. Automatically set by gorm
|
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
|
// When this account was last updated. Will also be used for refreshing remote accounts. Automatically set by gorm
|
||||||
UpdatedAt time.Time ` json:"updated_at"`
|
UpdatedAt time.Time
|
||||||
// When this entry was deleted (for soft deletions)
|
// 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
|
// 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
|
// If not null, this entry is marked as deleted
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
Server RemoteServer ` json:"-"`
|
Server RemoteServer
|
||||||
ServerId uint ` json:"server_id"` // Id of the server this user is from, needed for including RemoteServer
|
ServerId uint // Id of the server this user is from, needed for including RemoteServer
|
||||||
DisplayName string ` json:"display_name"` // The display name of the user. Can be different from the handle
|
DisplayName string // The display name of the user. Can be different from the handle
|
||||||
Description string ` json:"description"` // The description of a user account
|
Description string // The description of a user account
|
||||||
IsBot bool ` json:"is_bot"` // Whether to mark this account as a script controlled one
|
IsBot bool // Whether to mark this account as a script controlled one
|
||||||
Icon *MediaMetadata ` json:"-"`
|
Icon *MediaMetadata
|
||||||
IconId sql.NullString ` json:"icon_id"` // ID of a media file used as icon
|
IconId sql.NullString // ID of a media file used as icon
|
||||||
Background *MediaMetadata ` json:"-"`
|
Background *MediaMetadata
|
||||||
BackgroundId sql.NullString ` json:"background_id"` // ID of a media file used as background image
|
BackgroundId sql.NullString // ID of a media file used as background image
|
||||||
Banner *MediaMetadata ` json:"-"`
|
Banner *MediaMetadata
|
||||||
BannerId sql.NullString ` json:"banner_id"` // ID of a media file used as banner
|
BannerId sql.NullString // ID of a media file used as banner
|
||||||
Indexable bool ` json:"indexable"` // Whether this account can be found by crawlers
|
Indexable bool // Whether this account can be found by crawlers
|
||||||
PublicKey []byte ` json:"public_key"` // The public key of the account
|
PublicKey []byte // The public key of the account
|
||||||
// Whether this account restricts following
|
// Whether this account restricts following
|
||||||
// If true, the owner must approve of a follow request first
|
// If true, the owner must approve of a follow request first
|
||||||
RestrictedFollow bool ` json:"restricted_follow"`
|
RestrictedFollow bool
|
||||||
|
|
||||||
Location sql.NullString `json:"location"`
|
Location sql.NullString
|
||||||
Birthday sql.NullTime `json:"birthday"`
|
Birthday sql.NullTime
|
||||||
|
|
||||||
// Whether the account got verified and is allowed to be active
|
// 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 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
|
// For remote users, if an account is not verified, any interactions it sends are discarded
|
||||||
Verified bool `json:"verified"`
|
Verified bool
|
||||||
// 64 byte unique id for passkeys, because UUIDs are 128 bytes and passkey spec says 64 bytes max
|
// 64 byte unique id for passkeys, because UUIDs are 128 bytes and passkey spec says 64 bytes max
|
||||||
// In theory, could also slash Id in half, but that would be a lot more calculations than the
|
// In theory, could also slash Id in half, but that would be a lot more calculations than the
|
||||||
// saved space is worth
|
// saved space is worth
|
||||||
PasskeyId []byte `json:"-"`
|
PasskeyId []byte
|
||||||
FinishedRegistration bool `json:"-"` // Whether this account has completed registration yet
|
FinishedRegistration bool // Whether this account has completed registration yet
|
||||||
PrivateKey []byte `json:"-"`
|
PrivateKey []byte
|
||||||
|
|
||||||
// ---- "Remote" linked values
|
// ---- "Remote" linked values
|
||||||
InfoFields []UserInfoField `json:"-"`
|
InfoFields []UserInfoField
|
||||||
BeingTypes []UserToBeing `json:"-"`
|
BeingTypes []UserToBeing
|
||||||
Tags []UserToTag `json:"-"`
|
Tags []UserToTag
|
||||||
Relations []UserToUserRelation `json:"-"`
|
Relations []UserToUserRelation
|
||||||
Pronouns []UserToPronoun `json:"-"`
|
Pronouns []UserToPronoun
|
||||||
Roles []UserToRole `json:"-"`
|
Roles []UserToRole
|
||||||
RemoteInfo *UserRemoteLinks `json:"-"`
|
RemoteInfo *UserRemoteLinks
|
||||||
AuthMethods []UserAuthMethod `json:"-"`
|
AuthMethods []UserAuthMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
type IUser interface {
|
type IUser interface {
|
||||||
// Get a user by a username
|
// Get a user by a username
|
||||||
//
|
//
|
||||||
// SELECT * FROM @@table WHERE username = @username AND deleted_at IS NULL LIMIT 1
|
// SELECT * FROM @@table WHERE username = @username LIMIT 1
|
||||||
GetByUsername(username string) (*gen.T, error)
|
GetByUsername(username string) (*gen.T, error)
|
||||||
// Get all true public accounts (verified & no restricted follow & indexable)
|
|
||||||
// in a paged manner, sorted by date saved
|
|
||||||
//
|
|
||||||
// SELECT * FROM @@table WHERE
|
|
||||||
// deleted_at IS NULL AND
|
|
||||||
// verified = true AND
|
|
||||||
// restricted_follow = false AND
|
|
||||||
// indexable = true
|
|
||||||
// ORDER BY created_at ASC
|
|
||||||
// LIMIT 50
|
|
||||||
// OFFSET @pageNr * 50
|
|
||||||
GetPagedTruePublic(pageNr uint) ([]gen.T, error)
|
|
||||||
// Get all deleted accounts in a paged manner, sorted by date saved
|
|
||||||
//
|
|
||||||
// SELECT * FROM @@table WHERE
|
|
||||||
// deleted_at IS NOT NULL AND
|
|
||||||
// ORDER BY created_at ASC
|
|
||||||
// LIMIT 50
|
|
||||||
// OFFSET @pageNr * 50
|
|
||||||
GetPagedAllDeleted(pageNr uint) ([]gen.T, error)
|
|
||||||
// Get all accounts that aren't deleted in a paged manner, sorted by date saved
|
|
||||||
//
|
|
||||||
// SELECT * FROM @@table WHERE
|
|
||||||
// deleted_at IS NULL
|
|
||||||
// ORDER BY created_at ASC
|
|
||||||
// LIMIT 50
|
|
||||||
// OFFSET @pageNr * 50
|
|
||||||
GetPagedAllNonDeleted(pageNr uint) ([]gen.T, error)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package webpublic
|
package web
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
package webdebug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/rand"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
httputils "git.mstar.dev/mstar/goutils/http"
|
|
||||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
|
||||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getNonDeletedUsers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
pageStr := r.FormValue("page")
|
|
||||||
page := 0
|
|
||||||
if pageStr != "" {
|
|
||||||
var err error
|
|
||||||
page, err = strconv.Atoi(pageStr)
|
|
||||||
if err != nil {
|
|
||||||
httputils.HttpErr(w, 0, "page is not a number", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
users, err := dbgen.User.GetPagedAllNonDeleted(uint(page))
|
|
||||||
if err != nil {
|
|
||||||
httputils.HttpErr(w, 0, "failed to get users", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
marshalled, err := json.Marshal(sliceutils.Map(users, func(t models.User) webshared.User {
|
|
||||||
u := webshared.User{}
|
|
||||||
u.FromModel(&t)
|
|
||||||
return u
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
httputils.HttpErr(w, 0, "failed to marshal users", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprint(w, string(marshalled))
|
|
||||||
}
|
|
||||||
|
|
||||||
func createLocalUser(w http.ResponseWriter, r *http.Request) {
|
|
||||||
type Inbound struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Displayname string `json:"displayname"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Birthday *time.Time `json:"birthday"`
|
|
||||||
Location *string `json:"location"`
|
|
||||||
IsBot bool `json:"is_bot"`
|
|
||||||
}
|
|
||||||
jsonDecoder := json.NewDecoder(r.Body)
|
|
||||||
data := Inbound{}
|
|
||||||
err := jsonDecoder.Decode(&data)
|
|
||||||
if err != nil {
|
|
||||||
httputils.HttpErr(w, 0, "decode failed", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
|
||||||
pkeyId := make([]byte, 64)
|
|
||||||
_, err = rand.Read(pkeyId)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to generate passkey id")
|
|
||||||
httputils.HttpErr(w, 0, "failed to generate passkey id", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u := dbgen.User
|
|
||||||
query := u.Select(
|
|
||||||
u.Username,
|
|
||||||
u.DisplayName,
|
|
||||||
u.Description,
|
|
||||||
u.IsBot,
|
|
||||||
u.ServerId,
|
|
||||||
u.PrivateKey,
|
|
||||||
u.PublicKey,
|
|
||||||
u.PasskeyId,
|
|
||||||
)
|
|
||||||
if data.Birthday != nil {
|
|
||||||
query = query.Select(u.Birthday)
|
|
||||||
}
|
|
||||||
if data.Location != nil {
|
|
||||||
query = query.Select(u.Location)
|
|
||||||
}
|
|
||||||
user := models.User{
|
|
||||||
Username: data.Username,
|
|
||||||
DisplayName: data.Displayname,
|
|
||||||
Description: data.Description,
|
|
||||||
IsBot: data.IsBot,
|
|
||||||
ServerId: 1, // Hardcoded, Self is always first ID
|
|
||||||
PublicKey: publicKey,
|
|
||||||
PrivateKey: privateKey,
|
|
||||||
PasskeyId: pkeyId,
|
|
||||||
}
|
|
||||||
if data.Birthday != nil {
|
|
||||||
user.Birthday = sql.NullTime{Valid: true, Time: *data.Birthday}
|
|
||||||
}
|
|
||||||
if data.Location != nil {
|
|
||||||
user.Location = sql.NullString{Valid: true, String: *data.Location}
|
|
||||||
}
|
|
||||||
if err = u.Create(&user); err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed to create new local user")
|
|
||||||
httputils.HttpErr(w, 0, "db failure", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteUser(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := r.FormValue("id")
|
|
||||||
dbgen.User.Where(dbgen.User.ID.Eq(id)).Delete()
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
package webshared
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/shared"
|
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Web/json representation of a user
|
|
||||||
type User struct {
|
|
||||||
// ---- Section public data ----
|
|
||||||
// All data here will always be included, even if empty,
|
|
||||||
// in which case it will be marked as null instead of omitted
|
|
||||||
|
|
||||||
ID string `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
ServerId uint `json:"server_id"`
|
|
||||||
Displayname string `json:"displayname"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
IsBot bool `json:"is_bot"`
|
|
||||||
IconId *string `json:"icon_id"`
|
|
||||||
BackgroundId *string `json:"background_id"`
|
|
||||||
BannerId *string `json:"banner_id"`
|
|
||||||
Indexable bool `json:"indexable"`
|
|
||||||
PublicKey []byte `json:"public_key"`
|
|
||||||
RestrictedFollow bool `json:"restricted_follow"`
|
|
||||||
Location *string `json:"location"`
|
|
||||||
Birthday *time.Time `json:"birthday"`
|
|
||||||
|
|
||||||
// ---- Section Debug data ----
|
|
||||||
// All these entries should only be available
|
|
||||||
// for the debug server and should be cleared
|
|
||||||
// before serving on the public server.
|
|
||||||
// Every debug field must be omitempty
|
|
||||||
Verified *bool `json:"verified,omitempty"`
|
|
||||||
FinishedRegistration *bool `json:"finished_registration,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compiler assertations for interface implementations
|
|
||||||
var _ shared.Santisable = &User{}
|
|
||||||
var _ shared.Clonable = &User{}
|
|
||||||
|
|
||||||
func (u *User) Sanitize() {
|
|
||||||
u.Verified = nil
|
|
||||||
u.FinishedRegistration = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) Clone() shared.Clonable {
|
|
||||||
user := *u
|
|
||||||
if u.IconId != nil {
|
|
||||||
tmp := *u.IconId
|
|
||||||
user.IconId = &tmp
|
|
||||||
}
|
|
||||||
if u.BackgroundId != nil {
|
|
||||||
tmp := *u.BackgroundId
|
|
||||||
user.BackgroundId = &tmp
|
|
||||||
}
|
|
||||||
if u.BannerId != nil {
|
|
||||||
tmp := *u.BannerId
|
|
||||||
user.BannerId = &tmp
|
|
||||||
}
|
|
||||||
if u.Location != nil {
|
|
||||||
tmp := *u.Location
|
|
||||||
user.Location = &tmp
|
|
||||||
}
|
|
||||||
if u.Birthday != nil {
|
|
||||||
tmp := *u.Birthday
|
|
||||||
user.Birthday = &tmp
|
|
||||||
}
|
|
||||||
if u.Verified != nil {
|
|
||||||
tmp := *u.Verified
|
|
||||||
user.Verified = &tmp
|
|
||||||
}
|
|
||||||
if u.FinishedRegistration != nil {
|
|
||||||
tmp := *u.FinishedRegistration
|
|
||||||
user.FinishedRegistration = &tmp
|
|
||||||
}
|
|
||||||
user.PublicKey = slices.Clone(u.PublicKey)
|
|
||||||
return &user
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) FromModel(m *models.User) {
|
|
||||||
u.ID = m.ID
|
|
||||||
u.CreatedAt = m.CreatedAt
|
|
||||||
u.ServerId = m.ServerId
|
|
||||||
u.Displayname = m.DisplayName
|
|
||||||
u.IsBot = m.IsBot
|
|
||||||
if m.IconId.Valid {
|
|
||||||
u.IconId = &m.IconId.String
|
|
||||||
}
|
|
||||||
if m.BackgroundId.Valid {
|
|
||||||
u.BackgroundId = &m.IconId.String
|
|
||||||
}
|
|
||||||
if m.BannerId.Valid {
|
|
||||||
u.BannerId = &m.IconId.String
|
|
||||||
}
|
|
||||||
u.Indexable = m.Indexable
|
|
||||||
u.PublicKey = append(u.PublicKey, m.PublicKey...)
|
|
||||||
u.RestrictedFollow = m.RestrictedFollow
|
|
||||||
if m.Location.Valid {
|
|
||||||
u.Location = &m.Location.String
|
|
||||||
}
|
|
||||||
if m.Birthday.Valid {
|
|
||||||
u.Birthday = &m.Birthday.Time
|
|
||||||
}
|
|
||||||
u.Verified = &m.Verified
|
|
||||||
u.FinishedRegistration = &m.FinishedRegistration
|
|
||||||
}
|
|
|
@ -13,9 +13,6 @@ type Server struct {
|
||||||
|
|
||||||
func New() *Server {
|
func New() *Server {
|
||||||
handler := http.NewServeMux()
|
handler := http.NewServeMux()
|
||||||
handler.HandleFunc("GET /non-deleted", getNonDeletedUsers)
|
|
||||||
handler.HandleFunc("POST /local-user", createLocalUser)
|
|
||||||
handler.HandleFunc("GET /delete", deleteUser)
|
|
||||||
web := http.Server{
|
web := http.Server{
|
||||||
Addr: DebugAddr,
|
Addr: DebugAddr,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
|
@ -29,7 +26,6 @@ func (s *Server) Start() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
func (s *Server) Stop() error {
|
||||||
return s.server.Shutdown(context.Background())
|
return s.server.Shutdown(context.Background())
|
||||||
}
|
}
|
Loading…
Reference in a new issue