From 6cc699cbbd8c0765bb66ca756e54f34f21da06d3 Mon Sep 17 00:00:00 2001 From: mstar Date: Thu, 22 May 2025 16:56:56 +0200 Subject: [PATCH] Clean up unused code and better shutdown in main --- auth/auth.go | 17 - auth/checks.go | 116 ----- main.go | 103 ++--- plugins/loader.go | 1 - plugins/plugins.go | 5 - queues/inboundEventQueue/queue.go | 4 - queues/outgoingEventQueue/queue.go | 5 - server/apiLinstrom.go | 186 -------- server/apiLinstromAccounts.go | 375 ---------------- server/apiLinstromMedia.go | 8 - server/apiLinstromNotes.go | 158 ------- server/apiLinstromStreams.go | 24 - server/apiLinstromTypeHelpers.go | 184 -------- server/apiLinstromTypeHelpers_generated.go | 8 - server/apiLinstromTypes.go | 118 ----- server/apiLinstromTypes_generated.go | 59 --- server/apiRouter.go | 288 ------------ server/constants.go | 23 - server/endpoints_ap.go | 38 -- server/frontendRouter.go | 22 - server/healthAndMetrics.go | 77 ---- server/middlewareFixPasskeyPerms.go | 239 ---------- server/middlewares.go | 237 ---------- server/remoteServer/remoteServer.go | 13 - server/routerLinstromFe.go | 1 - server/server.go | 98 ----- server/testingEndpoints.go | 16 - server/utils.go | 55 --- shared/flags.go | 1 - storage/accessTokens.go | 44 -- storage/accountRelations.go | 25 -- storage/cache.go | 121 ------ storage/cache/cache.go | 105 ----- storage/cache/coderPools.go | 164 ------- storage/cache/lockedCoders.go | 35 -- storage/emote.go | 25 -- storage/errors.go | 15 - storage/gormLogger.go | 64 --- storage/housekeeping.go | 11 - storage/inboundJobs.go | 68 --- storage/inboundjobsource_string.go | 26 -- storage/mediaFile.go | 95 ---- storage/mediaProvider/preprocessor.go | 91 ---- storage/mediaProvider/provider.go | 59 --- storage/noteTargets.go | 41 -- storage/noteaccesslevel_string.go | 37 -- storage/notes.go | 164 ------- storage/outboundJobs.go | 65 --- storage/passkeySessions.go | 60 --- storage/reactions.go | 10 - storage/remoteServerInfo.go | 113 ----- storage/remoteUser.go | 7 - storage/roles.go | 253 ----------- storage/rolesDefaults.go | 244 ----------- storage/rolesUtil_generated.go | 336 -------------- storage/serverTypes.go | 39 -- storage/storage.go | 164 ------- storage/storage.go.old | 60 --- storage/user.go | 483 --------------------- storage/userIdentType.go | 21 - storage/userInfoFields.go | 71 --- 61 files changed, 36 insertions(+), 5559 deletions(-) delete mode 100644 auth/auth.go delete mode 100644 auth/checks.go delete mode 100644 plugins/loader.go delete mode 100644 plugins/plugins.go delete mode 100644 queues/inboundEventQueue/queue.go delete mode 100644 queues/outgoingEventQueue/queue.go delete mode 100644 server/apiLinstrom.go delete mode 100644 server/apiLinstromAccounts.go delete mode 100644 server/apiLinstromMedia.go delete mode 100644 server/apiLinstromNotes.go delete mode 100644 server/apiLinstromStreams.go delete mode 100644 server/apiLinstromTypeHelpers.go delete mode 100644 server/apiLinstromTypeHelpers_generated.go delete mode 100644 server/apiLinstromTypes.go delete mode 100644 server/apiLinstromTypes_generated.go delete mode 100644 server/apiRouter.go delete mode 100644 server/constants.go delete mode 100644 server/endpoints_ap.go delete mode 100644 server/frontendRouter.go delete mode 100644 server/healthAndMetrics.go delete mode 100644 server/middlewareFixPasskeyPerms.go delete mode 100644 server/middlewares.go delete mode 100644 server/remoteServer/remoteServer.go delete mode 100644 server/routerLinstromFe.go delete mode 100644 server/server.go delete mode 100644 server/testingEndpoints.go delete mode 100644 server/utils.go delete mode 100644 storage/accessTokens.go delete mode 100644 storage/accountRelations.go delete mode 100644 storage/cache.go delete mode 100644 storage/cache/cache.go delete mode 100644 storage/cache/coderPools.go delete mode 100644 storage/cache/lockedCoders.go delete mode 100644 storage/emote.go delete mode 100644 storage/errors.go delete mode 100644 storage/gormLogger.go delete mode 100644 storage/housekeeping.go delete mode 100644 storage/inboundJobs.go delete mode 100644 storage/inboundjobsource_string.go delete mode 100644 storage/mediaFile.go delete mode 100644 storage/mediaProvider/preprocessor.go delete mode 100644 storage/mediaProvider/provider.go delete mode 100644 storage/noteTargets.go delete mode 100644 storage/noteaccesslevel_string.go delete mode 100644 storage/notes.go delete mode 100644 storage/outboundJobs.go delete mode 100644 storage/passkeySessions.go delete mode 100644 storage/reactions.go delete mode 100644 storage/remoteServerInfo.go delete mode 100644 storage/remoteUser.go delete mode 100644 storage/roles.go delete mode 100644 storage/rolesDefaults.go delete mode 100644 storage/rolesUtil_generated.go delete mode 100644 storage/serverTypes.go delete mode 100644 storage/storage.go delete mode 100644 storage/storage.go.old delete mode 100644 storage/user.go delete mode 100644 storage/userIdentType.go delete mode 100644 storage/userInfoFields.go diff --git a/auth/auth.go b/auth/auth.go deleted file mode 100644 index ffe194b..0000000 --- a/auth/auth.go +++ /dev/null @@ -1,17 +0,0 @@ -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} -} diff --git a/auth/checks.go b/auth/checks.go deleted file mode 100644 index 319ad08..0000000 --- a/auth/checks.go +++ /dev/null @@ -1,116 +0,0 @@ -package auth - -import ( - "git.mstar.dev/mstar/goutils/sliceutils" - "github.com/rs/zerolog/log" - - "git.mstar.dev/mstar/linstrom/storage" -) - -// 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...) - return !sliceutils.Contains(combined.BlockedUsers, *actorId) -} - -// 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 { - // WARN: 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 } diff --git a/main.go b/main.go index 570a500..ae05a79 100644 --- a/main.go +++ b/main.go @@ -5,24 +5,22 @@ import ( "embed" "flag" "fmt" + "net/http" + "os" + "os/signal" "path" - "time" + "sync" "git.mstar.dev/mstar/goutils/other" - "github.com/go-webauthn/webauthn/webauthn" - "github.com/mstarongithub/passkey" "github.com/rs/zerolog/log" "gopkg.in/natefinch/lumberjack.v2" "gorm.io/driver/postgres" "gorm.io/gorm" "git.mstar.dev/mstar/linstrom/config" - "git.mstar.dev/mstar/linstrom/server" "git.mstar.dev/mstar/linstrom/shared" - "git.mstar.dev/mstar/linstrom/storage" storagenew "git.mstar.dev/mstar/linstrom/storage-new" "git.mstar.dev/mstar/linstrom/storage-new/dbgen" - "git.mstar.dev/mstar/linstrom/storage/cache" webdebug "git.mstar.dev/mstar/linstrom/web/debug" webpublic "git.mstar.dev/mstar/linstrom/web/public" ) @@ -43,6 +41,8 @@ var defaultDuck string var duckFS embed.FS func main() { + _ = reactiveFS + _ = nojsFS other.SetupFlags() flag.Parse() logfile := getLogFilePathOrNil() @@ -71,13 +71,7 @@ func main() { if *shared.FlagConfigOnly { return } - if *shared.FlagStartNew { - log.Info().Msg("Starting new system") - newServer() - } else { - log.Info().Msg("Starting old system") - oldServer() - } + newServer() } func getLogFilePathOrNil() *string { @@ -90,57 +84,8 @@ func getLogFilePathOrNil() *string { } } -func oldServer() { - storageCache, err := cache.NewCache( - config.GlobalConfig.Storage.MaxInMemoryCacheSize, - config.GlobalConfig.Storage.RedisUrl, - ) - if err != nil { - log.Fatal().Err(err).Msg("Failed to start cache") - } - - // var store *storage.Storage - // if config.GlobalConfig.Storage.DbIsPostgres != nil && *config.GlobalConfig.Storage.DbIsPostgres { - // store, err = storage.NewStoragePostgres(config.GlobalConfig.Storage.DatabaseUrl, storageCache) - // } else { - // store, err = storage.NewStorageSqlite(config.GlobalConfig.Storage.DatabaseUrl, storageCache) - // } - // - store, err := storage.NewStorage(config.GlobalConfig.Storage.BuildPostgresDSN(), storageCache) - - if err != nil { - log.Fatal().Err(err).Msg("Failed to setup storage") - } - - pkey, err := passkey.New(passkey.Config{ - WebauthnConfig: &webauthn.Config{ - RPDisplayName: "Linstrom", - RPID: "localhost", - RPOrigins: []string{"http://localhost:8000"}, - }, - UserStore: store, - SessionStore: store, - SessionMaxAge: time.Hour * 24, - }, passkey.WithLogger(&shared.ZerologWrapper{})) - if err != nil { - log.Fatal().Err(err).Msg("Failed to setup passkey support") - } - - server := server.NewServer( - store, - pkey, - shared.NewFSWrapper(reactiveFS, "frontend-reactive/dist/", false), - shared.NewFSWrapper(nojsFS, "frontend-noscript/", false), - &defaultDuck, - ) - server.Start(":8000") - // TODO: Set up media server - // TODO: Set up queues - // TODO: Set up plugins -} - func newServer() { - log.Info().Msg("Connectin to db") + log.Info().Msg("Connecting to db") db, err := gorm.Open( postgres.Open(config.GlobalConfig.Storage.BuildPostgresDSN()), &gorm.Config{ @@ -163,21 +108,45 @@ func newServer() { if err = storagenew.InsertUnknownActorPlaceholder(); err != nil { log.Fatal().Err(err).Msg("Failed to insert self properly") } + debugShutdownChan := make(chan *sync.WaitGroup, 1) + interuptChan := make(chan os.Signal, 1) + signal.Notify(interuptChan, os.Interrupt) if *shared.FlagStartDebugServer { go func() { log.Info().Msg("Starting debug server") - if err = webdebug.New(*shared.FlagDebugPort).Start(); err != nil { + s := webdebug.New(*shared.FlagDebugPort) + go func() { + wg := <-debugShutdownChan + if err := s.Stop(); err != nil { + log.Fatal().Err(err).Msg("Failed to cleanly stop debug server") + } + log.Info().Msg("Debug server stopped") + wg.Done() + }() + if err = s.Start(); err != nil && err != http.ErrServerClosed { log.Fatal().Err(err).Msg("Debug server failed") } }() } - log.Info().Msg("Starting public server") public := webpublic.New( fmt.Sprintf(":%v", config.GlobalConfig.General.PrivatePort), &defaultDuck, duckFS, ) - if err = public.Start(); err != nil { - log.Fatal().Err(err).Msg("Failed to start public server") + go func() { + log.Info().Msg("Starting public server") + if err = public.Start(); err != nil && err != http.ErrServerClosed { + log.Fatal().Err(err).Msg("Failed to start public server") + } + }() + <-interuptChan + log.Info().Msg("Received interrupt, shutting down") + wg := sync.WaitGroup{} + wg.Add(1) + debugShutdownChan <- &wg + if err = public.Stop(); err != nil { + log.Fatal().Err(err).Msg("Failed to stop public server") } + log.Info().Msg("Public server stopped") + wg.Wait() } diff --git a/plugins/loader.go b/plugins/loader.go deleted file mode 100644 index d5c343e..0000000 --- a/plugins/loader.go +++ /dev/null @@ -1 +0,0 @@ -package plugins diff --git a/plugins/plugins.go b/plugins/plugins.go deleted file mode 100644 index f665a6e..0000000 --- a/plugins/plugins.go +++ /dev/null @@ -1,5 +0,0 @@ -package plugins - -// TODO: Think about how to enable server side processing plugins - -// Options: Lua, wasm/wasi (probably prefered) diff --git a/queues/inboundEventQueue/queue.go b/queues/inboundEventQueue/queue.go deleted file mode 100644 index ce3467a..0000000 --- a/queues/inboundEventQueue/queue.go +++ /dev/null @@ -1,4 +0,0 @@ -// Job queue for all inbound events -// Well, I say queue but it's more of a security measure adding the inbound job to the db as backup -// in case processing fails for any reason -package inboundeventqueue diff --git a/queues/outgoingEventQueue/queue.go b/queues/outgoingEventQueue/queue.go deleted file mode 100644 index 87cdcce..0000000 --- a/queues/outgoingEventQueue/queue.go +++ /dev/null @@ -1,5 +0,0 @@ -// Queue for outbound events -// Actual queue here since other servers can't be expected to go at the same speed as Linstrom (be it slower or faster) -// This queue should enforce data consistency (new jobs are first stored in the db) and a fair processing speed for -// other servers, depending on their processing speed -package outgoingeventqueue diff --git a/server/apiLinstrom.go b/server/apiLinstrom.go deleted file mode 100644 index de943e6..0000000 --- a/server/apiLinstrom.go +++ /dev/null @@ -1,186 +0,0 @@ -package server - -import ( - "net/http" - - "git.mstar.dev/mstar/goutils/other" - "git.mstar.dev/mstar/linstrom/storage" -) - -func setupLinstromApiRouter() http.Handler { - router := http.NewServeMux() - router.Handle("/v1/", http.StripPrefix("/v1", setupLinstromApiV1Router())) - router.Handle("/s2s/v1/", http.StripPrefix("/s2s/v1", setupLinstromS2SApiV1Router())) - - return router -} - -func setupLinstromApiV1Router() http.Handler { - router := http.NewServeMux() - - // Notes - - // Get a note - router.HandleFunc("GET /notes/{noteId}", linstromGetNote) - // Send a new note - router.HandleFunc("POST /notes", linstromNewNote) - // Update a note - router.HandleFunc("PATCH /notes/{noteId}", linstromUpdateNote) - // Delete a note - router.HandleFunc("DELETE /notes/{noteId}", linstromDeleteNote) - - // Reactions - - // Get all reactions for a note - router.HandleFunc("GET /notes/{noteId}/reactions", linstromGetReactions) - // Send a new reaction to a note - router.HandleFunc("POST /notes/{noteId}/reactions", linstromAddReaction) - // Update own reaction on a note - router.HandleFunc("PATCH /notes/{noteId}/reactions", linstromUpdateReaction) - // Remove own reaction on a note - router.HandleFunc("DELETE /notes/{noteId}/reactions", linstromDeleteReaction) - - // Boosts - - // Get all boosters of a note - router.HandleFunc("GET /notes/{noteId}/boosts", linstromGetBoosts) - // Boost a note - router.HandleFunc("POST /notes/{noteId}/boosts", linstromAddBoost) - // Unboost a note - router.HandleFunc("DELETE /notes/{noteId}/boosts", linstromRemoveBoost) - - // Quotes - - // Get all quotes of a note - router.HandleFunc("GET /notes/{noteId}/quotes", linstromGetQuotes) - // Create a new quote message of a given note - router.HandleFunc("POST /notes/{noteId}/quotes", linstromAddQuote) - - // Pinning - - // Pin a note to account profile - router.HandleFunc("POST /notes/{noteId}/pin", linstromPinNote) - // Unpin a note from account profile - router.HandleFunc("DELETE /notes/{noteId}/pin", linstromUnpinNote) - // Reports - router.HandleFunc("POST /notes/{noteId}/report", linstromReportNote) - router.HandleFunc("DELETE /notes/{noteId}/report", linstromRetractReportNote) - // Admin - router.HandleFunc("POST /notes/{noteId}/admin/cw", linstromForceCWNote) - - // Accounts - // Creating a new account happens either during fetch of a remote one or during registration with a passkey - - // Get an account - router.HandleFunc("GET /accounts/{accountId}", linstromGetAccount) - // Update own account - // Technically also requires authenticated account to also be owner or correct admin perms, - // but that's annoying to handle in a general sense. So leaving that to the function - // though figuring out a nice generic-ish way to handle those checks would be nice too - router.HandleFunc( - "PATCH /accounts/{accountId}", - requireValidSessionMiddleware(linstromUpdateAccount), - ) - // Delete own account - // Technically also requires authenticated account to also be owner or correct admin perms, - // but that's annoying to handle in a general sense. So leaving that to the function - router.HandleFunc( - "DELETE /accounts/{accountId}", - requireValidSessionMiddleware(linstromDeleteAccount), - ) - // Follow - // Is logged in following accountId - router.HandleFunc( - "GET /accounts/{accountId}/follow/to", - requireValidSessionMiddleware(linstromIsFollowingToAccount), - ) - // Is accountId following logged in - router.HandleFunc( - "GET /accounts/{accountId}/follow/from", - requireValidSessionMiddleware(linstromIsFollowingFromAccount), - ) - // Send follow request to accountId - router.HandleFunc("POST /accounts/{accountId}/follow", linstromFollowAccount) - // Undo follow request to accountId - router.HandleFunc("DELETE /accounts/{accountId}/follow", linstromUnfollowAccount) - - // Block - - // Is logged in account blocking target account - router.HandleFunc("GET /accounts/{accountId}/block", linstromIsBlockingAccount) - // Block target account - router.HandleFunc("POST /accounts/{accountId}/block", linstromBlockAccount) - // Unblock target account - router.HandleFunc("DELETE /accounts/{accountId}/block", linstromUnblockAccount) - - // Mute - - // Has logged in account muted target account? - router.HandleFunc("GET /accounts/{accountId}/mute", linstromIsMutedAccount) - // Mute target account - router.HandleFunc("POST /accounts/{accountId}/mute", linstromMuteAccount) - // Unmute target account - router.HandleFunc("DELETE /accounts/{accountId}/mute", linstromUnmuteAccount) - - // Report - - // Report a target account - router.HandleFunc("POST /accounts/{accountId}/reports", linstromReportAccount) - // Undo report on target account - router.HandleFunc("DELETE /accounts/{accountId}/reports", linstromRetractReportAccount) - - // Admin - - // Add new role to account - router.Handle( - "POST /accounts/{accountId}/admin/roles", - buildRequirePermissionsMiddleware( - &storage.Role{CanAssignRoles: other.IntoPointer(true)}, - )( - http.HandlerFunc(linstromAdminAddRoleAccount), - ), - ) - // Remove role from account - router.Handle( - "DELETE /accounts/{accountId}/admin/roles/{roleName}", - buildRequirePermissionsMiddleware(&storage.Role{CanAssignRoles: other.IntoPointer(true)})( - http.HandlerFunc(linstromAdminRemoveRoleAccount), - ), - ) - // Send a warning to account - router.HandleFunc("POST /accounts/{accountId}/admin/warn", linstromAdminWarnAccount) - - // Roles - - // Get a role - router.HandleFunc("GET /roles/{roleId}", linstromGetRole) - // Create a new role - router.HandleFunc("POST /roles", linstromCreateRole) - // Update a role. Builtin roles cannot be edited - router.HandleFunc("PATCH /roles/{roleId}", linstromUpdateRole) - // Delete a role. Builtin roles cannot be deleted - router.HandleFunc("DELETE /roles/{roleId}", linstromDeleteRole) - - // Media metadata - - // Get the metadata for some media - router.HandleFunc("GET /media/{mediaId}", linstromGetMediaMetadata) - // Upload new media - router.HandleFunc("POST /media", linstromNewMediaMetadata) - // Update the metadata for some media - router.HandleFunc("PATCH /media/{mediaId}", linstromUpdateMediaMetadata) - // Delete a media entry - router.HandleFunc("DELETE /media/{mediaId}", linstromDeleteMediaMetadata) - - // Event streams - router.HandleFunc("/streams", linstromEventStream) - - return router -} - -func setupLinstromS2SApiV1Router() http.Handler { - router := http.NewServeMux() - // TODO: Figure out a decent server to server API definition - router.HandleFunc("/", placeholderEndpoint) - return router -} diff --git a/server/apiLinstromAccounts.go b/server/apiLinstromAccounts.go deleted file mode 100644 index 6b7fa48..0000000 --- a/server/apiLinstromAccounts.go +++ /dev/null @@ -1,375 +0,0 @@ -package server - -import ( - "net/http" - - httputil "git.mstar.dev/mstar/goutils/http" - "git.mstar.dev/mstar/goutils/sliceutils" - "github.com/google/jsonapi" - "github.com/rs/zerolog/hlog" - - "git.mstar.dev/mstar/linstrom/storage" -) - -// No create account. That happens during passkey registration -// and remote accounts are getting created at fetch time -func linstromGetAccount(w http.ResponseWriter, r *http.Request) { - store := StorageFromRequest(r) - log := hlog.FromRequest(r) - - accId := AccountIdFromRequest(r) - acc, err := store.FindAccountById(accId) - switch err { - case nil: - // Ok, do nothing - case storage.ErrEntryNotFound: - httputil.HttpErr(w, HttpErrIdNotFound, "account not found", http.StatusNotFound) - return - default: - log.Error().Err(err).Str("account-id", accId).Msg("Failed to get account from storage") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get account from storage", - http.StatusInternalServerError, - ) - return - } - actorId, ok := r.Context().Value(ContextKeyActorId).(string) - if ok { - // Logged in user is accessing account, check if target account has them blocked - roles, err := store.FindRolesByNames(acc.Roles) - if err != nil { - log.Error(). - Err(err). - Strs("role-names", acc.Roles). - Msg("Failed to get roles from storage") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get roles of target account", - http.StatusInternalServerError, - ) - return - } - collapsedRole := storage.CollapseRolesIntoOne(roles...) - if sliceutils.Contains(collapsedRole.BlockedUsers, actorId) { - // Actor account is in list of blocked accounts, deny access - httputil.HttpErr(w, HttpErrIdNotAuthenticated, "Access forbidden", http.StatusForbidden) - return - } - } - - outAccount, err := convertAccountStorageToLinstrom(acc, store) - if err != nil { - log.Error(). - Err(err). - Msg("Failed to convert storage account (and attached data) into linstrom API representation") - httputil.HttpErr( - w, - HttpErrIdConversionFailure, - "Failed to convert storage account and attached data into API representation", - http.StatusInternalServerError, - ) - return - } - err = jsonapi.MarshalPayload(w, outAccount) - if err != nil { - log.Error().Err(err).Any("account", outAccount).Msg("Failed to marshal and write account") - } -} - -func linstromUpdateAccount(w http.ResponseWriter, r *http.Request) { - store := StorageFromRequest(r) - log := hlog.FromRequest(r) - // Assumption: There must be a valid session once this function is called due to middlewares - actorId, _ := ActorIdFromRequest(r) - apiTarget := linstromAccount{} - err := jsonapi.UnmarshalPayload(r.Body, &apiTarget) - if err != nil { - httputil.HttpErr(w, HttpErrIdBadRequest, "bad body", http.StatusBadRequest) - return - } - targetAccId := AccountIdFromRequest(r) - if apiTarget.Id != targetAccId { - httputil.HttpErr( - w, - HttpErrIdBadRequest, - "Provided entity's id doesn't match path id", - http.StatusConflict, - ) - return - } - if !(actorId == apiTarget.Id) { - httputil.HttpErr(w, HttpErrIdNotAuthenticated, "Invalid permissions", http.StatusForbidden) - return - } - dbTarget, err := store.FindAccountById(apiTarget.Id) - // Assumption: The only sort of errors that can be returned are db failures. - // The account not existing is not possible anymore since this is in a valid session - // and a session is only injected if the actor account can be found - if err != nil { - log.Error(). - Err(err). - Str("account-id", actorId). - Msg("Failed to get account from db despite valid session") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get account despite valid session", - http.StatusInternalServerError, - ) - return - } - - // location, birthday, icon, banner, background, custom fields - // bluesky federation, uhhh - - dbTarget.DisplayName = apiTarget.DisplayName - dbTarget.Indexable = apiTarget.Indexable - dbTarget.Description = apiTarget.Description - // TODO: Figure out how to properly update custom fields - dbTarget.Gender = apiTarget.Pronouns - dbTarget.IdentifiesAs = sliceutils.Map( - sliceutils.Filter(apiTarget.IdentifiesAs, func(t string) bool { - return storage.IsValidBeing(t) - }), - func(t string) storage.Being { return storage.Being(t) }, - ) - dbTarget.Indexable = apiTarget.Indexable - dbTarget.RestrictedFollow = apiTarget.RestrictedFollow - err = store.UpdateAccount(dbTarget) - if err != nil { - log.Error().Err(err).Msg("Failed to update account in db") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to update db entries", - http.StatusInternalServerError, - ) - return - } - w.WriteHeader(http.StatusOK) - newAccData, err := convertAccountStorageToLinstrom(dbTarget, store) - if err != nil { - log.Error().Err(err).Msg("Failed to convert updated account back into api form") - httputil.HttpErr( - w, - HttpErrIdConversionFailure, - "Failed to convert updated account back into api form", - http.StatusInternalServerError, - ) - return - } - err = jsonapi.MarshalPayload(w, newAccData) - if err != nil { - log.Error().Err(err).Msg("Failed to marshal and write updated account") - } -} - -func linstromDeleteAccount(w http.ResponseWriter, r *http.Request) { - actorId, _ := ActorIdFromRequest(r) - log := hlog.FromRequest(r) - store := StorageFromRequest(r) - targetAccountId := AccountIdFromRequest(r) - if targetAccountId != actorId { - log.Debug(). - Str("actor-id", actorId). - Str("target-id", targetAccountId). - Msg("Invalid attempt to delete account") - httputil.HttpErr(w, HttpErrIdNotAuthenticated, "Action forbidden", http.StatusForbidden) - return - } - log.Info().Str("account-id", actorId).Msg("Deleting account") - acc, err := store.FindAccountById(targetAccountId) - if err != nil { - log.Error().Err(err).Str("account-id", actorId).Msg("Failed to get account for deletion") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get account from db", - http.StatusInternalServerError, - ) - return - } - // allRoles, err := store.FindRolesByNames(acc.Roles) - // collapsedRole := storage.CollapseRolesIntoOne(allRoles...) - - // TODO: Start job of sending out deletion messages to all federated servers - - // Clean up related data first - // TODO: Also delete media files - err = store.DeleteRoleByName(acc.ID) - if err != nil { - log.Error(). - Err(err). - Str("role-name", acc.ID). - Msg("Failed to delete user role for account deletion request") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to delete user role", - http.StatusInternalServerError, - ) - return - } - err = store.DeleteAllUserFieldsForAccountId(acc.ID) - if err != nil { - log.Error(). - Err(err). - Str("account-id", acc.ID). - Msg("Failed to delete custom info fields for account deletion") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to delete custom info fields", - http.StatusInternalServerError, - ) - return - } - err = store.DeleteAccount(actorId) - if err != nil { - log.Error().Err(err).Str("account-id", acc.ID).Msg("Failed to delete account") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to delete account from db", - http.StatusInternalServerError, - ) - return - } -} - -// Is logged in following accountId -func linstromIsFollowingToAccount(w http.ResponseWriter, r *http.Request) { - store := StorageFromRequest(r) - log := hlog.FromRequest(r) - actorId, _ := ActorIdFromRequest(r) - targetId := AccountIdFromRequest(r) - - relation, err := store.GetRelationBetween(actorId, targetId) - var outData linstromRelation - switch err { - case nil: - outData = linstromRelation{ - Id: relation.ID, - CreatedAt: relation.CreatedAt, - UpdatedAt: relation.UpdatedAt, - FromId: relation.FromId, - ToId: relation.ToId, - Accepted: relation.Accepted, - Requested: true, - } - case storage.ErrEntryNotFound: - outData = linstromRelation{ - Id: relation.ID, - CreatedAt: relation.CreatedAt, - UpdatedAt: relation.UpdatedAt, - FromId: relation.FromId, - ToId: relation.ToId, - Accepted: false, - Requested: false, - } - default: - log.Error(). - Err(err). - Str("from-id", actorId). - Str("to-id", targetId). - Msg("Failed to get follow relation") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get relation", - http.StatusInternalServerError, - ) - } - - err = jsonapi.MarshalPayload(w, outData) - if err != nil { - log.Warn().Err(err).Msg("Failed to marshal response") - httputil.HttpErr( - w, - HttpErrIdJsonMarshalFail, - "Failed to marshal response", - http.StatusInternalServerError, - ) - } -} - -func linstromIsFollowingFromAccount(w http.ResponseWriter, r *http.Request) { - store := StorageFromRequest(r) - log := hlog.FromRequest(r) - actorId, _ := ActorIdFromRequest(r) - targetId := AccountIdFromRequest(r) - - relation, err := store.GetRelationBetween(targetId, actorId) - var outData linstromRelation - switch err { - case nil: - outData = linstromRelation{ - Id: relation.ID, - CreatedAt: relation.CreatedAt, - UpdatedAt: relation.UpdatedAt, - FromId: relation.FromId, - ToId: relation.ToId, - Accepted: relation.Accepted, - Requested: true, - } - case storage.ErrEntryNotFound: - outData = linstromRelation{ - Id: relation.ID, - CreatedAt: relation.CreatedAt, - UpdatedAt: relation.UpdatedAt, - FromId: relation.FromId, - ToId: relation.ToId, - Accepted: false, - Requested: false, - } - default: - log.Error(). - Err(err). - Str("from-id", targetId). - Str("to-id", actorId). - Msg("Failed to get follow relation") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get relation", - http.StatusInternalServerError, - ) - } - - err = jsonapi.MarshalPayload(w, outData) - if err != nil { - log.Warn().Err(err).Msg("Failed to marshal response") - httputil.HttpErr( - w, - HttpErrIdJsonMarshalFail, - "Failed to marshal response", - http.StatusInternalServerError, - ) - } -} - -func linstromFollowAccount(w http.ResponseWriter, r *http.Request) {} -func linstromUnfollowAccount(w http.ResponseWriter, r *http.Request) {} - -func linstromIsBlockingAccount(w http.ResponseWriter, r *http.Request) {} -func linstromBlockAccount(w http.ResponseWriter, r *http.Request) {} -func linstromUnblockAccount(w http.ResponseWriter, r *http.Request) {} - -func linstromIsMutedAccount(w http.ResponseWriter, r *http.Request) {} -func linstromMuteAccount(w http.ResponseWriter, r *http.Request) {} -func linstromUnmuteAccount(w http.ResponseWriter, r *http.Request) {} - -func linstromReportAccount(w http.ResponseWriter, r *http.Request) {} -func linstromRetractReportAccount(w http.ResponseWriter, r *http.Request) {} - -func linstromAdminAddRoleAccount(w http.ResponseWriter, r *http.Request) {} -func linstromAdminRemoveRoleAccount(w http.ResponseWriter, r *http.Request) {} -func linstromAdminWarnAccount(w http.ResponseWriter, r *http.Request) {} - -func linstromGetRole(w http.ResponseWriter, r *http.Request) {} -func linstromCreateRole(w http.ResponseWriter, r *http.Request) {} -func linstromUpdateRole(w http.ResponseWriter, r *http.Request) {} -func linstromDeleteRole(w http.ResponseWriter, r *http.Request) {} diff --git a/server/apiLinstromMedia.go b/server/apiLinstromMedia.go deleted file mode 100644 index 6bfaaf5..0000000 --- a/server/apiLinstromMedia.go +++ /dev/null @@ -1,8 +0,0 @@ -package server - -import "net/http" - -func linstromGetMediaMetadata(w http.ResponseWriter, r *http.Request) {} -func linstromNewMediaMetadata(w http.ResponseWriter, r *http.Request) {} -func linstromUpdateMediaMetadata(w http.ResponseWriter, r *http.Request) {} -func linstromDeleteMediaMetadata(w http.ResponseWriter, r *http.Request) {} diff --git a/server/apiLinstromNotes.go b/server/apiLinstromNotes.go deleted file mode 100644 index d478647..0000000 --- a/server/apiLinstromNotes.go +++ /dev/null @@ -1,158 +0,0 @@ -package server - -import ( - "net/http" - "time" - - httputil "git.mstar.dev/mstar/goutils/http" - "github.com/google/jsonapi" - "github.com/rs/zerolog/hlog" - - "git.mstar.dev/mstar/linstrom/shared" - "git.mstar.dev/mstar/linstrom/storage" -) - -// Notes -func linstromGetNote(w http.ResponseWriter, r *http.Request) { - store := StorageFromRequest(r) - noteId := NoteIdFromRequest(r) - log := hlog.FromRequest(r) - sNote, err := store.FindNoteById(noteId) - switch err { - case nil: - // Found, progress past switch statement - case storage.ErrEntryNotFound: - httputil.HttpErr(w, HttpErrIdNotFound, "Note not found", http.StatusNotFound) - return - default: - log.Error().Err(err).Str("note-id", noteId).Msg("Failed to get note from db") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get note from db", - http.StatusInternalServerError, - ) - return - } - note, err := convertNoteStorageToLinstrom(sNote, store) - if err != nil { - log.Error(). - Err(err). - Str("note-id", noteId). - Msg("Failed to convert note into linstrom api form") - httputil.HttpErr( - w, - HttpErrIdConversionFailure, - "Failed to convert note", - http.StatusInternalServerError, - ) - return - } - err = jsonapi.MarshalPayload(w, note) - if err != nil { - log.Error().Err(err).Any("note", note).Msg("Failed to marshal and send note") - httputil.HttpErr( - w, - HttpErrIdJsonMarshalFail, - "Failed to convert note", - http.StatusInternalServerError, - ) - } -} - -func linstromUpdateNote(w http.ResponseWriter, r *http.Request) {} -func linstromNewNote(w http.ResponseWriter, r *http.Request) { - store := StorageFromRequest(r) - actorId, ok := ActorIdFromRequest(r) - log := hlog.FromRequest(r) - - if !ok { - httputil.HttpErr( - w, - HttpErrIdNotAuthenticated, - "Needs a valid session to create new notes", - http.StatusUnauthorized, - ) - return - } - - newNote := linstromNote{} - err := jsonapi.UnmarshalPayload(r.Body, &newNote) - if err != nil { - log.Warn().Err(err).Msg("Failed to unmarshal body") - httputil.HttpErr(w, HttpErrIdBadRequest, "bad body", http.StatusBadRequest) - return - } - - if newNote.AuthorId != actorId { - log.Debug(). - Str("actor-id", actorId). - Str("target-id", newNote.AuthorId). - Msg("Blocking attempt at creating a note for a different account") - httputil.HttpErr( - w, - HttpErrIdNotAllowed, - "creating a note for someone else is not allowed", - http.StatusForbidden, - ) - return - } - - rawTags := shared.TagsFromText(newNote.RawContent) - - note, err := store.CreateNoteLocal( - actorId, - newNote.RawContent, - newNote.ContentWarning, - time.Now(), - newNote.AttachmentIds, - newNote.EmoteIds, - newNote.InReplyToId, - newNote.QuotesId, - storage.NoteAccessLevel(newNote.AccessLevel), - rawTags, - // tags []string - ) - if err != nil { - log.Error().Err(err).Any("note", newNote).Msg("Failed to insert new note into storage") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to insert new note into db", - http.StatusInternalServerError, - ) - return - } - _ = note -} -func linstromDeleteNote(w http.ResponseWriter, r *http.Request) {} - -// Reactions -func linstromGetReactions(w http.ResponseWriter, r *http.Request) {} -func linstromAddReaction(w http.ResponseWriter, r *http.Request) {} -func linstromDeleteReaction(w http.ResponseWriter, r *http.Request) {} -func linstromUpdateReaction(w http.ResponseWriter, r *http.Request) {} - -// Boosts -func linstromGetBoosts(w http.ResponseWriter, r *http.Request) {} -func linstromAddBoost(w http.ResponseWriter, r *http.Request) {} -func linstromRemoveBoost(w http.ResponseWriter, r *http.Request) {} - -// Quotes -func linstromGetQuotes(w http.ResponseWriter, r *http.Request) {} -func linstromAddQuote(w http.ResponseWriter, r *http.Request) {} - -// No delete quote since quotes are their own notes with an extra attribute - -// Pinning -func linstromPinNote(w http.ResponseWriter, r *http.Request) {} -func linstromUnpinNote(w http.ResponseWriter, r *http.Request) {} - -// Reporting -func linstromReportNote(w http.ResponseWriter, r *http.Request) {} -func linstromRetractReportNote(w http.ResponseWriter, r *http.Request) {} - -// Admin tools -// TODO: Figure out more admin tools for managing notes -// Delete can be done via normal note delete, common permission check -func linstromForceCWNote(w http.ResponseWriter, r *http.Request) {} diff --git a/server/apiLinstromStreams.go b/server/apiLinstromStreams.go deleted file mode 100644 index b5dc306..0000000 --- a/server/apiLinstromStreams.go +++ /dev/null @@ -1,24 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog/hlog" -) - -// TODO: Decide where to put data stream handlers - -var websocketUpgrader = websocket.Upgrader{} - -// Entrypoint for a new stream will be in here at least -func linstromEventStream(w http.ResponseWriter, r *http.Request) { - log := hlog.FromRequest(r) - conn, err := websocketUpgrader.Upgrade(w, r, nil) - if err != nil { - log.Warn().Err(err).Msg("Failed to upgrade connection to websocket") - } - defer conn.Close() - // TODO: Handle initial request for what events to receive - // TODO: Stream all requested events until connection closes (due to bad data from client or disconnect) -} diff --git a/server/apiLinstromTypeHelpers.go b/server/apiLinstromTypeHelpers.go deleted file mode 100644 index 92c4fd4..0000000 --- a/server/apiLinstromTypeHelpers.go +++ /dev/null @@ -1,184 +0,0 @@ -package server - -import ( - "fmt" - - "git.mstar.dev/mstar/goutils/sliceutils" - "git.mstar.dev/mstar/linstrom/storage" -) - -func convertAccountStorageToLinstrom( - acc *storage.Account, - store *storage.Storage, -) (*linstromAccount, error) { - storageServer, err := store.FindRemoteServerById(acc.ServerId) - if err != nil { - return nil, fmt.Errorf("remote server: %w", err) - } - apiServer, err := convertServerStorageToLinstrom(storageServer, store) - if err != nil { - return nil, fmt.Errorf("remote server conversion: %w", err) - } - storageIcon, err := store.GetMediaMetadataById(acc.Icon) - if err != nil { - return nil, fmt.Errorf("icon: %w", err) - } - var apiBanner *linstromMediaMetadata - if acc.Banner != nil { - storageBanner, err := store.GetMediaMetadataById(*acc.Banner) - if err != nil { - return nil, fmt.Errorf("banner: %w", err) - } - apiBanner = convertMediaMetadataStorageToLinstrom(storageBanner) - } - - var apiBackground *linstromMediaMetadata - if acc.Background != nil { - storageBackground, err := store.GetMediaMetadataById(*acc.Background) - if err != nil { - return nil, fmt.Errorf("banner: %w", err) - } - apiBackground = convertMediaMetadataStorageToLinstrom(storageBackground) - } - storageFields, err := store.FindMultipleUserFieldsById(acc.CustomFields) - if err != nil { - return nil, fmt.Errorf("customFields: %w", err) - } - - return &linstromAccount{ - Id: acc.ID, - CreatedAt: acc.CreatedAt, - UpdatedAt: &acc.UpdatedAt, - Username: acc.Username, - OriginServer: apiServer, - OriginServerId: int(acc.ServerId), - DisplayName: acc.DisplayName, - CustomFields: sliceutils.Map( - storageFields, - func(t storage.UserInfoField) *linstromCustomAccountField { - return convertInfoFieldStorageToLinstrom(t) - }, - ), - CustomFieldIds: acc.CustomFields, - IsBot: acc.IsBot, - Description: acc.Description, - Icon: convertMediaMetadataStorageToLinstrom(storageIcon), - IconId: acc.Icon, - Banner: apiBanner, - BannerId: acc.Banner, - Background: apiBackground, - BackgroundId: acc.Background, - RelationIds: acc.Relations, - Indexable: acc.Indexable, - RestrictedFollow: acc.RestrictedFollow, - IdentifiesAs: sliceutils.Map( - acc.IdentifiesAs, - func(t storage.Being) string { return string(t) }, - ), - Pronouns: acc.Gender, - Roles: acc.Roles, - }, nil -} - -func convertServerStorageToLinstrom( - server *storage.RemoteServer, - store *storage.Storage, -) (*linstromOriginServer, error) { - storageMeta, err := store.GetMediaMetadataById(server.Icon) - if err != nil { - return nil, fmt.Errorf("icon metadata: %w", err) - } - return &linstromOriginServer{ - Id: server.ID, - CreatedAt: server.CreatedAt, - UpdatedAt: &server.UpdatedAt, - ServerType: string(server.ServerType), - Domain: server.Domain, - DisplayName: server.Name, - Icon: convertMediaMetadataStorageToLinstrom(storageMeta), - IsSelf: server.IsSelf, - }, nil -} - -func convertMediaMetadataStorageToLinstrom(metadata *storage.MediaMetadata) *linstromMediaMetadata { - return &linstromMediaMetadata{ - Id: metadata.ID, - CreatedAt: metadata.CreatedAt, - UpdatedAt: &metadata.UpdatedAt, - IsRemote: metadata.Remote, - Url: metadata.Location, - MimeType: metadata.Type, - Name: metadata.Name, - AltText: metadata.AltText, - Blurred: metadata.Blurred, - } -} - -func convertInfoFieldStorageToLinstrom(field storage.UserInfoField) *linstromCustomAccountField { - return &linstromCustomAccountField{ - Id: field.ID, - CreatedAt: field.CreatedAt, - UpdatedAt: &field.UpdatedAt, - Key: field.Name, - Value: field.Value, - Verified: &field.Confirmed, - BelongsToId: field.BelongsTo, - } -} - -func convertNoteStorageToLinstrom( - note *storage.Note, - store *storage.Storage, -) (*linstromNote, error) { - panic("Not implemented") -} - -func convertEmoteStorageToLinstrom( - emote *storage.Emote, - store *storage.Storage, -) (*linstromEmote, error) { - storageServer, err := store.FindRemoteServerById(emote.ServerId) - if err != nil { - return nil, fmt.Errorf("server: %w", err) - } - server, err := convertServerStorageToLinstrom(storageServer, store) - if err != nil { - return nil, fmt.Errorf("server conversion: %w", err) - } - storageMedia, err := store.GetMediaMetadataById(emote.MetadataId) - if err != nil { - return nil, fmt.Errorf("media metadata: %w", err) - } - media := convertMediaMetadataStorageToLinstrom(storageMedia) - - return &linstromEmote{ - Id: emote.ID, - MetadataId: emote.MetadataId, - Metadata: media, - Name: emote.Name, - ServerId: emote.ServerId, - Server: server, - }, nil -} - -func convertReactionStorageToLinstrom( - reaction *storage.Reaction, - store *storage.Storage, -) (*linstromReaction, error) { - storageEmote, err := store.GetEmoteById(reaction.EmoteId) - if err != nil { - return nil, fmt.Errorf("emote: %w", err) - } - emote, err := convertEmoteStorageToLinstrom(storageEmote, store) - if err != nil { - return nil, fmt.Errorf("emote conversion: %w", err) - } - - return &linstromReaction{ - Id: reaction.ID, - NoteId: reaction.NoteId, - ReactorId: reaction.ReactorId, - EmoteId: reaction.EmoteId, - Emote: emote, - }, nil -} diff --git a/server/apiLinstromTypeHelpers_generated.go b/server/apiLinstromTypeHelpers_generated.go deleted file mode 100644 index 7b0b643..0000000 --- a/server/apiLinstromTypeHelpers_generated.go +++ /dev/null @@ -1,8 +0,0 @@ -// Code generated by cmd/RolesApiConverter DO NOT EDIT. -// If you need to refresh the content, run go generate again -package server - -import "git.mstar.dev/mstar/linstrom/storage" -func convertRoleStorageToLinstrom(r storage.Role) linstromRole { -return linstromRole{Id:r.ID,CreatedAt:r.CreatedAt,UpdatedAt:r.UpdatedAt,Name:r.Name,Priority:r.Priority,IsUserRole:r.IsUserRole,IsBuiltIn:r.IsBuiltIn,CanRecoverDeletedNotes:r.CanRecoverDeletedNotes,CanSendFollowerOnlyNotes:r.CanSendFollowerOnlyNotes,CanSendReplies:r.CanSendReplies,AutoNsfwMedia:r.AutoNsfwMedia,WithholdNotesForManualApproval:r.WithholdNotesForManualApproval,CanAffectOtherAdmins:r.CanAffectOtherAdmins,CanAssignRoles:r.CanAssignRoles,CanOverwriteDisplayNames:r.CanOverwriteDisplayNames,CanManageAvatarDecorations:r.CanManageAvatarDecorations,CanSendMedia:r.CanSendMedia,ScanCreatedLocalNotes:r.ScanCreatedLocalNotes,CanSendCustomEmotes:r.CanSendCustomEmotes,CanSendPublicNotes:r.CanSendPublicNotes,CanIncludeSurvey:r.CanIncludeSurvey,AutoCwPostsText:r.AutoCwPostsText,CanManageCustomEmotes:r.CanManageCustomEmotes,CanSendAnnouncements:r.CanSendAnnouncements,CanSendLocalNotes:r.CanSendLocalNotes,CanBoost:r.CanBoost,CanLogin:r.CanLogin,CanMentionOthers:r.CanMentionOthers,CanManageAds:r.CanManageAds,CanQuote:r.CanQuote,CanChangeDisplayName:r.CanChangeDisplayName,CanSubmitReports:r.CanSubmitReports,FullAdmin:r.FullAdmin,CanSendPrivateNotes:r.CanSendPrivateNotes,CanIncludeLinks:r.CanIncludeLinks,CanFederateFedi:r.CanFederateFedi,HasMentionCountLimit:r.HasMentionCountLimit,AutoCwPosts:r.AutoCwPosts,ScanCreatedPublicNotes:r.ScanCreatedPublicNotes,DisallowInteractionsWith:r.DisallowInteractionsWith,CanDeleteNotes:r.CanDeleteNotes,CanConfirmWithheldNotes:r.CanConfirmWithheldNotes,ScanCreatedPrivateNotes:r.ScanCreatedPrivateNotes,WithholdNotesBasedOnRegex:r.WithholdNotesBasedOnRegex,WithholdNotesRegexes:r.WithholdNotesRegexes,CanSupressInteractionsBetweenUsers:r.CanSupressInteractionsBetweenUsers,CanViewDeletedNotes:r.CanViewDeletedNotes,CanSendCustomReactions:r.CanSendCustomReactions,CanFederateBsky:r.CanFederateBsky,BlockedUsers:r.BlockedUsers,MentionLimit:r.MentionLimit,ScanCreatedFollowerOnlyNotes:r.ScanCreatedFollowerOnlyNotes,} -} diff --git a/server/apiLinstromTypes.go b/server/apiLinstromTypes.go deleted file mode 100644 index bdebe34..0000000 --- a/server/apiLinstromTypes.go +++ /dev/null @@ -1,118 +0,0 @@ -package server - -// Contains types used by the Linstrom API. Types comply with the jsonapi spec - -import "time" - -var ( - _ = linstromRole{} - _ = linstromRelation{} -) - -type linstromNote struct { - Id string `jsonapi:"primary,notes"` - RawContent string `jsonapi:"attr,content"` - OriginServer *linstromOriginServer `jsonapi:"relation,origin-server"` - OriginServerId int `jsonapi:"attr,origin-server-id"` - ReactionCount string `jsonapi:"attr,reaction-count"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt *time.Time `jsonapi:"attr,updated-at,omitempty"` - Author *linstromAccount `jsonapi:"relation,author"` - AuthorId string `jsonapi:"attr,author-id"` - ContentWarning *string `jsonapi:"attr,content-warning,omitempty"` - InReplyToId *string `jsonapi:"attr,in-reply-to-id,omitempty"` - QuotesId *string `jsonapi:"attr,quotes-id,omitempty"` - EmoteIds []string `jsonapi:"attr,emotes,omitempty"` - Attachments []*linstromMediaMetadata `jsonapi:"relation,attachments,omitempty"` - AttachmentIds []string `jsonapi:"attr,attachment-ids"` - AccessLevel uint8 `jsonapi:"attr,access-level"` - Pings []*linstromAccount `jsonapi:"relation,pings,omitempty"` - PingIds []string `jsonapi:"attr,ping-ids,omitempty"` - ReactionIds []uint `jsonapi:"attr,reaction-ids"` -} - -type linstromOriginServer struct { - Id uint `jsonapi:"primary,origins"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt *time.Time `jsonapi:"attr,updated-at,omitempty"` - ServerType string `jsonapi:"attr,server-type"` // one of "Linstrom", "Mastodon", "Plemora", "Misskey" or "Wafrn" - Domain string `jsonapi:"attr,domain"` - DisplayName string `jsonapi:"attr,display-name"` - Icon *linstromMediaMetadata `jsonapi:"relation,icon"` - IsSelf bool `jsonapi:"attr,is-self"` -} - -type linstromMediaMetadata struct { - Id string `jsonapi:"primary,media"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt *time.Time `jsonapi:"attr,updated-at,omitempty"` - IsRemote bool `jsonapi:"attr,is-remote"` - Url string `jsonapi:"attr,url"` - MimeType string `jsonapi:"attr,mime-type"` - Name string `jsonapi:"attr,name"` - AltText string `jsonapi:"attr,alt-text"` - Blurred bool `jsonapi:"attr,blurred"` -} - -type linstromAccount struct { - Id string `jsonapi:"primary,accounts"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt *time.Time `jsonapi:"attr,updated-at,omitempty"` - Username string `jsonapi:"attr,username"` - OriginServer *linstromOriginServer `jsonapi:"relation,origin-server"` - OriginServerId int `jsonapi:"attr,origin-server-id"` - DisplayName string `jsonapi:"attr,display-name"` - CustomFields []*linstromCustomAccountField `jsonapi:"relation,custom-fields"` - CustomFieldIds []uint `jsonapi:"attr,custom-field-ids"` - IsBot bool `jsonapi:"attr,is-bot"` - Description string `jsonapi:"attr,description"` - Icon *linstromMediaMetadata `jsonapi:"relation,icon"` - IconId string `jsonapi:"attr,icon-id"` - Banner *linstromMediaMetadata `jsonapi:"relation,banner"` - BannerId *string `jsonapi:"attr,banner-id"` - Background *linstromMediaMetadata `jsonapi:"relation,background"` - BackgroundId *string `jsonapi:"attr,background-id"` - RelationIds []uint `jsonapi:"attr,follows-ids"` - Indexable bool `jsonapi:"attr,indexable"` - RestrictedFollow bool `jsonapi:"attr,restricted-follow"` - IdentifiesAs []string `jsonapi:"attr,identifies-as"` - Pronouns []string `jsonapi:"attr,pronouns"` - Roles []string `jsonapi:"attr,roles"` -} - -type linstromCustomAccountField struct { - Id uint `jsonapi:"primary,custom-account-fields"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt *time.Time `jsonapi:"attr,updated-at,omitempty"` - Key string `jsonapi:"attr,key"` - Value string `jsonapi:"attr,value"` - Verified *bool `jsonapi:"attr,verified,omitempty"` - BelongsToId string `jsonapi:"attr,belongs-to-id"` -} - -type linstromRelation struct { - Id uint `jsonapi:"primary,relations"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt time.Time `jsonapi:"attr,updated-at"` - FromId string `jsonapi:"attr,from-id"` - ToId string `jsonapi:"attr,to-id"` - Requested bool `jsonapi:"attr,requested"` - Accepted bool `jsonapi:"attr,accepted"` -} - -type linstromReaction struct { - Id uint `jsonapi:"primary,reactions"` - NoteId string `jsonapi:"attr,note-id"` - ReactorId string `jsonapi:"attr,reactor-id"` - EmoteId uint `jsonapi:"attr,emote-id"` - Emote *linstromEmote `jsonapi:"relation,emote"` -} - -type linstromEmote struct { - Id uint `jsonapi:"primary,emotes"` - MetadataId string `jsonapi:"attr,metadata-id"` - Metadata *linstromMediaMetadata `jsonapi:"relation,metadata"` - Name string `jsonapi:"attr,name"` - ServerId uint `jsonapi:"attr,server-id"` - Server *linstromOriginServer `jsonapi:"relation,server"` -} diff --git a/server/apiLinstromTypes_generated.go b/server/apiLinstromTypes_generated.go deleted file mode 100644 index 53e6034..0000000 --- a/server/apiLinstromTypes_generated.go +++ /dev/null @@ -1,59 +0,0 @@ -// Code generated by cmd/RolesApiTypeGenerator DO NOT EDIT. -// If you need to refresh the content, run go generate again -package server - -import "time" -type linstromRole struct { - Id uint `jsonapi:"primary,roles"` - CreatedAt time.Time `jsonapi:"attr,created-at"` - UpdatedAt time.Time `jsonapi:"attr,updated-at"` - Name string `jsonapi:"attr,name"` - Priority uint32 `jsonapi:"attr,priority"` - IsUserRole bool `jsonapi:"attr,is-user-role"` - IsBuiltIn bool `jsonapi:"attr,is-built-in"` - CanIncludeLinks *bool `jsonapi:"attr,can-include-links"` - CanIncludeSurvey *bool `jsonapi:"attr,can-include-survey"` - CanLogin *bool `jsonapi:"attr,can-login"` - CanSupressInteractionsBetweenUsers *bool `jsonapi:"attr,can-supress-interactions-between-users"` - CanManageCustomEmotes *bool `jsonapi:"attr,can-manage-custom-emotes"` - CanSendAnnouncements *bool `jsonapi:"attr,can-send-announcements"` - CanSendReplies *bool `jsonapi:"attr,can-send-replies"` - CanRecoverDeletedNotes *bool `jsonapi:"attr,can-recover-deleted-notes"` - CanMentionOthers *bool `jsonapi:"attr,can-mention-others"` - CanSendFollowerOnlyNotes *bool `jsonapi:"attr,can-send-follower-only-notes"` - CanSendPrivateNotes *bool `jsonapi:"attr,can-send-private-notes"` - HasMentionCountLimit *bool `jsonapi:"attr,has-mention-count-limit"` - MentionLimit *uint32 `jsonapi:"attr,mention-limit"` - ScanCreatedFollowerOnlyNotes *bool `jsonapi:"attr,scan-created-follower-only-notes"` - FullAdmin *bool `jsonapi:"attr,full-admin"` - CanSendPublicNotes *bool `jsonapi:"attr,can-send-public-notes"` - CanFederateBsky *bool `jsonapi:"attr,can-federate-bsky"` - CanSubmitReports *bool `jsonapi:"attr,can-submit-reports"` - AutoNsfwMedia *bool `jsonapi:"attr,auto-nsfw-media"` - AutoCwPostsText *string `jsonapi:"attr,auto-cw-posts-text"` - WithholdNotesForManualApproval *bool `jsonapi:"attr,withhold-notes-for-manual-approval"` - CanManageAds *bool `jsonapi:"attr,can-manage-ads"` - CanFederateFedi *bool `jsonapi:"attr,can-federate-fedi"` - CanChangeDisplayName *bool `jsonapi:"attr,can-change-display-name"` - ScanCreatedPublicNotes *bool `jsonapi:"attr,scan-created-public-notes"` - ScanCreatedPrivateNotes *bool `jsonapi:"attr,scan-created-private-notes"` - CanAssignRoles *bool `jsonapi:"attr,can-assign-roles"` - CanSendCustomReactions *bool `jsonapi:"attr,can-send-custom-reactions"` - BlockedUsers []string `jsonapi:"attr,blocked-users"` - DisallowInteractionsWith []string `jsonapi:"attr,disallow-interactions-with"` - CanDeleteNotes *bool `jsonapi:"attr,can-delete-notes"` - CanViewDeletedNotes *bool `jsonapi:"attr,can-view-deleted-notes"` - CanManageAvatarDecorations *bool `jsonapi:"attr,can-manage-avatar-decorations"` - CanSendMedia *bool `jsonapi:"attr,can-send-media"` - CanSendLocalNotes *bool `jsonapi:"attr,can-send-local-notes"` - CanBoost *bool `jsonapi:"attr,can-boost"` - AutoCwPosts *bool `jsonapi:"attr,auto-cw-posts"` - ScanCreatedLocalNotes *bool `jsonapi:"attr,scan-created-local-notes"` - WithholdNotesRegexes []string `jsonapi:"attr,withhold-notes-regexes"` - CanSendCustomEmotes *bool `jsonapi:"attr,can-send-custom-emotes"` - WithholdNotesBasedOnRegex *bool `jsonapi:"attr,withhold-notes-based-on-regex"` - CanAffectOtherAdmins *bool `jsonapi:"attr,can-affect-other-admins"` - CanConfirmWithheldNotes *bool `jsonapi:"attr,can-confirm-withheld-notes"` - CanOverwriteDisplayNames *bool `jsonapi:"attr,can-overwrite-display-names"` - CanQuote *bool `jsonapi:"attr,can-quote"` -} \ No newline at end of file diff --git a/server/apiRouter.go b/server/apiRouter.go deleted file mode 100644 index 75591f0..0000000 --- a/server/apiRouter.go +++ /dev/null @@ -1,288 +0,0 @@ -package server - -import "net/http" - -// Mounted at /api -func setupApiRouter() http.Handler { - router := http.NewServeMux() - router.Handle("/linstrom/", http.StripPrefix("/linstrom", setupLinstromApiRouter())) - - // Section MastoApi - // First segment are endpoints that will need to be moved to primary router since at top route - router.HandleFunc("GET /oauth/authorize", placeholderEndpoint) - router.HandleFunc("POST /oauth/token", placeholderEndpoint) - router.HandleFunc("POST /oauth/revoke", placeholderEndpoint) - router.HandleFunc("GET /.well-known/oauth-authorization-server", placeholderEndpoint) - // These ones are actually mounted under /api/ - router.HandleFunc("POST /v1/apps", placeholderEndpoint) - router.HandleFunc("GET /v1/apps/verify_credentials", placeholderEndpoint) - router.HandleFunc("POST /v1/emails/confirmations", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/verify_credentials", placeholderEndpoint) - router.HandleFunc("PATCH /v1/accounts/update_credentials", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/{id}/statuses", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/{id}/followers", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/{id}/following", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/{id}/featured_tags", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/{id}/lists", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/follow", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/unfollow", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/remove_from_followers", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/block", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/unblock", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/mute", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/unmute", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/pin", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/unpin", placeholderEndpoint) - router.HandleFunc("POST /v1/accounts/{id}/note", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/relationships", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/familiar_followers", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/search", placeholderEndpoint) - router.HandleFunc("GET /v1/accounts/lookup", placeholderEndpoint) - router.HandleFunc("GET /v1/bookmarks", placeholderEndpoint) - router.HandleFunc("GET /v1/favourites", placeholderEndpoint) - router.HandleFunc("GET /v1/mutes", placeholderEndpoint) - router.HandleFunc("GET /v1/blocks", placeholderEndpoint) - router.HandleFunc("GET /v1/domain_blocks", placeholderEndpoint) - router.HandleFunc("POST /v1/domain_blocks", placeholderEndpoint) - router.HandleFunc("DELETE /v1/domain_blocks", placeholderEndpoint) - router.HandleFunc("GET /v2/filters", placeholderEndpoint) - router.HandleFunc("GET /v2/filters/{id}", placeholderEndpoint) - router.HandleFunc("POST /v2/filters", placeholderEndpoint) - router.HandleFunc("PUT /v2/filters/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v2/filters/{id}", placeholderEndpoint) - router.HandleFunc("GET /v2/filters/:filter_id/keywords", placeholderEndpoint) - router.HandleFunc("POST /v2/filters/:filter_id/keywords", placeholderEndpoint) - router.HandleFunc("GET /v2/filters/keywords/{id}", placeholderEndpoint) - router.HandleFunc("PUT /v2/filters/keywords/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v2/filters/keywords/{id}", placeholderEndpoint) - router.HandleFunc("GET /v2/filters/:filter_id/statuses", placeholderEndpoint) - router.HandleFunc("POST /v2/filters/:filter_id/statuses", placeholderEndpoint) - router.HandleFunc("GET /v2/filters/statuses/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v2/filters/statuses/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/reports", placeholderEndpoint) - router.HandleFunc("GET /v1/follow_requests", placeholderEndpoint) - router.HandleFunc("POST /v1/follow_requests/:account_id/authorize", placeholderEndpoint) - router.HandleFunc("POST /v1/follow_requests/:account_id/reject", placeholderEndpoint) - router.HandleFunc("GET /v1/endorsements", placeholderEndpoint) - router.HandleFunc("GET /v1/featured_tags", placeholderEndpoint) - router.HandleFunc("POST /v1/featured_tags", placeholderEndpoint) - router.HandleFunc("DELETE /v1/featured_tags/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/featured_tags/suggestions", placeholderEndpoint) - router.HandleFunc("GET /v1/preferences", placeholderEndpoint) - router.HandleFunc("GET /v1/followed_tags", placeholderEndpoint) - router.HandleFunc("GET /v2/suggestions", placeholderEndpoint) - router.HandleFunc("DELETE /v1/suggestions/:account_id", placeholderEndpoint) - router.HandleFunc("GET /v1/tags/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/tags/{id}/follow", placeholderEndpoint) - router.HandleFunc("POST /v1/tags/{id}/unfollow", placeholderEndpoint) - router.HandleFunc("DELETE /v1/profile/avatar", placeholderEndpoint) - router.HandleFunc("DELETE /v1/profile/header", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses", placeholderEndpoint) - router.HandleFunc("DELETE /v1/statuses/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses/{id}/context", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/translate", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses/{id}/reblogged_by", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses/{id}/favourited_by", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/favourite", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/unfavourite", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/reblog", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/unreblog", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/bookmark", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/unbookmark", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/mute", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/unmute", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/pin", placeholderEndpoint) - router.HandleFunc("POST /v1/statuses/{id}/unpin", placeholderEndpoint) - router.HandleFunc("PUT /v1/statuses/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses/{id}/history", placeholderEndpoint) - router.HandleFunc("GET /v1/statuses/{id}/source", placeholderEndpoint) - router.HandleFunc("POST /v2/media", placeholderEndpoint) - router.HandleFunc("GET /v1/media/{id}", placeholderEndpoint) - router.HandleFunc("PUT /v1/media/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/polls/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/polls/{id}/votes", placeholderEndpoint) - router.HandleFunc("GET /v1/scheduled_statuses", placeholderEndpoint) - router.HandleFunc("GET /v1/scheduled_statuses/{id}", placeholderEndpoint) - router.HandleFunc("PUT /v1/scheduled_statuses/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v1/scheduled_statuses/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/timelines/public", placeholderEndpoint) - router.HandleFunc("GET /v1/timelines/tag/:hashtag", placeholderEndpoint) - router.HandleFunc("GET /v1/timelines/home", placeholderEndpoint) - router.HandleFunc("GET /v1/timelines/link", placeholderEndpoint) // ?url=:url - router.HandleFunc("GET /v1/timelines/list/:list_id", placeholderEndpoint) - router.HandleFunc("GET /v1/conversations", placeholderEndpoint) - router.HandleFunc("DELETE /v1/conversations/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/conversations/{id}/read", placeholderEndpoint) - router.HandleFunc("GET /v1/lists", placeholderEndpoint) - router.HandleFunc("GET /v1/lists/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/lists", placeholderEndpoint) - router.HandleFunc("PUT /v1/lists/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v1/lists/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/lists/{id}/accounts", placeholderEndpoint) - router.HandleFunc("POST /v1/lists/{id}/accounts", placeholderEndpoint) - router.HandleFunc("DELETE /v1/lists/{id}/accounts", placeholderEndpoint) - router.HandleFunc("GET /v1/markers", placeholderEndpoint) - router.HandleFunc("POST /v1/markers", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/health", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/user", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/user/notification", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/public", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/public/local", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/public/remote", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/hashtag", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/hashtag/local", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/list", placeholderEndpoint) - router.HandleFunc("GET /v1/streaming/direct", placeholderEndpoint) - router.HandleFunc("GET /v2/notifications", placeholderEndpoint) - router.HandleFunc("GET /v2/notifications/:group_key", placeholderEndpoint) - router.HandleFunc("POST /v2/notifications/:group_key/dismiss", placeholderEndpoint) - router.HandleFunc("GET /v2/notifications/:group_key/accounts", placeholderEndpoint) - router.HandleFunc("GET /v2/notifications/unread_count", placeholderEndpoint) - router.HandleFunc("GET /v1/notifications", placeholderEndpoint) - router.HandleFunc("GET /v1/notifications/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/notifications/clear", placeholderEndpoint) - router.HandleFunc("POST /v1/notifications/{id}/dismiss", placeholderEndpoint) - router.HandleFunc("GET /v1/notifications/unread_count", placeholderEndpoint) - router.HandleFunc("GET /v2/notifications/policy", placeholderEndpoint) - router.HandleFunc("PATCH /v2/notifications/policy", placeholderEndpoint) - router.HandleFunc("GET /v1/notifications/requests", placeholderEndpoint) - router.HandleFunc("GET /v1/notifications/requests/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/notifications/requests/{id}/accept", placeholderEndpoint) - router.HandleFunc("POST /v1/notifications/requests/{id}/dismiss", placeholderEndpoint) - router.HandleFunc("POST /v1/notifications/requests/accept", placeholderEndpoint) - router.HandleFunc("POST /v1/notifications/requests/dismiss", placeholderEndpoint) - router.HandleFunc("GET /v1/notifications/requests/merged", placeholderEndpoint) - router.HandleFunc("POST /v1/push/subscription", placeholderEndpoint) - router.HandleFunc("GET /v1/push/subscription", placeholderEndpoint) - router.HandleFunc("PUT /v1/push/subscription", placeholderEndpoint) - router.HandleFunc("DELETE /v1/push/subscription", placeholderEndpoint) - router.HandleFunc("GET /v2/search", placeholderEndpoint) - router.HandleFunc("GET /v2/instance", placeholderEndpoint) - router.HandleFunc("GET /v1/instance/peers", placeholderEndpoint) - router.HandleFunc("GET /v1/instance/activity", placeholderEndpoint) - router.HandleFunc("GET /v1/instance/rules", placeholderEndpoint) - router.HandleFunc("GET /v1/instance/domain_blocks", placeholderEndpoint) - router.HandleFunc("GET /v1/instance/extended_description", placeholderEndpoint) - router.HandleFunc("GET /v1/instance/translation_languages", placeholderEndpoint) - router.HandleFunc("GET /v1/trends/tags", placeholderEndpoint) - router.HandleFunc("GET /v1/trends/statuses", placeholderEndpoint) - router.HandleFunc("GET /v1/trends/links", placeholderEndpoint) - router.HandleFunc("GET /v1/directory", placeholderEndpoint) - router.HandleFunc("GET /v1/custom_emojis", placeholderEndpoint) - router.HandleFunc("GET /v1/announcements", placeholderEndpoint) - router.HandleFunc("POST /v1/announcements/{id}/dismiss", placeholderEndpoint) - router.HandleFunc("PUT /v1/announcements/{id}/reactions/:name", placeholderEndpoint) - router.HandleFunc("DELETE /v1/announcements/{id}/reactions/:name", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/accounts", placeholderEndpoint) - router.HandleFunc("GET /v2/admin/accounts", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/accounts/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/approve", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/reject", placeholderEndpoint) - router.HandleFunc("DELETE /v1/admin/accounts/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/action", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/enable", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/unsilence", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/unsuspend", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/accounts/{id}/unsensitive", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/canonical_email_blocks", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/canonical_email_blocks/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/canonical_email_blocks/test", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/canonical_email_blocks", placeholderEndpoint) - router.HandleFunc("DELETE /v1/admin/canonical_email_blocks/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/dimensions", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/domain_allows", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/domain_allows/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/domain_allows", placeholderEndpoint) - router.HandleFunc("DELETE /v1/admin/domain_allows/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/domain_blocks", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/domain_blocks/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/domain_blocks", placeholderEndpoint) - router.HandleFunc("PUT /v1/admin/domain_blocks/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v1/admin/domain_blocks/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/email_domain_blocks", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/email_domain_blocks/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/email_domain_blocks", placeholderEndpoint) - router.HandleFunc("DELETE /v1/admin/email_domain_blocks/{id}", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/ip_blocks", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/ip_blocks/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/ip_blocks", placeholderEndpoint) - router.HandleFunc("PUT /v1/admin/ip_blocks/{id}", placeholderEndpoint) - router.HandleFunc("DELETE /v1/admin/ip_blocks/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/measures", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/reports", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/reports/{id}", placeholderEndpoint) - router.HandleFunc("PUT /v1/admin/reports/{id}", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/reports/{id}/assign_to_self", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/reports/{id}/unassign", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/reports/{id}/resolve", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/reports/{id}/reopen", placeholderEndpoint) - router.HandleFunc("POST /v1/admin/retention", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/trends/links", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/trends/statuses", placeholderEndpoint) - router.HandleFunc("GET /v1/admin/trends/tags", placeholderEndpoint) - router.HandleFunc("GET /oembed", placeholderEndpoint) - - router.HandleFunc( - "GET /v1/accounts/{id}/identity_proofs", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "GET /v1/filters", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "GET /v1/filters/{id}", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "POST /v1/filters", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "PUT /v1/filters/{id}", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "DELETE /v1/filters/{id}", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "GET /v1/suggestions", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "GET /v1/statuses/{id}/card", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "POST /v1/media", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "GET /v1/timelines/direct", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "POST /v1/notifications/dismiss", - placeholderEndpoint, - ) // Removed - router.HandleFunc( - "GET /v1/search", - placeholderEndpoint, - ) // Removed - router.HandleFunc( - "GET /v1/instance", - placeholderEndpoint, - ) // Deprecated - router.HandleFunc( - "GET /proofs", - placeholderEndpoint, - ) // Removed - - return router -} diff --git a/server/constants.go b/server/constants.go deleted file mode 100644 index 8783939..0000000 --- a/server/constants.go +++ /dev/null @@ -1,23 +0,0 @@ -package server - -const ContextKeyPasskeyUsername = "context-passkey-username" - -type ContextKey string - -const ( - ContextKeyStorage ContextKey = "Context key for storage" - ContextKeyActorId ContextKey = "Context key for actor id" -) - -const ( - HttpErrIdPlaceholder = iota - HttpErrIdMissingContextValue - HttpErrIdDbFailure - HttpErrIdNotAuthenticated - HttpErrIdJsonMarshalFail - HttpErrIdBadRequest - HttpErrIdAlreadyExists - HttpErrIdNotFound - HttpErrIdConversionFailure - HttpErrIdNotAllowed -) diff --git a/server/endpoints_ap.go b/server/endpoints_ap.go deleted file mode 100644 index 01cdb03..0000000 --- a/server/endpoints_ap.go +++ /dev/null @@ -1,38 +0,0 @@ -package server - -import ( - "errors" - "fmt" - "net/http" - "strings" - - "github.com/rs/zerolog" - "git.mstar.dev/mstar/linstrom/storage" - "gorm.io/gorm" -) - -// Mount under /.well-known/webfinger -func webfingerHandler(w http.ResponseWriter, r *http.Request) { - logger := zerolog.Ctx(r.Context()) - store := storage.Storage{} - - requestedResource := r.FormValue("resource") - if requestedResource == "" { - http.Error(w, "bad request. Include \"resource\" parameter", http.StatusBadRequest) - logger.Debug().Msg("Resource parameter missing. Cancelling") - return - } - accName := strings.TrimPrefix(requestedResource, "acc:") - acc, err := store.FindAccountByFullHandle(accName) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - http.Error(w, "account not found", http.StatusNotFound) - logger.Debug().Str("account-name", accName).Msg("Account not found") - } else { - http.Error(w, err.Error(), http.StatusInternalServerError) - logger.Error().Err(err).Msg("Error while searching for account") - return - } - } - fmt.Fprint(w, acc) -} diff --git a/server/frontendRouter.go b/server/frontendRouter.go deleted file mode 100644 index e7aa33c..0000000 --- a/server/frontendRouter.go +++ /dev/null @@ -1,22 +0,0 @@ -package server - -import ( - "io/fs" - "net/http" -) - -// Mounted at / -func setupFrontendRouter(interactiveFs, noscriptFs fs.FS) http.Handler { - router := http.NewServeMux() - router.Handle("/noscript/", http.StripPrefix("/noscript", http.FileServerFS(noscriptFs))) - router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.ServeFileFS(w, r, interactiveFs, "index.html") - }) - router.Handle("/assets/", http.FileServerFS(interactiveFs)) - router.HandleFunc( - "/robots.txt", - func(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, interactiveFs, "robots.txt") }, - ) - - return router -} diff --git a/server/healthAndMetrics.go b/server/healthAndMetrics.go deleted file mode 100644 index 0b01962..0000000 --- a/server/healthAndMetrics.go +++ /dev/null @@ -1,77 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/pprof" - "runtime" - "runtime/debug" - "time" - - httputil "git.mstar.dev/mstar/goutils/http" -) - -// Mounted at /profiling -func setupProfilingHandler() http.Handler { - router := http.NewServeMux() - router.HandleFunc("/", profilingRootHandler) - router.HandleFunc("GET /current-goroutines", metricActiveGoroutinesHandler) - router.HandleFunc("GET /memory", metricMemoryStatsHandler) - router.HandleFunc("GET /pprof/cpu", pprof.Profile) - router.Handle("GET /pprof/memory", pprof.Handler("heap")) - router.Handle("GET /pprof/goroutines", pprof.Handler("goroutine")) - router.Handle("GET /pprof/blockers", pprof.Handler("block")) - - return router -} - -func isAliveHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, "yup") -} - -func profilingRootHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprint( - w, - "Endpoints: /, /{memory,current-goroutines}, /pprof/{cpu,memory,goroutines,blockers}", - ) -} - -func metricActiveGoroutinesHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "{\"goroutines\": %d}", runtime.NumGoroutine()) -} - -func metricMemoryStatsHandler(w http.ResponseWriter, r *http.Request) { - type OutData struct { - CollectedAt time.Time `json:"collected_at"` - HeapUsed uint64 `json:"heap_used"` - HeapIdle uint64 `json:"heap_idle"` - StackUsed uint64 `json:"stack_used"` - GCLastFired time.Time `json:"gc_last_fired"` - GCNextTargetHeapSize uint64 `json:"gc_next_target_heap_size"` - } - stats := runtime.MemStats{} - gcStats := debug.GCStats{} - runtime.ReadMemStats(&stats) - debug.ReadGCStats(&gcStats) - outData := OutData{ - CollectedAt: time.Now(), - HeapUsed: stats.HeapInuse, - HeapIdle: stats.HeapIdle, - StackUsed: stats.StackInuse, - GCLastFired: gcStats.LastGC, - GCNextTargetHeapSize: stats.NextGC, - } - - jsonData, err := json.Marshal(&outData) - if err != nil { - httputil.HttpErr( - w, - HttpErrIdJsonMarshalFail, - "Failed to encode return data", - http.StatusInternalServerError, - ) - return - } - fmt.Fprint(w, string(jsonData)) -} diff --git a/server/middlewareFixPasskeyPerms.go b/server/middlewareFixPasskeyPerms.go deleted file mode 100644 index 50d00cc..0000000 --- a/server/middlewareFixPasskeyPerms.go +++ /dev/null @@ -1,239 +0,0 @@ -package server - -import ( - "encoding/json" - "errors" - "io" - "net/http" - "strings" - "time" - - httputil "git.mstar.dev/mstar/goutils/http" - "github.com/rs/zerolog/hlog" - - "git.mstar.dev/mstar/linstrom/storage" -) - -func forceCorrectPasskeyAuthFlowMiddleware( - handler http.Handler, -) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log := hlog.FromRequest(r) - // Don't fuck with the request if not intended for starting to register or login - if strings.HasSuffix(r.URL.Path, "loginFinish") { - log.Debug().Msg("Request to finish login method, doing nothing") - handler.ServeHTTP(w, r) - return - } else if strings.HasSuffix(r.URL.Path, "registerFinish") { - handler.ServeHTTP(w, r) - // Force unset session cookie here - w.Header().Del("Set-Cookie") - http.SetCookie(w, &http.Cookie{ - Name: "sid", - Value: "", - Path: "", - MaxAge: 0, - Expires: time.UnixMilli(0), - }) - return - } else if strings.HasSuffix(r.URL.Path, "loginBegin") { - fuckWithLoginRequest(w, r, handler) - } else if strings.HasSuffix(r.URL.Path, "registerBegin") { - fuckWithRegisterRequest(w, r, handler) - } - }) -} - -func fuckWithRegisterRequest( - w http.ResponseWriter, - r *http.Request, - nextHandler http.Handler, -) { - log := hlog.FromRequest(r) - log.Debug().Msg("Messing with register start request") - store := StorageFromRequest(r) - if store == nil { - return - } - cookie, cookieErr := r.Cookie("sid") - var username struct { - Username string `json:"username"` - } - body, _ := io.ReadAll(r.Body) - log.Debug().Bytes("body", body).Msg("Body of auth begin request") - err := json.Unmarshal(body, &username) - if err != nil { - httputil.HttpErr( - w, - HttpErrIdBadRequest, - "Not a username json object", - http.StatusBadRequest, - ) - return - } - if cookieErr == nil { - // Already authenticated, overwrite username to logged in account's name - // Get session from cookie - log.Debug().Msg("Session token exists, force overwriting username of register request") - session, ok := store.GetSession(cookie.Value) - if !ok { - log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Passkey session missing", - http.StatusInternalServerError, - ) - return - } - acc, err := store.FindAccountByPasskeyId(session.UserID) - // Assume account must exist if a session for it exists - if err != nil { - log.Error().Err(err).Msg("Failed to get account from passkey id from session") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get authenticated account", - http.StatusInternalServerError, - ) - return - } - // Replace whatever username may be given with username of logged in account - newBody := strings.ReplaceAll(string(body), username.Username, acc.Username) - // Assign to request - r.Body = io.NopCloser(strings.NewReader(newBody)) - r.ContentLength = int64(len(newBody)) - // And pass on - nextHandler.ServeHTTP(w, r) - } else { - // Not authenticated, ensure that no existing name is registered with - _, err = store.FindLocalAccountByUsername(username.Username) - log.Debug().Bool("err-equals-not_found", err == storage.ErrEntryNotFound).Send() - switch err { - case nil: - // No error while getting account means account exists, refuse access - log.Info(). - Str("username", username.Username). - Msg("Account with same name already exists, preventing login") - httputil.HttpErr( - w, - HttpErrIdAlreadyExists, - "Account with that name already exists", - http.StatusBadRequest, - ) - case storage.ErrEntryNotFound: - // Didn't find account with that name, give access - log.Debug(). - Str("username", username.Username). - Msg("No account with this username exists yet, passing through") - // Copy original body since previous reader hit EOF - r.Body = io.NopCloser(strings.NewReader(string(body))) - r.ContentLength = int64(len(body)) - nextHandler.ServeHTTP(w, r) - default: - // Some other error, log it and return appropriate message - log.Error(). - Err(err). - Str("username", username.Username). - Msg("Failed to check if account with username already exists") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to check if account with that name already exists", - http.StatusInternalServerError, - ) - } - } -} - -func fuckWithLoginRequest( - w http.ResponseWriter, - r *http.Request, - nextHandler http.Handler, -) { - log := hlog.FromRequest(r) - log.Debug().Msg("Messing with login start request") - store := StorageFromRequest(r) - if store == nil { - return - } - cookie, cookieErr := r.Cookie("sid") - var username struct { - Username string `json:"username"` - } - // Force ignore cookie for now - _ = cookieErr - var err error = errors.New("placeholder") - if err == nil { - // Someone is logged in, overwrite username with logged in account's one - body, _ := io.ReadAll(r.Body) - log.Debug().Bytes("body", body).Msg("Body of auth begin request") - err := json.Unmarshal(body, &username) - if err != nil { - httputil.HttpErr( - w, - HttpErrIdBadRequest, - "Not a username json object", - http.StatusBadRequest, - ) - return - } - session, ok := store.GetSession(cookie.Value) - if !ok { - log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Passkey session missing", - http.StatusInternalServerError, - ) - return - } - acc, err := store.FindAccountByPasskeyId(session.UserID) - // Assume account must exist if a session for it exists - if err != nil { - log.Error().Err(err).Msg("Failed to get account from passkey id from session") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get authenticated account", - http.StatusInternalServerError, - ) - return - } - // Replace whatever username may be given with username of logged in account - newBody := strings.ReplaceAll(string(body), username.Username, acc.Username) - // Assign to request - r.Body = io.NopCloser(strings.NewReader(newBody)) - r.ContentLength = int64(len(newBody)) - // And pass on - nextHandler.ServeHTTP(w, r) - } else { - // No one logged in, check if user exists to prevent creating a bugged account - body, _ := io.ReadAll(r.Body) - log.Debug().Bytes("body", body).Msg("Body of auth begin request") - err := json.Unmarshal(body, &username) - if err != nil { - httputil.HttpErr(w, HttpErrIdBadRequest, "Not a username json object", http.StatusBadRequest) - return - } - _, err = store.FindLocalAccountByUsername(username.Username) - switch err { - case nil: - // All good, account exists, keep going - // Do nothing in this branch - case storage.ErrEntryNotFound: - // Account doesn't exist, catch it - httputil.HttpErr(w, HttpErrIdNotFound, "Username not found", http.StatusNotFound) - return - default: - // catch db failures - log.Error().Err(err).Str("username", username.Username).Msg("Db failure while getting account") - httputil.HttpErr(w, HttpErrIdDbFailure, "Failed to check for account in db", http.StatusInternalServerError) - return - } - // Restore body as new reader of the same content - r.Body = io.NopCloser(strings.NewReader(string(body))) - nextHandler.ServeHTTP(w, r) - } -} diff --git a/server/middlewares.go b/server/middlewares.go deleted file mode 100644 index 17f8548..0000000 --- a/server/middlewares.go +++ /dev/null @@ -1,237 +0,0 @@ -package server - -import ( - "context" - "net/http" - "slices" - "strings" - "time" - - httputil "git.mstar.dev/mstar/goutils/http" - "github.com/rs/zerolog/hlog" - "github.com/rs/zerolog/log" - - "git.mstar.dev/mstar/linstrom/config" - "git.mstar.dev/mstar/linstrom/storage" -) - -type HandlerBuilder func(http.Handler) http.Handler - -func ChainMiddlewares(base http.Handler, links ...HandlerBuilder) http.Handler { - slices.Reverse(links) - for _, f := range links { - base = f(base) - } - return base -} - -func ContextValsMiddleware(pairs map[any]any) HandlerBuilder { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - for key, val := range pairs { - ctx = context.WithValue(ctx, key, val) - } - newRequest := r.WithContext(ctx) - h.ServeHTTP(w, newRequest) - }) - } -} - -func LoggingMiddleware(handler http.Handler) http.Handler { - return ChainMiddlewares(handler, - hlog.NewHandler(log.Logger), - hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { - if strings.HasPrefix(r.URL.Path, "/assets") { - return - } - hlog.FromRequest(r).Info(). - Str("method", r.Method). - Stringer("url", r.URL). - Int("status", status). - Int("size", size). - Dur("duration", duration). - Send() - }), - hlog.RemoteAddrHandler("ip"), - hlog.UserAgentHandler("user_agent"), - hlog.RefererHandler("referer"), - hlog.RequestIDHandler("req_id", "Request-Id"), - ) -} - -func passkeyIdToAccountIdTransformerMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - s := StorageFromRequest(r) - if s == nil { - return - } - log := hlog.FromRequest(r) - passkeyId, ok := r.Context().Value(ContextKeyPasskeyUsername).(string) - if !ok { - httputil.HttpErr( - w, - HttpErrIdMissingContextValue, - "Actor name missing", - http.StatusInternalServerError, - ) - return - } - log.Debug().Bytes("passkey-bytes", []byte(passkeyId)).Msg("Id from passkey auth") - acc, err := s.FindAccountByPasskeyId([]byte(passkeyId)) - if err != nil { - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get account from storage", - http.StatusInternalServerError, - ) - return - } - r = r.WithContext(context.WithValue(r.Context(), ContextKeyActorId, acc.ID)) - handler.ServeHTTP(w, r) - }) -} - -func profilingAuthenticationMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.FormValue("password") != config.GlobalConfig.Admin.ProfilingPassword { - httputil.HttpErr(w, HttpErrIdNotAuthenticated, "Bad password", http.StatusUnauthorized) - return - } - handler.ServeHTTP(w, r) - }) -} - -// Middleware for inserting a logged in account's id into the request context if a session exists -// Does not cancel requests ever. If an error occurs, it's treated as if no session is set -func checkSessionMiddleware(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie("sid") - log := hlog.FromRequest(r) - if err != nil { - // No cookie is ok, this function is only for inserting account id into the context - // if one exists, not for checking permissions - log.Debug().Msg("No session cookie, passing along") - handler.ServeHTTP(w, r) - return - } - store := StorageFromRequest(r) - session, ok := store.GetSession(cookie.Value) - if !ok { - // Failed to get session from cookie id. Log, then move on as if no session is set - log.Warn(). - Str("session-id", cookie.Value). - Msg("Cookie with session id found, but session doesn't exist") - handler.ServeHTTP(w, r) - return - } - if session.Expires.Before(time.Now()) { - // Session expired. Move on as if no session was set - store.DeleteSession(cookie.Value) - handler.ServeHTTP(w, r) - return - } - acc, err := store.FindAccountByPasskeyId(session.UserID) - switch err { - case storage.ErrEntryNotFound: - log.Info().Msg("No account for the given passkey id found. It probably got deleted") - handler.ServeHTTP(w, r) - return - case nil: - default: - // Failed to get account for passkey id. Log, then move on as if no session is set - log.Error(). - Err(err). - Bytes("passkey-id", session.UserID). - Msg("Failed to get account with passkey id while checking session. Ignoring session") - handler.ServeHTTP(w, r) - return - } - handler.ServeHTTP( - w, - r.WithContext( - context.WithValue( - r.Context(), - ContextKeyActorId, - acc.ID, - ), - ), - ) - }) -} - -func requireValidSessionMiddleware( - h func(http.ResponseWriter, *http.Request), -) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - _, ok := r.Context().Value(ContextKeyActorId).(string) - if !ok { - httputil.HttpErr( - w, - HttpErrIdNotAuthenticated, - "Not authenticated", - http.StatusUnauthorized, - ) - return - } - h(w, r) - } -} - -func buildRequirePermissionsMiddleware(permissionRole *storage.Role) HandlerBuilder { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - accId, ok := r.Context().Value(ContextKeyActorId).(string) - if !ok { - httputil.HttpErr( - w, - HttpErrIdNotAuthenticated, - "Not authenticated", - http.StatusUnauthorized, - ) - return - } - store := StorageFromRequest(r) - log := hlog.FromRequest(r) - acc, err := store.FindAccountById(accId) - // Assumption: If this handler is hit, the middleware for checking if a session exists at all has already passed - // and thus a valid account id must exist in the context - if err != nil { - log.Error(). - Err(err). - Str("account-id", accId). - Msg("Error while getting account from session") - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Error while getting account from session", - http.StatusInternalServerError, - ) - return - } - roles, err := store.FindRolesByNames(acc.Roles) - // Assumption: There will always be at least two roles per user, default user and user-specific one - if err != nil { - httputil.HttpErr( - w, - HttpErrIdDbFailure, - "Failed to get roles for account", - http.StatusInternalServerError, - ) - return - } - collapsedRole := storage.CollapseRolesIntoOne(roles...) - if !storage.CompareRoles(&collapsedRole, permissionRole) { - httputil.HttpErr( - w, - HttpErrIdNotAuthenticated, - "Insufficient permisions", - http.StatusForbidden, - ) - return - } - h.ServeHTTP(w, r) - }) - } -} diff --git a/server/remoteServer/remoteServer.go b/server/remoteServer/remoteServer.go deleted file mode 100644 index 1043093..0000000 --- a/server/remoteServer/remoteServer.go +++ /dev/null @@ -1,13 +0,0 @@ -package remotestorage - -import "git.mstar.dev/mstar/linstrom/storage" - -// Wrapper around db storage -// storage.Storage is for the db and cache access only, -// while this one wraps storage.Storage to also provide remote fetching of missing resources. -// So if an account doesn't exist in db or cache, this wrapper will attempt to fetch it -type RemoteStorage struct { - store *storage.Storage -} - -// TODO: Implement just about everything storage has, but with remote fetching if storage fails diff --git a/server/routerLinstromFe.go b/server/routerLinstromFe.go deleted file mode 100644 index abb4e43..0000000 --- a/server/routerLinstromFe.go +++ /dev/null @@ -1 +0,0 @@ -package server diff --git a/server/server.go b/server/server.go deleted file mode 100644 index e81a01d..0000000 --- a/server/server.go +++ /dev/null @@ -1,98 +0,0 @@ -package server - -import ( - "fmt" - "io/fs" - "net/http" - - httputil "git.mstar.dev/mstar/goutils/http" - "github.com/mstarongithub/passkey" - "github.com/rs/zerolog/log" - - "git.mstar.dev/mstar/linstrom/storage" -) - -type Server struct { - store *storage.Storage - router http.Handler - server *http.Server -} - -func NewServer( - store *storage.Storage, - pkey *passkey.Passkey, - reactiveFS, staticFS fs.FS, - placeholderFile *string, -) *Server { - handler := buildRootHandler(pkey, reactiveFS, staticFS, placeholderFile) - handler = ChainMiddlewares(handler, LoggingMiddleware, ContextValsMiddleware(map[any]any{ - ContextKeyStorage: store, - })) - - server := http.Server{ - Handler: handler, - } - return &Server{ - store: store, - router: handler, - server: &server, - } -} - -func buildRootHandler( - pkey *passkey.Passkey, - reactiveFS, staticFS fs.FS, - placeholderFile *string, -) http.Handler { - mux := http.NewServeMux() - mux.Handle( - "/webauthn/", - http.StripPrefix( - "/webauthn", - forceCorrectPasskeyAuthFlowMiddleware(buildPasskeyAuthRouter(pkey)), - ), - ) - mux.Handle("/", setupFrontendRouter(reactiveFS, staticFS)) - mux.Handle("/pk/", http.StripPrefix("/pk", http.FileServer(http.Dir("pk-auth")))) - mux.HandleFunc("/alive", isAliveHandler) - mux.Handle("/api/", http.StripPrefix("/api", checkSessionMiddleware(setupApiRouter()))) - - mux.Handle( - "/profiling/", - http.StripPrefix("/profiling", profilingAuthenticationMiddleware(setupProfilingHandler())), - ) - // temporary until proper route structure exists - mux.Handle( - "/authonly/", - pkey.Auth( - ContextKeyPasskeyUsername, - nil, - func(w http.ResponseWriter, r *http.Request) { - httputil.HttpErr( - w, - HttpErrIdNotAuthenticated, - "Not authenticated", - http.StatusUnauthorized, - ) - }, - )(ChainMiddlewares(setupTestEndpoints(), passkeyIdToAccountIdTransformerMiddleware)), - ) - - mux.HandleFunc("/placeholder-file", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, *placeholderFile) - }) - - return mux -} - -func buildPasskeyAuthRouter(pkey *passkey.Passkey) http.Handler { - router := http.NewServeMux() - pkey.MountRoutes(router, "/") - return router -} - -func (s *Server) Start(addr string) error { - log.Info().Str("addr", addr).Msg("Starting server") - s.server.Addr = addr - return s.server.ListenAndServe() -} diff --git a/server/testingEndpoints.go b/server/testingEndpoints.go deleted file mode 100644 index 67be6f4..0000000 --- a/server/testingEndpoints.go +++ /dev/null @@ -1,16 +0,0 @@ -package server - -import ( - "fmt" - "net/http" -) - -func setupTestEndpoints() http.Handler { - router := http.NewServeMux() - router.HandleFunc( - "/", - func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "test root") }, - ) - - return router -} diff --git a/server/utils.go b/server/utils.go deleted file mode 100644 index 0c4bf4d..0000000 --- a/server/utils.go +++ /dev/null @@ -1,55 +0,0 @@ -package server - -import ( - "net/http" - - httputil "git.mstar.dev/mstar/goutils/http" - "github.com/rs/zerolog/hlog" - - "git.mstar.dev/mstar/linstrom/storage" -) - -func placeholderEndpoint(w http.ResponseWriter, r *http.Request) { - hlog.FromRequest(r).Error().Stringer("url", r.URL).Msg("Placeholder endpoint accessed") - httputil.HttpErr( - w, - HttpErrIdPlaceholder, - "Endpoint not implemented yet, this is a placeholder", - http.StatusInternalServerError, - ) -} - -func StorageFromRequest(r *http.Request) *storage.Storage { - store, ok := r.Context().Value(ContextKeyStorage).(*storage.Storage) - if !ok { - hlog.FromRequest(r).Fatal().Msg("Failed to get storage reference from context") - return nil - } - return store -} - -func ActorIdFromRequest(r *http.Request) (string, bool) { - id, ok := r.Context().Value(ContextKeyActorId).(string) - return id, ok -} - -func NoteIdFromRequest(r *http.Request) string { - return r.PathValue("noteId") -} - -func AccountIdFromRequest(r *http.Request) string { - return r.PathValue("accountId") -} - -func CheckIfAccountIdHasPermissions(accId string, perms storage.Role, store *storage.Storage) bool { - acc, err := store.FindAccountById(accId) - if err != nil { - return false - } - roles, err := store.FindRolesByNames(acc.Roles) - if err != nil { - return false - } - collapsed := storage.CollapseRolesIntoOne(roles...) - return storage.CompareRoles(&collapsed, &perms) -} diff --git a/shared/flags.go b/shared/flags.go index 74e13df..34388e8 100644 --- a/shared/flags.go +++ b/shared/flags.go @@ -19,7 +19,6 @@ var ( false, "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") FlagStartDebugServer *bool = flag.Bool( "debugserver", false, diff --git a/storage/accessTokens.go b/storage/accessTokens.go deleted file mode 100644 index f1a95bb..0000000 --- a/storage/accessTokens.go +++ /dev/null @@ -1,44 +0,0 @@ -package storage - -import ( - "time" - - "gorm.io/gorm" -) - -type AccessToken struct { - gorm.Model - BelongsToUserId string - Name string - Token string - ExpiresAt time.Time -} - -func (s *Storage) GetTokensForAccId(accId uint) ([]AccessToken, error) { - // TODO: Implement me - panic("Not implemented") -} - -func (s *Storage) NewAccessToken( - forAccId uint, - name string, - expiresAt time.Time, -) (*AccessToken, error) { - // TODO: Implement me - panic("Not implemented") -} - -func (s *Storage) ExtendToken(accId uint, newExpiry time.Time) error { - // TODO: Implement me - panic("Not implemented") -} - -func (s *Storage) RenameToken(accId, oldName string, newName string) error { - // TODO: Implement me - panic("Not implemented") -} - -func (s *Storage) DiscardToken(accId uint, name string) error { - // TODO: Implement me - panic("Not implemented") -} diff --git a/storage/accountRelations.go b/storage/accountRelations.go deleted file mode 100644 index 1a55eb9..0000000 --- a/storage/accountRelations.go +++ /dev/null @@ -1,25 +0,0 @@ -package storage - -import ( - "gorm.io/gorm" -) - -type AccountRelation struct { - gorm.Model - FromId string - ToId string - Accepted bool -} - -func (s *Storage) GetRelationBetween(fromId, toId string) (*AccountRelation, error) { - rel := AccountRelation{} - err := s.db.Where(AccountRelation{FromId: fromId, ToId: toId}).First(&rel).Error - switch err { - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - case nil: - return &rel, nil - default: - return nil, err - } -} diff --git a/storage/cache.go b/storage/cache.go deleted file mode 100644 index dc4a26d..0000000 --- a/storage/cache.go +++ /dev/null @@ -1,121 +0,0 @@ -package storage - -import ( - "errors" - "strings" - - "github.com/redis/go-redis/v9" - "github.com/rs/zerolog/log" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// various prefixes for accessing items in the cache (since it's a simple key-value store) -const ( - cacheUserHandleToIdPrefix = "acc-name-to-id:" - cacheLocalUsernameToIdPrefix = "acc-local-name-to-id:" - cachePasskeyIdToAccIdPrefix = "acc-pkey-id-to-id:" - cacheUserIdToAccPrefix = "acc-id-to-data:" - cacheNoteIdToNotePrefix = "note-id-to-data:" -) - -// An error describing the case where some value was just not found in the cache -var errCacheNotFound = errors.New("not found in cache") - -// Find an account id in cache using a given user handle ("@bob@example.com" or "bob@example.com") -// accId contains the Id of the account if found -// err contains an error describing why an account's id couldn't be found -// The most common one should be errCacheNotFound -func (s *Storage) cacheHandleToAccUid(handle string) (accId *string, err error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // Where to put the data (in case it's found) - var target string - found, err := s.cache.Get(cacheUserHandleToIdPrefix+strings.TrimLeft(handle, "@"), &target) - // If nothing was found, check error - if !found { - // Case error is set and NOT redis' error for nothing found: Return that error - if err != nil && !errors.Is(err, redis.Nil) { - return nil, err - } else { - // Else return errCacheNotFound - return nil, errCacheNotFound - } - } - return &target, nil -} - -// Find a local account's id in cache using a given username ("bob") -// accId containst the Id of the account if found -// err contains an error describing why an account's id couldn't be found -// The most common one should be errCacheNotFound -func (s *Storage) cacheLocalUsernameToAccUid(username string) (accId *string, err error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // Where to put the data (in case it's found) - var target string - found, err := s.cache.Get(cacheLocalUsernameToIdPrefix+username, &target) - // If nothing was found, check error - if !found { - // Case error is set and NOT redis' error for nothing found: Return that error - if err != nil && !errors.Is(err, redis.Nil) { - return nil, err - } else { - // Else return errCacheNotFound - return nil, errCacheNotFound - } - } - return &target, nil -} - -func (s *Storage) cachePkeyIdToAccId(pkeyId []byte) (accId *string, err error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // Where to put the data (in case it's found) - var target string - found, err := s.cache.Get(cachePasskeyIdToAccIdPrefix+string(pkeyId), &target) - // If nothing was found, check error - if !found { - // Case error is set and NOT redis' error for nothing found: Return that error - if err != nil && !errors.Is(err, redis.Nil) { - return nil, err - } else { - // Else return errCacheNotFound - return nil, errCacheNotFound - } - } - return &target, nil -} - -// Find an account's data in cache using a given account id -// acc contains the full account as stored last time if found -// err contains an error describing why an account couldn't be found -// The most common one should be errCacheNotFound -func (s *Storage) cacheAccIdToData(id string) (acc *Account, err error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - var target Account - found, err := s.cache.Get(cacheUserIdToAccPrefix+id, &target) - if !found { - if err != nil && !errors.Is(err, redis.Nil) { - return nil, err - } else { - return nil, errCacheNotFound - } - } - return &target, nil -} - -// Find a cached note given its ID -// note contains the full note as stored last time if found -// err contains an error describing why a note couldn't be found -// The most common one should be errCacheNotFound -func (s *Storage) cacheNoteIdToData(id string) (note *Note, err error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - target := Note{} - found, err := s.cache.Get(cacheNoteIdToNotePrefix+id, &target) - if !found { - if err != nil && !errors.Is(err, redis.Nil) { - return nil, err - } else { - return nil, errCacheNotFound - } - } - return &target, nil -} diff --git a/storage/cache/cache.go b/storage/cache/cache.go deleted file mode 100644 index d3a563f..0000000 --- a/storage/cache/cache.go +++ /dev/null @@ -1,105 +0,0 @@ -package cache - -import ( - "context" - "fmt" - "time" - - "github.com/dgraph-io/ristretto" - "github.com/eko/gocache/lib/v4/cache" - "github.com/eko/gocache/lib/v4/store" - redis_store "github.com/eko/gocache/store/redis/v4" - ristretto_store "github.com/eko/gocache/store/ristretto/v4" - "github.com/redis/go-redis/v9" - "github.com/rs/zerolog/log" - - "git.mstar.dev/mstar/linstrom/config" - "git.mstar.dev/mstar/linstrom/shared" -) - -type Cache struct { - cache *cache.ChainCache[[]byte] - decoders *DecoderPool - encoders *EncoderPool -} - -var ctxBackground = context.Background() - -// TODO: Maybe also include metrics -func NewCache(maxSize int64, redisUrl *string) (*Cache, error) { - // ristretto is an in-memory cache - log.Debug().Int64("max-size", maxSize).Msg("Setting up ristretto") - ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ - // The *10 is a recommendation from ristretto - NumCounters: maxSize * 10, - MaxCost: maxSize, - BufferItems: 64, // Same here - }) - if err != nil { - return nil, fmt.Errorf("ristretto cache error: %w", err) - } - ristrettoStore := ristretto_store.NewRistretto( - ristrettoCache, - store.WithExpiration( - time.Second*time.Duration(config.GlobalConfig.Storage.MaxInMemoryCacheSize), - ), - ) - - var cacheManager *cache.ChainCache[[]byte] - if redisUrl != nil { - opts, err := redis.ParseURL(*redisUrl) - if err != nil { - return nil, err - } - redisClient := redis.NewClient(opts) - redisStore := redis_store.NewRedis( - redisClient, - store.WithExpiration( - time.Second*time.Duration(*config.GlobalConfig.Storage.MaxRedisCacheTTL), - ), - ) - cacheManager = cache.NewChain( - cache.New[[]byte](ristrettoStore), - cache.New[[]byte](redisStore), - ) - } else { - cacheManager = cache.NewChain(cache.New[[]byte](ristrettoStore)) - } - - return &Cache{ - cache: cacheManager, - decoders: NewDecoderPool(), - encoders: NewEncoderPool(), - }, nil -} - -func (c *Cache) Get(key string, target any) (bool, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - return false, nil - data, err := c.cache.Get(ctxBackground, key) - if err != nil { - return false, err - } - err = c.decoders.Decode(data, target) - if err != nil { - return false, err - } - return true, err -} - -func (c *Cache) Set(key string, value any) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return nil - data, err := c.encoders.Encode(value) - if err != nil { - return err - } - err = c.cache.Set(ctxBackground, key, data, nil) - return err -} - -func (c *Cache) Delete(key string) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // Error return doesn't matter here. Delete is delete is gone - _ = c.cache.Delete(ctxBackground, key) -} diff --git a/storage/cache/coderPools.go b/storage/cache/coderPools.go deleted file mode 100644 index 7003939..0000000 --- a/storage/cache/coderPools.go +++ /dev/null @@ -1,164 +0,0 @@ -package cache - -import ( - "io" - "sync" -) - -type EncoderPool struct { - encoders []*gobEncoder - lock sync.RWMutex -} - -func NewEncoderPool() *EncoderPool { - return &EncoderPool{ - encoders: []*gobEncoder{}, - lock: sync.RWMutex{}, - } -} - -// Encode some value with gob -func (p *EncoderPool) Encode(raw any) ([]byte, error) { - var encoder *gobEncoder - // First try to find an available encoder - // Read only lock should be fine here since locks are atomic i - //and thus no two goroutines should be able to lock the same encoder at the same time - // One of those attempts is going to fail and continue looking for another available one - p.lock.RLock() - for _, v := range p.encoders { - // If we can lock one, it's available - if v.TryLock() { - // Keep the reference, then break - encoder = v - break - } - } - p.lock.RUnlock() - // Didn't find an available encoder, create new one and add to pool - if encoder == nil { - encoder = p.expand() - } - // Ensure we free the encoder at the end - defer encoder.Unlock() - // Clear the buffer to avoid funky output from previous operations - encoder.Buffer.Reset() - if err := encoder.Encoder.Encode(raw); err != nil { - return nil, err - } - data, err := io.ReadAll(encoder.Buffer) - if err != nil { - return nil, err - } - return data, nil -} - -// Expands the pool of available encoders by one and returns a reference to the new one -// The new encoder is already locked and ready for use -func (p *EncoderPool) expand() *gobEncoder { - enc := newEncoder() - // Lock everything. First the pool fully since we need to overwrite the encoders slice - p.lock.Lock() - // And then the new encoder to make it available for use by the caller - // so that they don't have to search for it again - enc.Lock() - p.encoders = append(p.encoders, &enc) - p.lock.Unlock() - return &enc -} - -// Prune all encoders not currently used from the pool -func (p *EncoderPool) Prune() { - stillActiveEncoders := []*gobEncoder{} - p.lock.Lock() - for _, v := range p.encoders { - if !v.TryLock() { - // Can't lock, encoder in use, keep it - stillActiveEncoders = append(stillActiveEncoders, v) - continue - } - // If we reach here, the encoder was available (since not locked), unlock and continue - v.Unlock() - } - // Overwrite list of available encoders to only contain the ones we found to still be active - p.encoders = stillActiveEncoders - p.lock.Unlock() -} - -type DecoderPool struct { - encoders []*gobDecoder - lock sync.RWMutex -} - -func NewDecoderPool() *DecoderPool { - return &DecoderPool{ - encoders: []*gobDecoder{}, - lock: sync.RWMutex{}, - } -} - -// Decode some value with gob -func (p *DecoderPool) Decode(raw []byte, target any) error { - var encoder *gobDecoder - // First try to find an available encoder - // Read only lock should be fine here since locks are atomic i - //and thus no two goroutines should be able to lock the same encoder at the same time - // One of those attempts is going to fail and continue looking for another available one - p.lock.RLock() - for _, v := range p.encoders { - // If we can lock one, it's available - if v.TryLock() { - // Keep the reference, then break - encoder = v - break - } - } - p.lock.RUnlock() - // Didn't find an available encoder, create new one and add to pool - if encoder == nil { - encoder = p.expand() - } - // Desure we free the encoder at the end - defer encoder.Unlock() - // Clear the buffer to avoid funky output from previous operations - encoder.Buffer.Reset() - // Write the raw data to the buffer, then decode it - // The write will always succeed (or panic) - _, _ = encoder.Buffer.Write(raw) - err := encoder.Decoder.Decode(target) - if err != nil { - return err - } - return nil -} - -// Expands the pool of available encoders by one and returns a reference to the new one -// The new encoder is already locked and ready for use -func (p *DecoderPool) expand() *gobDecoder { - enc := newDecoder() - // Lock everything. First the pool fully since we need to overwrite the encoders slice - p.lock.Lock() - // And then the new encoder to make it available for use by the caller - // so that they don't have to search for it again - enc.Lock() - p.encoders = append(p.encoders, &enc) - p.lock.Unlock() - return &enc -} - -// Prune all encoders not currently used from the pool -func (p *DecoderPool) Prune() { - stillActiveDecoders := []*gobDecoder{} - p.lock.Lock() - for _, v := range p.encoders { - if !v.TryLock() { - // Can't lock, encoder in use, keep it - stillActiveDecoders = append(stillActiveDecoders, v) - continue - } - // If we reach here, the encoder was available (since not locked), unlock and continue - v.Unlock() - } - // Overwrite list of available encoders to only contain the ones we found to still be active - p.encoders = stillActiveDecoders - p.lock.Unlock() -} diff --git a/storage/cache/lockedCoders.go b/storage/cache/lockedCoders.go deleted file mode 100644 index 74f3ed7..0000000 --- a/storage/cache/lockedCoders.go +++ /dev/null @@ -1,35 +0,0 @@ -package cache - -import ( - "bytes" - "encoding/gob" - "sync" -) - -type gobEncoder struct { - sync.Mutex - Encoder *gob.Encoder - Buffer *bytes.Buffer -} - -func newEncoder() gobEncoder { - buf := bytes.Buffer{} - return gobEncoder{ - Encoder: gob.NewEncoder(&buf), - Buffer: &buf, - } -} - -type gobDecoder struct { - sync.Mutex - Decoder *gob.Decoder - Buffer *bytes.Buffer -} - -func newDecoder() gobDecoder { - buf := bytes.Buffer{} - return gobDecoder{ - Decoder: gob.NewDecoder(&buf), - Buffer: &buf, - } -} diff --git a/storage/emote.go b/storage/emote.go deleted file mode 100644 index 6f0bb6e..0000000 --- a/storage/emote.go +++ /dev/null @@ -1,25 +0,0 @@ -package storage - -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 -} - -func (s *Storage) GetEmoteById(id uint) (*Emote, error) { - out := Emote{} - err := s.db.First(&out, id).Error - switch err { - case nil: - return &out, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} diff --git a/storage/errors.go b/storage/errors.go deleted file mode 100644 index be599e6..0000000 --- a/storage/errors.go +++ /dev/null @@ -1,15 +0,0 @@ -package storage - -import "errors" - -type ErrNotImplemented struct{} - -func (n ErrNotImplemented) Error() string { - return "Not implemented yet" -} - -var ErrEntryNotFound = errors.New("entry not found") -var ErrEntryAlreadyExists = errors.New("entry already exists") -var ErrNothingToChange = errors.New("nothing to change") -var ErrInvalidData = errors.New("invalid data") -var ErrNotAllowed = errors.New("action not allowed") diff --git a/storage/gormLogger.go b/storage/gormLogger.go deleted file mode 100644 index ed8f976..0000000 --- a/storage/gormLogger.go +++ /dev/null @@ -1,64 +0,0 @@ -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 -} diff --git a/storage/housekeeping.go b/storage/housekeeping.go deleted file mode 100644 index a0a3d10..0000000 --- a/storage/housekeeping.go +++ /dev/null @@ -1,11 +0,0 @@ -package storage - -// Contains various functions for housekeeping -// Things like true deletion of soft deleted data after some time -// Or removing inactive access tokens -// All of this will be handled by goroutines - -// TODO: Delete everything soft deleted and older than a month -// TODO: Delete old tokens not in active use anymore -// TODO: Start jobs where the last check-in was more than an hour ago -// diff --git a/storage/inboundJobs.go b/storage/inboundJobs.go deleted file mode 100644 index 52018ad..0000000 --- a/storage/inboundJobs.go +++ /dev/null @@ -1,68 +0,0 @@ -package storage - -import ( - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// Auto-generate string names for the various constants -//go:generate stringer -type InboundJobSource - -type InboundJobSource int - -// TODO: Adjust and expand these constants later, depending on sources -const ( - InJobSourceAccInbox InboundJobSource = iota - InJobSourceServerInbox - InJobSourceApiMasto - InJobSourceApiLinstrom -) - -// Store inbound jobs from api and ap in the db until they finished processing -// Ensures data consistency in case the server is forced to restart unexpectedly -// Inbound jobs must allways be processed in order from oldest to newest to ensure consistency -type InboundJob struct { - gorm.Model - // Raw data, could be json or gob data, check source for how to interpret - RawData []byte `gorm:"->;<-create"` - // Where this job is coming from. Important for figuring out how to decode the raw data and what to do with it - Source InboundJobSource `gorm:"->;<-create"` - - // Section: Various data - // TODO: Expand based on needs - - // If from an inbox, include the owner id here - InboxOwner *string `gorm:"->;<-create"` -} - -func (s *Storage) AddNewInboundJob(data []byte, source InboundJobSource, inboxOwner *string) { - defer shared.Untrace(shared.Trace(&log.Logger)) - newJob := InboundJob{ - RawData: data, - Source: source, - InboxOwner: inboxOwner, - } - s.db.Create(&newJob) -} - -// Get the specified amount of jobs, sorted by age (oldest first) -func (s *Storage) GetOldestInboundJobs(amount uint) ([]InboundJob, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - jobs := []InboundJob{} - switch err := s.db.Order("id asc, created_at asc").Limit(int(amount)).Find(jobs).Error; err { - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - case nil: - return jobs, nil - default: - return nil, err - } -} - -func (s *Storage) CompleteInboundJob(id uint) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - s.db.Delete(InboundJob{Model: gorm.Model{ID: id}}) - return nil -} diff --git a/storage/inboundjobsource_string.go b/storage/inboundjobsource_string.go deleted file mode 100644 index cc240c9..0000000 --- a/storage/inboundjobsource_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -type InboundJobSource"; DO NOT EDIT. - -package storage - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[InJobSourceAccInbox-0] - _ = x[InJobSourceServerInbox-1] - _ = x[InJobSourceApiMasto-2] - _ = x[InJobSourceApiLinstrom-3] -} - -const _InboundJobSource_name = "InJobSourceAccInboxInJobSourceServerInboxInJobSourceApiMastoInJobSourceApiLinstrom" - -var _InboundJobSource_index = [...]uint8{0, 19, 41, 60, 82} - -func (i InboundJobSource) String() string { - if i < 0 || i >= InboundJobSource(len(_InboundJobSource_index)-1) { - return "InboundJobSource(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _InboundJobSource_name[_InboundJobSource_index[i]:_InboundJobSource_index[i+1]] -} diff --git a/storage/mediaFile.go b/storage/mediaFile.go deleted file mode 100644 index 4f45079..0000000 --- a/storage/mediaFile.go +++ /dev/null @@ -1,95 +0,0 @@ -package storage - -import ( - "time" - - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// MediaMetadata contains metadata about some media -// Metadata includes whether it's a remote file or not, what the name is, -// the MIME type, and an identifier pointing to its location -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 .png, .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 -} - -func (s *Storage) NewMediaMetadata( - ownerId, location, mediaType, name string, -) (*MediaMetadata, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - newMedia := MediaMetadata{ - OwnedBy: ownerId, - Location: location, - Name: name, - Type: mediaType, - } - s.db.Create(&newMedia) - return nil, nil -} - -func (s *Storage) FuzzyFindMediaMetadataByName(name string) ([]MediaMetadata, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - notes := []MediaMetadata{} - err := s.db.Where("name LIKE %?%", name).Find(notes).Error - if err != nil { - return nil, err - } - return notes, nil -} - -func (s *Storage) GetMediaMetadataById(id string) (*MediaMetadata, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - media := MediaMetadata{ID: id} - err := s.db.First(&media).Error - if err != nil { - return nil, err - } - return &media, nil -} - -func (s *Storage) FuzzyFindMediaMetadataByLocation(location string) ([]MediaMetadata, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - data := []MediaMetadata{} - if err := s.db.Where("location LIKE %?%", location).Find(data).Error; err != nil { - return nil, err - } - return data, nil -} - -func (s *Storage) DeleteMediaMetadataById(id string) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Delete(MediaMetadata{ID: id}).Error -} - -func (s *Storage) DeleteMediaMetadataByFuzzyLocation(location string) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - var tmp MediaMetadata - return s.db.Where("location LIKE %?%", location).Delete(&tmp).Error -} - -func (s *Storage) DeleteMediaMetadataByFuzzyName(name string) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - var tmp MediaMetadata - return s.db.Where("name LIKE %?%", name).Delete(&tmp).Error -} diff --git a/storage/mediaProvider/preprocessor.go b/storage/mediaProvider/preprocessor.go deleted file mode 100644 index 1a29755..0000000 --- a/storage/mediaProvider/preprocessor.go +++ /dev/null @@ -1,91 +0,0 @@ -package mediaprovider - -import ( - "bytes" - "encoding/base64" - "errors" - "image" - "image/jpeg" - "image/png" - "io" - "strings" - - "github.com/gabriel-vasile/mimetype" - "github.com/gen2brain/avif" - "golang.org/x/image/draw" - "golang.org/x/image/webp" -) - -var ErrUnknownImageType = errors.New("unknown image format") - -func Compress(dataReader io.Reader, mimeType *string) (io.Reader, error) { - // TODO: Get inspired by GTS and use wasm ffmpeg (https://codeberg.org/gruf/go-ffmpreg) for compression - data, err := io.ReadAll(dataReader) - if err != nil { - return nil, err - } - if mimeType == nil { - tmp := mimetype.Detect(data).String() - mimeType = &tmp - } - uberType, subType, _ := strings.Cut(*mimeType, "/") - var dataOut []byte - switch uberType { - case "text": - case "application": - case "image": - dataOut, err = compressImage(data, subType, 1280, 720) - case "video": - dataOut, err = compressVideo(data, subType) - case "audio": - case "font": - default: - } - if err != nil && err != ErrUnknownImageType { - return nil, err - } - dataOut = compressBase64(dataOut) - return bytes.NewReader(dataOut), nil -} - -func compressVideo(dataIn []byte, subType string) (dataOut []byte, err error) { - // TODO: Implement me - panic("Implement me") -} - -func compressImage( - dataIn []byte, - subType string, - maxSizeX, maxSizeY uint, -) (dataOut []byte, err error) { - imageSize := image.Rect(0, 0, int(maxSizeX), int(maxSizeY)) - dst := image.NewRGBA(imageSize) - var sourceImage image.Image - switch subType { - case "png": - sourceImage, err = png.Decode(bytes.NewReader(dataIn)) - if err != nil { - return nil, err - } - case "jpg", "jpeg": - sourceImage, err = jpeg.Decode(bytes.NewReader(dataIn)) - case "webp": - sourceImage, err = webp.Decode(bytes.NewReader(dataIn)) - case "avif": - sourceImage, err = avif.Decode(bytes.NewReader(dataIn)) - default: - return nil, ErrUnknownImageType - } - if err != nil { - return nil, err - } - - draw.CatmullRom.Scale(dst, imageSize, sourceImage, sourceImage.Bounds(), draw.Src, nil) - return dst.Pix, nil -} - -func compressBase64(dataIn []byte) []byte { - result := []byte{} - base64.StdEncoding.Encode(result, dataIn) - return result -} diff --git a/storage/mediaProvider/provider.go b/storage/mediaProvider/provider.go deleted file mode 100644 index 00c41eb..0000000 --- a/storage/mediaProvider/provider.go +++ /dev/null @@ -1,59 +0,0 @@ -package mediaprovider - -import ( - "context" - "time" - - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "git.mstar.dev/mstar/linstrom/config" -) - -type Provider struct { - client *minio.Client -} - -// Create a new storage provider using the values from the global config -// It also sets up the bucket in the s3 provider if it doesn't exist yet -func NewProviderFromConfig() (*Provider, error) { - client, err := minio.New(config.GlobalConfig.S3.Endpoint, &minio.Options{ - Creds: credentials.NewStaticV4( - config.GlobalConfig.S3.KeyId, - config.GlobalConfig.S3.Secret, - "", - ), - Region: config.GlobalConfig.S3.Region, - Secure: config.GlobalConfig.S3.UseSSL, - }) - if err != nil { - return nil, err - } - - if err = setupBucket(client, config.GlobalConfig.S3.BucketName); err != nil { - return nil, err - } - return &Provider{ - client: client, - }, nil -} - -func setupBucket(client *minio.Client, bucketName string) error { - ctx := context.Background() - // Give it half a minute tops to check if a bucket with the given name already exists - existsContext, cancel := context.WithTimeout(ctx, time.Second*30) - defer cancel() - ok, err := client.BucketExists(existsContext, bucketName) - if err != nil { - return err - } - if ok { - return nil - } - // Same timeout for creating the new bucket if it doesn't exist - createContext, createCancel := context.WithTimeout(ctx, time.Second*30) - defer createCancel() - err = client.MakeBucket(createContext, bucketName, minio.MakeBucketOptions{ - Region: config.GlobalConfig.S3.Region, - }) - return err -} diff --git a/storage/noteTargets.go b/storage/noteTargets.go deleted file mode 100644 index 21bcb1d..0000000 --- a/storage/noteTargets.go +++ /dev/null @@ -1,41 +0,0 @@ -package storage - -import ( - "database/sql/driver" - "errors" -) - -// For pretty printing during debug -// If `go generate` is run, it'll generate the necessary function and data for pretty printing -//go:generate stringer -type NoteAccessLevel - -// What feed a note is targeting (public, home, followers or dm) -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 -} - -// Converts the raw value from the DB into a NoteTarget -func (n *NoteAccessLevel) Scan(value any) error { - vBig, ok := value.(int64) - if !ok { - return errors.New("not an int64") - } - *n = NoteAccessLevel(vBig) - return nil -} diff --git a/storage/noteaccesslevel_string.go b/storage/noteaccesslevel_string.go deleted file mode 100644 index 6f71df3..0000000 --- a/storage/noteaccesslevel_string.go +++ /dev/null @@ -1,37 +0,0 @@ -// Code generated by "stringer -type NoteAccessLevel"; DO NOT EDIT. - -package storage - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[NOTE_TARGET_PUBLIC-0] - _ = x[NOTE_TARGET_HOME-2] - _ = x[NOTE_TARGET_FOLLOWERS-4] - _ = x[NOTE_TARGET_DM-8] -} - -const ( - _NoteAccessLevel_name_0 = "NOTE_TARGET_PUBLIC" - _NoteAccessLevel_name_1 = "NOTE_TARGET_HOME" - _NoteAccessLevel_name_2 = "NOTE_TARGET_FOLLOWERS" - _NoteAccessLevel_name_3 = "NOTE_TARGET_DM" -) - -func (i NoteAccessLevel) String() string { - switch { - case i == 0: - return _NoteAccessLevel_name_0 - case i == 2: - return _NoteAccessLevel_name_1 - case i == 4: - return _NoteAccessLevel_name_2 - case i == 8: - return _NoteAccessLevel_name_3 - default: - return "NoteAccessLevel(" + strconv.FormatInt(int64(i), 10) + ")" - } -} diff --git a/storage/notes.go b/storage/notes.go deleted file mode 100644 index 3c40fae..0000000 --- a/storage/notes.go +++ /dev/null @@ -1,164 +0,0 @@ -package storage - -import ( - "fmt" - "time" - - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// Note represents an ActivityPub note -// ActivityPub notes can be quite a few things, depending on fields provided. -// A survey, a reply, a quote of another note, etc -// And depending on the origin server of a note, they are treated differently -// with for example rendering or available actions -// This struct attempts to contain all information necessary for easily working with a note -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 - Attachments []string `gorm:"serializer:json"` // List of Ids for mediaFiles - Emotes []string `gorm:"serializer:json"` // Emotes used in that message - 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) - Pings []string `gorm:"serializer:json"` // Who is being tagged in this message. Also serves as DM targets - OriginServer string // Url of the origin server. Also the primary key for those - Tags []string `gorm:"serializer:json"` // Hashtags -} - -func (s *Storage) FindNoteById(id string) (*Note, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - note := &Note{} - cacheNote, err := s.cacheNoteIdToData(id) - switch err { - case nil: - return cacheNote, nil - // Empty case, not found in cache means check db - case errCacheNotFound: - default: - return nil, err - } - switch err { - - } - err = s.db.Find(note, id).Error - switch err { - case nil: - if err = s.cache.Set(cacheNoteIdToNotePrefix+id, note); err != nil { - log.Warn().Err(err).Str("note-id", id).Msg("Failed to place note in cache") - } - return note, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -func (s *Storage) FindNotesByFuzzyContent(fuzzyContent string) ([]Note, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - notes := []Note{} - // TODO: Figure out if cache can be used here too - err := s.db.Where("raw_content LIKE %?%", fuzzyContent).Find(notes).Error - if err != nil { - return nil, err - } - return notes, nil -} - -func (s *Storage) FindNotesByAuthorHandle(handle string) ([]Note, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - acc, err := s.FindAccountByFullHandle(handle) - if err != nil { - return nil, fmt.Errorf("account with handle %s not found: %w", handle, err) - } - return s.FindNotesByAuthorId(acc.ID) -} - -func (s *Storage) FindNotesByAuthorId(id string) ([]Note, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - notes := []Note{} - err := s.db.Where("creator = ?", id).Find(notes).Error - switch err { - case nil: - return notes, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -func (s *Storage) UpdateNote(note *Note) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - if note == nil || note.ID == "" { - return ErrInvalidData - } - err := s.db.Save(note).Error - if err != nil { - return err - } - err = s.cache.Set(cacheNoteIdToNotePrefix+note.ID, note) - if err != nil { - log.Warn(). - Err(err). - Msg("Failed to update note into cache. Cache and db might be out of sync, a force sync is recommended") - } - return nil -} - -func (s *Storage) CreateNoteLocal( - creatorId string, - rawContent string, - contentWarning *string, - timestamp time.Time, - attachmentIds []string, - emoteIds []string, - repliesToId *string, - quotesId *string, - accessLevel NoteAccessLevel, - tags []string, -) (*Note, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // TODO: Implement me - panic("not implemented") -} - -func (s *Storage) CreateNoteRemote( - creatorId string, - rawContent string, - contentWarning *string, - timestamp time.Time, - attachmentIds []string, - emoteIds []string, - repliesToId *string, - quotesId *string, - accessLevel NoteAccessLevel, - tags []string, - originId string, -) (*Note, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // TODO: Implement me - panic("not implemented") -} - -func (s *Storage) DeleteNote(id string) { - defer shared.Untrace(shared.Trace(&log.Logger)) - s.cache.Delete(cacheNoteIdToNotePrefix + id) - s.db.Delete(Note{ID: id}) -} diff --git a/storage/outboundJobs.go b/storage/outboundJobs.go deleted file mode 100644 index 0fe6995..0000000 --- a/storage/outboundJobs.go +++ /dev/null @@ -1,65 +0,0 @@ -package storage - -import ( - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -type OutboundJob struct { - gorm.Model // Include full model. Gives ID, created and updated at timestamps as well as soft deletes - // Read (and create) only values to ensure consistency - TargetServer string `gorm:"->;<-:create"` // The url of the target server - TargetPath string `gorm:"->;<-:create"` // The full path of api endpoint targeted - Data []byte `gorm:"->;<-:create"` // The raw data to send -} - -func (s *Storage) AddNewOutboundJob(data []byte, targetDomain string, targetUrl string) { - defer shared.Untrace(shared.Trace(&log.Logger)) - newJob := OutboundJob{ - Data: data, - TargetServer: targetDomain, - TargetPath: targetUrl, - } - s.db.Create(&newJob) -} - -// Get the specified amount of jobs, sorted by age (oldest first) -func (s *Storage) GetOldestOutboundJobs(amount uint) ([]OutboundJob, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - jobs := []OutboundJob{} - err := s.db.Order("id asc, created_at asc").Limit(int(amount)).Find(jobs).Error - switch err { - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - case nil: - return jobs, nil - default: - return nil, err - } -} - -func (s *Storage) GetOutboundJobsForDomain(domain string, amount uint) ([]OutboundJob, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - jobs := []OutboundJob{} - err := s.db.Where("target_server = ?", domain). - Order("id asc, created_at asc"). - Limit(int(amount)). - Find(jobs). - Error - switch err { - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - case nil: - return jobs, nil - default: - return nil, err - } -} - -func (s *Storage) CompleteOutboundJob(id uint) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - s.db.Delete(OutboundJob{Model: gorm.Model{ID: id}}) - return nil -} diff --git a/storage/passkeySessions.go b/storage/passkeySessions.go deleted file mode 100644 index 97ca0d6..0000000 --- a/storage/passkeySessions.go +++ /dev/null @@ -1,60 +0,0 @@ -package storage - -import ( - "github.com/go-webauthn/webauthn/webauthn" - "github.com/google/uuid" - "github.com/rs/zerolog/log" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// Session data used during login attempts with a passkey -// Not actually used afterwards to verify a normal session -// NOTE: Doesn't contain a DeletedAt field, thus deletions are automatically hard and not reversible -type PasskeySession struct { - ID string `gorm:"primarykey"` - Data webauthn.SessionData `gorm:"serializer:json"` -} - -// ---- Section SessionStore - -// Generate some id for a new session. Just returns a new uuid -func (s *Storage) GenSessionID() (string, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - x := uuid.NewString() - log.Debug().Str("session-id", x).Msg("Generated new passkey session id") - return x, nil -} - -// Look for an active session with a given id -// Returns the session if found and a bool indicating if a session was found -func (s *Storage) GetSession(sessionId string) (*webauthn.SessionData, bool) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Str("id", sessionId).Msg("Looking for passkey session") - session := PasskeySession{} - res := s.db.Where("id = ?", sessionId).First(&session) - if res.Error != nil { - return nil, false - } - log.Debug().Str("id", sessionId).Any("webauthn-data", &session).Msg("Found passkey session") - return &session.Data, true -} - -// Save (or update) a session with the new data -func (s *Storage) SaveSession(token string, data *webauthn.SessionData) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Str("id", token).Any("webauthn-data", data).Msg("Saving passkey session") - session := PasskeySession{ - ID: token, - Data: *data, - } - s.db.Save(&session) -} - -// Delete a session -// NOTE: This is a hard delete since the session struct contains no DeletedAt field -func (s *Storage) DeleteSession(token string) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Str("id", token).Msg("Deleting passkey session (if one exists)") - s.db.Delete(&PasskeySession{ID: token}) -} diff --git a/storage/reactions.go b/storage/reactions.go deleted file mode 100644 index db5ff4c..0000000 --- a/storage/reactions.go +++ /dev/null @@ -1,10 +0,0 @@ -package storage - -import "gorm.io/gorm" - -type Reaction struct { - gorm.Model - NoteId string - ReactorId string - EmoteId uint -} diff --git a/storage/remoteServerInfo.go b/storage/remoteServerInfo.go deleted file mode 100644 index fec15ad..0000000 --- a/storage/remoteServerInfo.go +++ /dev/null @@ -1,113 +0,0 @@ -package storage - -import ( - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -type RemoteServer struct { - gorm.Model - ServerType RemoteServerType // 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 -} - -func (s *Storage) FindRemoteServerByDomain(url string) (*RemoteServer, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - server := RemoteServer{} - err := s.db.Where("domain = ?").First(&server).Error - switch err { - case nil: - return &server, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -// Find a remote server with a given display name -func (s *Storage) FindRemoteServerByDisplayName(displayName string) (*RemoteServer, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - server := RemoteServer{} - err := s.db.Where("name = ?", displayName).First(&server).Error - switch err { - case nil: - return &server, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -func (s *Storage) FindRemoteServerById(id uint) (*RemoteServer, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - server := RemoteServer{} - err := s.db.First(&server, id).Error - switch err { - case nil: - return &server, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -// Create a new remote server -func (s *Storage) NewRemoteServer( - url, displayName, icon string, - serverType RemoteServerType, -) (*RemoteServer, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - _, err := s.FindRemoteServerByDomain(url) - switch err { - case nil: - return nil, ErrEntryAlreadyExists - case ErrEntryNotFound: // Empty case, not found is what we want - default: - return nil, err - } - server := RemoteServer{ - Domain: url, - Name: displayName, - Icon: icon, - ServerType: serverType, - } - err = s.db.Create(&server).Error - if err != nil { - return nil, err - } - return &server, nil -} - -// Update a remote server with the given url -// If displayName is set, update that -// If icon is set, update that -// Returns the updated version -func (s *Storage) UpdateRemoteServer(url string, displayName, icon *string) (*RemoteServer, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - if displayName == nil && icon == nil { - return nil, ErrNothingToChange - } - server, err := s.FindRemoteServerByDomain(url) - if err != nil { - return nil, err - } - if displayName != nil { - server.Name = *displayName - } - if icon != nil { - server.Icon = *icon - } - err = s.db.Save(server).Error - if err != nil { - return nil, err - } - return server, nil -} diff --git a/storage/remoteUser.go b/storage/remoteUser.go deleted file mode 100644 index c2bad26..0000000 --- a/storage/remoteUser.go +++ /dev/null @@ -1,7 +0,0 @@ -package storage - -// TODO: More helper stuff - -func (s *Storage) NewRemoteUser(fullHandle string) (*Account, error) { - return nil, nil -} diff --git a/storage/roles.go b/storage/roles.go deleted file mode 100644 index c822883..0000000 --- a/storage/roles.go +++ /dev/null @@ -1,253 +0,0 @@ -package storage - -import ( - "git.mstar.dev/mstar/goutils/sliceutils" - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// 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=rolesshared_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 -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 - - // Internal ids of accounts blocked by this role - BlockedUsers []string `gorm:"type:bytes;serializer:gob"` // 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 -*/ - -func (s *Storage) NewEmptyRole(name string) (*Role, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - // Check if a role with the given name already exists - _, err := s.FindRoleByName(name) - switch err { - case nil: - return nil, ErrEntryAlreadyExists - case ErrEntryNotFound: // Empty case, since this is what we want - default: - return nil, err - } - - // New roles have a priority of 1 by default - newRole := Role{Name: name, Priority: 1} - err = s.db.Create(&newRole).Error - if err != nil { - return nil, err - } - return &newRole, nil -} - -func (s *Storage) FindRoleByName(name string) (*Role, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - role := Role{} - err := s.db.Where("name = ?", name).First(&role).Error - switch err { - case nil: - return &role, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -func (s *Storage) FindRolesByNames(names []string) ([]Role, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - roles := []Role{} - err := s.db.Where("name IN ?", names).Find(&roles).Error - switch err { - case nil: - return roles, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -func (s *Storage) UpdateRole(role *Role) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Save(role).Error -} - -func (s *Storage) DeleteRoleByName(name string) error { - // Prevent deletion of built-in roles - if sliceutils.Contains( - sliceutils.Map(allDefaultRoles, func(t *Role) string { return t.Name }), - name, - ) { - return ErrNotAllowed - } - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Where(&Role{Name: name, IsBuiltIn: false}).Delete(&Role{}).Error -} diff --git a/storage/rolesDefaults.go b/storage/rolesDefaults.go deleted file mode 100644 index 246b249..0000000 --- a/storage/rolesDefaults.go +++ /dev/null @@ -1,244 +0,0 @@ -package storage - -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), - - BlockedUsers: []string{}, - 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), - - BlockedUsers: []string{}, - 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), - - BlockedUsers: []string{}, - 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), - - BlockedUsers: []string{}, - 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, -} diff --git a/storage/rolesUtil_generated.go b/storage/rolesUtil_generated.go deleted file mode 100644 index 4d83e09..0000000 --- a/storage/rolesUtil_generated.go +++ /dev/null @@ -1,336 +0,0 @@ -// Code generated by cmd/RolesGenerator DO NOT EDIT. -// If you need to refresh the content, run go generate again -package storage - -import ( - "slices" - "git.mstar.dev/mstar/goutils/sliceutils" -) - -func CollapseRolesIntoOne(roles ...Role) Role { - startingRole := RoleDeepCopy(DefaultUserRole) - slices.SortFunc(roles, func(a, b Role) int { return int(int64(a.Priority)-int64(b.Priority)) }) - for _, role := range roles { - if role.CanSendReplies != nil { - *startingRole.CanSendReplies = *role.CanSendReplies - } - if role.CanBoost != nil { - *startingRole.CanBoost = *role.CanBoost - } - if role.AutoNsfwMedia != nil { - *startingRole.AutoNsfwMedia = *role.AutoNsfwMedia - } - if role.WithholdNotesForManualApproval != nil { - *startingRole.WithholdNotesForManualApproval = *role.WithholdNotesForManualApproval - } - if role.WithholdNotesBasedOnRegex != nil { - *startingRole.WithholdNotesBasedOnRegex = *role.WithholdNotesBasedOnRegex - } - if role.CanSendCustomReactions != nil { - *startingRole.CanSendCustomReactions = *role.CanSendCustomReactions - } - if role.CanSendLocalNotes != nil { - *startingRole.CanSendLocalNotes = *role.CanSendLocalNotes - } - if role.DisallowInteractionsWith != nil { - startingRole.DisallowInteractionsWith = append(startingRole.DisallowInteractionsWith, role.DisallowInteractionsWith...) - } - if role.CanDeleteNotes != nil { - *startingRole.CanDeleteNotes = *role.CanDeleteNotes - } - if role.CanSupressInteractionsBetweenUsers != nil { - *startingRole.CanSupressInteractionsBetweenUsers = *role.CanSupressInteractionsBetweenUsers - } - if role.CanSendPublicNotes != nil { - *startingRole.CanSendPublicNotes = *role.CanSendPublicNotes - } - if role.ScanCreatedPrivateNotes != nil { - *startingRole.ScanCreatedPrivateNotes = *role.ScanCreatedPrivateNotes - } - if role.ScanCreatedPublicNotes != nil { - *startingRole.ScanCreatedPublicNotes = *role.ScanCreatedPublicNotes - } - if role.CanConfirmWithheldNotes != nil { - *startingRole.CanConfirmWithheldNotes = *role.CanConfirmWithheldNotes - } - if role.CanManageAds != nil { - *startingRole.CanManageAds = *role.CanManageAds - } - if role.CanIncludeSurvey != nil { - *startingRole.CanIncludeSurvey = *role.CanIncludeSurvey - } - if role.CanLogin != nil { - *startingRole.CanLogin = *role.CanLogin - } - if role.CanChangeDisplayName != nil { - *startingRole.CanChangeDisplayName = *role.CanChangeDisplayName - } - if role.FullAdmin != nil { - *startingRole.FullAdmin = *role.FullAdmin - } - if role.CanOverwriteDisplayNames != nil { - *startingRole.CanOverwriteDisplayNames = *role.CanOverwriteDisplayNames - } - if role.CanSendCustomEmotes != nil { - *startingRole.CanSendCustomEmotes = *role.CanSendCustomEmotes - } - if role.CanQuote != nil { - *startingRole.CanQuote = *role.CanQuote - } - if role.CanSubmitReports != nil { - *startingRole.CanSubmitReports = *role.CanSubmitReports - } - if role.WithholdNotesRegexes != nil { - startingRole.WithholdNotesRegexes = append(startingRole.WithholdNotesRegexes, role.WithholdNotesRegexes...) - } - if role.CanAffectOtherAdmins != nil { - *startingRole.CanAffectOtherAdmins = *role.CanAffectOtherAdmins - } - if role.CanViewDeletedNotes != nil { - *startingRole.CanViewDeletedNotes = *role.CanViewDeletedNotes - } - if role.CanManageAvatarDecorations != nil { - *startingRole.CanManageAvatarDecorations = *role.CanManageAvatarDecorations - } - if role.CanSendFollowerOnlyNotes != nil { - *startingRole.CanSendFollowerOnlyNotes = *role.CanSendFollowerOnlyNotes - } - if role.BlockedUsers != nil { - startingRole.BlockedUsers = append(startingRole.BlockedUsers, role.BlockedUsers...) - } - if role.CanAssignRoles != nil { - *startingRole.CanAssignRoles = *role.CanAssignRoles - } - if role.CanManageCustomEmotes != nil { - *startingRole.CanManageCustomEmotes = *role.CanManageCustomEmotes - } - if role.CanSendPrivateNotes != nil { - *startingRole.CanSendPrivateNotes = *role.CanSendPrivateNotes - } - if role.CanFederateBsky != nil { - *startingRole.CanFederateBsky = *role.CanFederateBsky - } - if role.CanMentionOthers != nil { - *startingRole.CanMentionOthers = *role.CanMentionOthers - } - if role.AutoCwPostsText != nil { - *startingRole.AutoCwPostsText = *role.AutoCwPostsText - } - if role.CanSendMedia != nil { - *startingRole.CanSendMedia = *role.CanSendMedia - } - if role.CanIncludeLinks != nil { - *startingRole.CanIncludeLinks = *role.CanIncludeLinks - } - if role.MentionLimit != nil { - *startingRole.MentionLimit = *role.MentionLimit - } - if role.AutoCwPosts != nil { - *startingRole.AutoCwPosts = *role.AutoCwPosts - } - if role.ScanCreatedLocalNotes != nil { - *startingRole.ScanCreatedLocalNotes = *role.ScanCreatedLocalNotes - } - if role.ScanCreatedFollowerOnlyNotes != nil { - *startingRole.ScanCreatedFollowerOnlyNotes = *role.ScanCreatedFollowerOnlyNotes - } - if role.CanRecoverDeletedNotes != nil { - *startingRole.CanRecoverDeletedNotes = *role.CanRecoverDeletedNotes - } - if role.CanSendAnnouncements != nil { - *startingRole.CanSendAnnouncements = *role.CanSendAnnouncements - } - if role.CanFederateFedi != nil { - *startingRole.CanFederateFedi = *role.CanFederateFedi - } - if role.HasMentionCountLimit != nil { - *startingRole.HasMentionCountLimit = *role.HasMentionCountLimit - } - } - return startingRole -} - -func RoleDeepCopy(o Role) Role { - n := Role{} - n.Model = o.Model - n.Name = o.Name - n.Priority = o.Priority - n.IsUserRole = o.IsUserRole - n.IsBuiltIn = o.IsBuiltIn - if o.CanSubmitReports == nil { n.CanSubmitReports = nil } else { - t := *o.CanSubmitReports - n.CanSubmitReports = &t - } - n.WithholdNotesRegexes = slices.Clone(o.WithholdNotesRegexes) - if o.CanAffectOtherAdmins == nil { n.CanAffectOtherAdmins = nil } else { - t := *o.CanAffectOtherAdmins - n.CanAffectOtherAdmins = &t - } - if o.CanViewDeletedNotes == nil { n.CanViewDeletedNotes = nil } else { - t := *o.CanViewDeletedNotes - n.CanViewDeletedNotes = &t - } - if o.CanManageAvatarDecorations == nil { n.CanManageAvatarDecorations = nil } else { - t := *o.CanManageAvatarDecorations - n.CanManageAvatarDecorations = &t - } - if o.CanSendFollowerOnlyNotes == nil { n.CanSendFollowerOnlyNotes = nil } else { - t := *o.CanSendFollowerOnlyNotes - n.CanSendFollowerOnlyNotes = &t - } - n.BlockedUsers = slices.Clone(o.BlockedUsers) - if o.CanAssignRoles == nil { n.CanAssignRoles = nil } else { - t := *o.CanAssignRoles - n.CanAssignRoles = &t - } - if o.CanManageCustomEmotes == nil { n.CanManageCustomEmotes = nil } else { - t := *o.CanManageCustomEmotes - n.CanManageCustomEmotes = &t - } - if o.CanSendPrivateNotes == nil { n.CanSendPrivateNotes = nil } else { - t := *o.CanSendPrivateNotes - n.CanSendPrivateNotes = &t - } - if o.CanFederateBsky == nil { n.CanFederateBsky = nil } else { - t := *o.CanFederateBsky - n.CanFederateBsky = &t - } - if o.CanMentionOthers == nil { n.CanMentionOthers = nil } else { - t := *o.CanMentionOthers - n.CanMentionOthers = &t - } - if o.AutoCwPostsText == nil { n.AutoCwPostsText = nil } else { - t := *o.AutoCwPostsText - n.AutoCwPostsText = &t - } - if o.CanSendMedia == nil { n.CanSendMedia = nil } else { - t := *o.CanSendMedia - n.CanSendMedia = &t - } - if o.CanIncludeLinks == nil { n.CanIncludeLinks = nil } else { - t := *o.CanIncludeLinks - n.CanIncludeLinks = &t - } - if o.MentionLimit == nil { n.MentionLimit = nil } else { - t := *o.MentionLimit - n.MentionLimit = &t - } - if o.AutoCwPosts == nil { n.AutoCwPosts = nil } else { - t := *o.AutoCwPosts - n.AutoCwPosts = &t - } - if o.ScanCreatedLocalNotes == nil { n.ScanCreatedLocalNotes = nil } else { - t := *o.ScanCreatedLocalNotes - n.ScanCreatedLocalNotes = &t - } - if o.ScanCreatedFollowerOnlyNotes == nil { n.ScanCreatedFollowerOnlyNotes = nil } else { - t := *o.ScanCreatedFollowerOnlyNotes - n.ScanCreatedFollowerOnlyNotes = &t - } - if o.CanRecoverDeletedNotes == nil { n.CanRecoverDeletedNotes = nil } else { - t := *o.CanRecoverDeletedNotes - n.CanRecoverDeletedNotes = &t - } - if o.CanSendAnnouncements == nil { n.CanSendAnnouncements = nil } else { - t := *o.CanSendAnnouncements - n.CanSendAnnouncements = &t - } - if o.CanFederateFedi == nil { n.CanFederateFedi = nil } else { - t := *o.CanFederateFedi - n.CanFederateFedi = &t - } - if o.HasMentionCountLimit == nil { n.HasMentionCountLimit = nil } else { - t := *o.HasMentionCountLimit - n.HasMentionCountLimit = &t - } - if o.CanSendReplies == nil { n.CanSendReplies = nil } else { - t := *o.CanSendReplies - n.CanSendReplies = &t - } - if o.CanBoost == nil { n.CanBoost = nil } else { - t := *o.CanBoost - n.CanBoost = &t - } - if o.AutoNsfwMedia == nil { n.AutoNsfwMedia = nil } else { - t := *o.AutoNsfwMedia - n.AutoNsfwMedia = &t - } - if o.WithholdNotesForManualApproval == nil { n.WithholdNotesForManualApproval = nil } else { - t := *o.WithholdNotesForManualApproval - n.WithholdNotesForManualApproval = &t - } - if o.WithholdNotesBasedOnRegex == nil { n.WithholdNotesBasedOnRegex = nil } else { - t := *o.WithholdNotesBasedOnRegex - n.WithholdNotesBasedOnRegex = &t - } - if o.CanSendCustomReactions == nil { n.CanSendCustomReactions = nil } else { - t := *o.CanSendCustomReactions - n.CanSendCustomReactions = &t - } - if o.CanSendLocalNotes == nil { n.CanSendLocalNotes = nil } else { - t := *o.CanSendLocalNotes - n.CanSendLocalNotes = &t - } - n.DisallowInteractionsWith = slices.Clone(o.DisallowInteractionsWith) - if o.CanDeleteNotes == nil { n.CanDeleteNotes = nil } else { - t := *o.CanDeleteNotes - n.CanDeleteNotes = &t - } - if o.CanSupressInteractionsBetweenUsers == nil { n.CanSupressInteractionsBetweenUsers = nil } else { - t := *o.CanSupressInteractionsBetweenUsers - n.CanSupressInteractionsBetweenUsers = &t - } - if o.CanSendPublicNotes == nil { n.CanSendPublicNotes = nil } else { - t := *o.CanSendPublicNotes - n.CanSendPublicNotes = &t - } - if o.ScanCreatedPrivateNotes == nil { n.ScanCreatedPrivateNotes = nil } else { - t := *o.ScanCreatedPrivateNotes - n.ScanCreatedPrivateNotes = &t - } - if o.ScanCreatedPublicNotes == nil { n.ScanCreatedPublicNotes = nil } else { - t := *o.ScanCreatedPublicNotes - n.ScanCreatedPublicNotes = &t - } - if o.CanConfirmWithheldNotes == nil { n.CanConfirmWithheldNotes = nil } else { - t := *o.CanConfirmWithheldNotes - n.CanConfirmWithheldNotes = &t - } - if o.CanManageAds == nil { n.CanManageAds = nil } else { - t := *o.CanManageAds - n.CanManageAds = &t - } - if o.CanIncludeSurvey == nil { n.CanIncludeSurvey = nil } else { - t := *o.CanIncludeSurvey - n.CanIncludeSurvey = &t - } - if o.CanLogin == nil { n.CanLogin = nil } else { - t := *o.CanLogin - n.CanLogin = &t - } - if o.CanChangeDisplayName == nil { n.CanChangeDisplayName = nil } else { - t := *o.CanChangeDisplayName - n.CanChangeDisplayName = &t - } - if o.FullAdmin == nil { n.FullAdmin = nil } else { - t := *o.FullAdmin - n.FullAdmin = &t - } - if o.CanOverwriteDisplayNames == nil { n.CanOverwriteDisplayNames = nil } else { - t := *o.CanOverwriteDisplayNames - n.CanOverwriteDisplayNames = &t - } - if o.CanSendCustomEmotes == nil { n.CanSendCustomEmotes = nil } else { - t := *o.CanSendCustomEmotes - n.CanSendCustomEmotes = &t - } - if o.CanQuote == nil { n.CanQuote = nil } else { - t := *o.CanQuote - n.CanQuote = &t - } - return n -} - -func CompareRoles(a, b *Role) bool { - return (a.CanSendLocalNotes == nil || b.CanSendLocalNotes == nil || a.CanSendLocalNotes == b.CanSendLocalNotes) && (a.CanSendReplies == nil || b.CanSendReplies == nil || a.CanSendReplies == b.CanSendReplies) && (a.CanBoost == nil || b.CanBoost == nil || a.CanBoost == b.CanBoost) && (a.AutoNsfwMedia == nil || b.AutoNsfwMedia == nil || a.AutoNsfwMedia == b.AutoNsfwMedia) && (a.WithholdNotesForManualApproval == nil || b.WithholdNotesForManualApproval == nil || a.WithholdNotesForManualApproval == b.WithholdNotesForManualApproval) && (a.WithholdNotesBasedOnRegex == nil || b.WithholdNotesBasedOnRegex == nil || a.WithholdNotesBasedOnRegex == b.WithholdNotesBasedOnRegex) && (a.CanSendCustomReactions == nil || b.CanSendCustomReactions == nil || a.CanSendCustomReactions == b.CanSendCustomReactions) && (a.ScanCreatedPrivateNotes == nil || b.ScanCreatedPrivateNotes == nil || a.ScanCreatedPrivateNotes == b.ScanCreatedPrivateNotes) && (a.DisallowInteractionsWith == nil || b.DisallowInteractionsWith == nil || sliceutils.CompareUnordered(a.DisallowInteractionsWith,b.DisallowInteractionsWith)) && (a.CanDeleteNotes == nil || b.CanDeleteNotes == nil || a.CanDeleteNotes == b.CanDeleteNotes) && (a.CanSupressInteractionsBetweenUsers == nil || b.CanSupressInteractionsBetweenUsers == nil || a.CanSupressInteractionsBetweenUsers == b.CanSupressInteractionsBetweenUsers) && (a.CanSendPublicNotes == nil || b.CanSendPublicNotes == nil || a.CanSendPublicNotes == b.CanSendPublicNotes) && (a.CanLogin == nil || b.CanLogin == nil || a.CanLogin == b.CanLogin) && (a.ScanCreatedPublicNotes == nil || b.ScanCreatedPublicNotes == nil || a.ScanCreatedPublicNotes == b.ScanCreatedPublicNotes) && (a.CanConfirmWithheldNotes == nil || b.CanConfirmWithheldNotes == nil || a.CanConfirmWithheldNotes == b.CanConfirmWithheldNotes) && (a.CanManageAds == nil || b.CanManageAds == nil || a.CanManageAds == b.CanManageAds) && (a.CanIncludeSurvey == nil || b.CanIncludeSurvey == nil || a.CanIncludeSurvey == b.CanIncludeSurvey) && (a.CanQuote == nil || b.CanQuote == nil || a.CanQuote == b.CanQuote) && (a.CanChangeDisplayName == nil || b.CanChangeDisplayName == nil || a.CanChangeDisplayName == b.CanChangeDisplayName) && (a.FullAdmin == nil || b.FullAdmin == nil || a.FullAdmin == b.FullAdmin) && (a.CanOverwriteDisplayNames == nil || b.CanOverwriteDisplayNames == nil || a.CanOverwriteDisplayNames == b.CanOverwriteDisplayNames) && (a.CanSendCustomEmotes == nil || b.CanSendCustomEmotes == nil || a.CanSendCustomEmotes == b.CanSendCustomEmotes) && (a.BlockedUsers == nil || b.BlockedUsers == nil || sliceutils.CompareUnordered(a.BlockedUsers,b.BlockedUsers)) && (a.CanSubmitReports == nil || b.CanSubmitReports == nil || a.CanSubmitReports == b.CanSubmitReports) && (a.WithholdNotesRegexes == nil || b.WithholdNotesRegexes == nil || sliceutils.CompareUnordered(a.WithholdNotesRegexes,b.WithholdNotesRegexes)) && (a.CanAffectOtherAdmins == nil || b.CanAffectOtherAdmins == nil || a.CanAffectOtherAdmins == b.CanAffectOtherAdmins) && (a.CanViewDeletedNotes == nil || b.CanViewDeletedNotes == nil || a.CanViewDeletedNotes == b.CanViewDeletedNotes) && (a.CanManageAvatarDecorations == nil || b.CanManageAvatarDecorations == nil || a.CanManageAvatarDecorations == b.CanManageAvatarDecorations) && (a.CanSendFollowerOnlyNotes == nil || b.CanSendFollowerOnlyNotes == nil || a.CanSendFollowerOnlyNotes == b.CanSendFollowerOnlyNotes) && (a.CanFederateBsky == nil || b.CanFederateBsky == nil || a.CanFederateBsky == b.CanFederateBsky) && (a.CanAssignRoles == nil || b.CanAssignRoles == nil || a.CanAssignRoles == b.CanAssignRoles) && (a.CanManageCustomEmotes == nil || b.CanManageCustomEmotes == nil || a.CanManageCustomEmotes == b.CanManageCustomEmotes) && (a.CanSendPrivateNotes == nil || b.CanSendPrivateNotes == nil || a.CanSendPrivateNotes == b.CanSendPrivateNotes) && (a.CanIncludeLinks == nil || b.CanIncludeLinks == nil || a.CanIncludeLinks == b.CanIncludeLinks) && (a.CanMentionOthers == nil || b.CanMentionOthers == nil || a.CanMentionOthers == b.CanMentionOthers) && (a.AutoCwPostsText == nil || b.AutoCwPostsText == nil || a.AutoCwPostsText == b.AutoCwPostsText) && (a.CanSendMedia == nil || b.CanSendMedia == nil || a.CanSendMedia == b.CanSendMedia) && (a.HasMentionCountLimit == nil || b.HasMentionCountLimit == nil || a.HasMentionCountLimit == b.HasMentionCountLimit) && (a.MentionLimit == nil || b.MentionLimit == nil || a.MentionLimit == b.MentionLimit) && (a.AutoCwPosts == nil || b.AutoCwPosts == nil || a.AutoCwPosts == b.AutoCwPosts) && (a.ScanCreatedLocalNotes == nil || b.ScanCreatedLocalNotes == nil || a.ScanCreatedLocalNotes == b.ScanCreatedLocalNotes) && (a.ScanCreatedFollowerOnlyNotes == nil || b.ScanCreatedFollowerOnlyNotes == nil || a.ScanCreatedFollowerOnlyNotes == b.ScanCreatedFollowerOnlyNotes) && (a.CanRecoverDeletedNotes == nil || b.CanRecoverDeletedNotes == nil || a.CanRecoverDeletedNotes == b.CanRecoverDeletedNotes) && (a.CanSendAnnouncements == nil || b.CanSendAnnouncements == nil || a.CanSendAnnouncements == b.CanSendAnnouncements) && (a.CanFederateFedi == nil || b.CanFederateFedi == nil || a.CanFederateFedi == b.CanFederateFedi) && (a == nil || b == nil || a.CanFederateFedi == b.CanFederateFedi) -} \ No newline at end of file diff --git a/storage/serverTypes.go b/storage/serverTypes.go deleted file mode 100644 index d8cfdc9..0000000 --- a/storage/serverTypes.go +++ /dev/null @@ -1,39 +0,0 @@ -package storage - -import ( - "database/sql/driver" - "errors" -) - -// TODO: Decide whether to turn this into an int too to save resources -// And then use go:generate instead for pretty printing - -// What software a server is running -// Mostly important for rendering -type RemoteServerType string - -const ( - // Includes forks like glitch-soc, etc - REMOTE_SERVER_MASTODON = RemoteServerType("Mastodon") - // Includes forks like Ice Shrimp, Sharkey, Cutiekey, etc - REMOTE_SERVER_MISSKEY = RemoteServerType("Misskey") - // Includes Akkoma - REMOTE_SERVER_PLEMORA = RemoteServerType("Plemora") - // Wafrn is a new entry - REMOTE_SERVER_WAFRN = RemoteServerType("Wafrn") - // And of course, yours truly - REMOTE_SERVER_LINSTROM = RemoteServerType("Linstrom") -) - -func (r *RemoteServerType) Value() (driver.Value, error) { - return r, nil -} - -func (r *RemoteServerType) Scan(raw any) error { - if v, ok := raw.(string); ok { - *r = RemoteServerType(v) - return nil - } else { - return errors.New("value not a string") - } -} diff --git a/storage/storage.go b/storage/storage.go deleted file mode 100644 index 47f92ce..0000000 --- a/storage/storage.go +++ /dev/null @@ -1,164 +0,0 @@ -// TODO: Unify function names - -// Storage is the handler for cache and db access -// It handles storing various data in the database as well as caching that data -// Said data includes notes, accounts, metadata about media files, servers and similar -package storage - -import ( - "crypto/ed25519" - "fmt" - - "github.com/rs/zerolog/log" - "gorm.io/driver/postgres" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/config" - "git.mstar.dev/mstar/linstrom/shared" - "git.mstar.dev/mstar/linstrom/storage/cache" -) - -// Always keep a reference of the server's own RemoteServer entry here -// Removes the need to perform a db request every time a new local anything -// is created -var serverSelf RemoteServer - -// Storage is responsible for all database, cache and media related actions -// and serves as the lowest layer of the cake -type Storage struct { - db *gorm.DB - cache *cache.Cache -} - -func NewStorage(dbUrl string, cache *cache.Cache) (*Storage, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - db, err := gorm.Open(postgres.Open(dbUrl), &gorm.Config{ - Logger: newGormLogger(log.Logger), - }) - if err != nil { - return nil, err - } - err = db.AutoMigrate( - MediaMetadata{}, - Account{}, - RemoteServer{}, - Note{}, - Role{}, - PasskeySession{}, - InboundJob{}, - OutboundJob{}, - AccessToken{}, - Emote{}, - UserInfoField{}, - AccountRelation{}, - ) - if err != nil { - return nil, fmt.Errorf("failed to apply migrations: %w", err) - } - - s := &Storage{db, cache} - - if err = s.insertDefaultRoles(); err != nil { - return nil, fmt.Errorf("default roles insertion failed: %w", err) - } - - if err = s.insertPlaceholderFile(); err != nil { - return nil, fmt.Errorf("placeholder file insertion failed: %w", err) - } - - if err = s.insertSelfFromConfig(); err != nil { - return nil, fmt.Errorf("self insertion failed: %w", err) - } - - return s, nil -} - -func (s *Storage) insertSelfFromConfig() error { - defer shared.Untrace(shared.Trace(&log.Logger)) - const ServerActorId = "self" - - var err error - - // Insert server info - serverData := RemoteServer{} - err = s.db.Where("id = 1"). - // Set once on creation - Attrs(RemoteServer{ - Domain: config.GlobalConfig.General.GetFullDomain(), - }). - // Set every time - Assign(RemoteServer{ - IsSelf: true, - Name: config.GlobalConfig.Self.ServerDisplayName, - ServerType: REMOTE_SERVER_LINSTROM, - Icon: "placeholder", // TODO: Set to server icon media - }).FirstOrCreate(&serverData).Error - if err != nil { - return err - } - // Set module specific global var - serverSelf = serverData - - // Insert server actor - serverActor := Account{} - serverActorPublicKey, serverActorPrivateKey, err := ed25519.GenerateKey(nil) - if err != nil { - return err - } - err = s.db.Where(Account{ID: ServerActorId}). - // Values to always (re)set after launch - Assign(Account{ - Username: "self", - DisplayName: config.GlobalConfig.Self.ServerActorDisplayName, - // Server: serverData, - ServerId: serverData.ID, - // CustomFields: []uint{}, - Description: "Server actor of a Linstrom server", - // Tags: []string{}, - IsBot: true, - // Followers: []string{}, - // Follows: []string{}, - Indexable: false, - RestrictedFollow: false, - IdentifiesAs: []Being{}, - Gender: []string{}, - Roles: []string{"ServerActor"}, // TODO: Add server actor role once created - }). - // Values that'll only be set on first creation - Attrs(Account{ - PublicKey: serverActorPublicKey, - PrivateKey: serverActorPrivateKey, - Icon: "placeholder", - Background: nil, - Banner: nil, - }). - FirstOrCreate(&serverActor).Error - if err != nil { - return err - } - return nil -} - -func (s *Storage) insertDefaultRoles() error { - defer shared.Untrace(shared.Trace(&log.Logger)) - for _, role := range allDefaultRoles { - log.Debug().Str("role-name", role.Name).Msg("Inserting default role") - if err := s.db.FirstOrCreate(role).Error; err != nil { - return err - } - } - return nil -} - -func (s *Storage) insertPlaceholderFile() error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Model(&MediaMetadata{}).Assign(&MediaMetadata{ - ID: "placeholder", - Type: "image/webp", - Name: "placeholderFile", - Blurred: false, - Remote: false, - Location: "/placeholder-file", - AltText: "Greyscale image of a pidgeon, captioned with the text \"Duck\"", - }).FirstOrCreate(&MediaMetadata{}).Error -} diff --git a/storage/storage.go.old b/storage/storage.go.old deleted file mode 100644 index 9bd457d..0000000 --- a/storage/storage.go.old +++ /dev/null @@ -1,60 +0,0 @@ -package storage - -import ( - "errors" - - "github.com/glebarez/sqlite" - "git.mstar.dev/mstar/linstrom/storage/cache" - "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -// Storage is responsible for all database, cache and media related actions -// and serves as the lowest layer of the cake -type Storage struct { - db *gorm.DB - cache *cache.Cache -} - -var ErrInvalidData = errors.New("invalid data") - -// Build a new storage using sqlite as database backend -func NewStorageSqlite(filePath string, cache *cache.Cache) (*Storage, error) { - db, err := gorm.Open(sqlite.Open(filePath)) - if err != nil { - return nil, err - } - return storageFromEmptyDb(db, cache) -} - -func NewStoragePostgres(dbUrl string, cache *cache.Cache) (*Storage, error) { - db, err := gorm.Open(postgres.Open(dbUrl)) - if err != nil { - return nil, err - } - return storageFromEmptyDb(db, cache) -} - -func storageFromEmptyDb(db *gorm.DB, cache *cache.Cache) (*Storage, error) { - // AutoMigrate ensures the db is in a state where all the structs given here - // have their own tables and relations setup. It also updates tables if necessary - err := db.AutoMigrate( - MediaMetadata{}, - Account{}, - RemoteServer{}, - Note{}, - Role{}, - PasskeySession{}, - InboundJob{}, - OutboundJob{}, - ) - if err != nil { - return nil, err - } - - // And finally, build the actual storage struct - return &Storage{ - db: db, - cache: cache, - }, nil -} diff --git a/storage/user.go b/storage/user.go deleted file mode 100644 index 9453cc1..0000000 --- a/storage/user.go +++ /dev/null @@ -1,483 +0,0 @@ -package storage - -import ( - "crypto/ed25519" - "crypto/rand" - "errors" - "fmt" - "strings" - "time" - - "git.mstar.dev/mstar/linstrom/activitypub" - "git.mstar.dev/mstar/linstrom/shared" - "github.com/go-webauthn/webauthn/webauthn" - "github.com/google/uuid" - "github.com/mstarongithub/passkey" - "github.com/rs/zerolog/log" - "gorm.io/gorm" -) - -// Database representation of a user account -// This can be a bot, remote or not -// If remote, this is used for caching the account -type Account 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 - CustomFields []uint `gorm:"serializer:json"` // IDs to the custom fields a user has - Description string // The description of a user account - Tags []string `gorm:"serializer:json"` // Hashtags - IsBot bool // Whether to mark this account as a script controlled one - Relations []uint `gorm:"serializer:json"` // List of ids of all relations this account has. Both follows and followers - 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 - // List of things the owner identifies as - // Example [cat human robot] means that the owner probably identifies as - // a cyborg-catgirl/boy/human or a cathuman shaped robot, refer to Gender for pronouns - IdentifiesAs []Being `gorm:"serializer:json"` - // List of pronouns the owner identifies with - // An unordered list since the owner can freely set it - // Examples: [she her], [it they its them] or, if you want to go fancy, [this is super serious] - Gender []string `gorm:"serializer:json"` - // The roles assocciated with an account. Values are the names of the roles - Roles []string `gorm:"serializer:json"` - - Location *string - Birthday *time.Time - - // --- And internal account stuff --- - // Still public fields since they wouldn't be able to be stored in the db otherwise - PrivateKey []byte // The private key of the account. Nil if remote user - WebAuthnId []byte // The unique and random ID of this account used for passkey authentication - // 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 - // TODO: Turn this into a map to give passkeys names. - // Needed for supporting a decent passkey management interface. - // Or check if webauthn.Credential has sufficiently easy to identify data - // to use instead of a string mapping - PasskeyCredentials []webauthn.Credential `gorm:"serializer:json"` // Webauthn credentials data - // Has a RemoteAccountLinks included if remote user - RemoteLinks *RemoteAccountLinks -} - -// Contains static and cached info about a remote account, mostly links -type RemoteAccountLinks struct { - // ---- Section: gorm - // Sets this struct up as a value that an Account may have - gorm.Model - AccountID 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 -} - -// Find an account in the db using a given full handle (@max@example.com) -// Returns an account and nil if an account is found, otherwise nil and the error -func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Str("account-handle", handle).Msg("Looking for account by handle") - log.Debug().Str("account-handle", handle).Msg("Checking if there's a cache hit") - - // Try and find the account in cache first - cacheAccId, err := s.cacheHandleToAccUid(handle) - if err == nil { - log.Info().Str("account-handle", handle).Msg("Hit account handle in cache") - // Then always load via id since unique key access should be faster than string matching - return s.FindAccountById(*cacheAccId) - } else { - if !errors.Is(err, errCacheNotFound) { - log.Error().Err(err).Str("account-handle", handle).Msg("Problem while checking cache for account") - return nil, err - } - } - - // Failed to find in cache, go the slow route of hitting the db - log.Debug().Str("account-handle", handle).Msg("Didn't hit account in cache, going to db") - name, server, err := activitypub.SplitFullHandle(handle) - if err != nil { - log.Warn().Err(err).Str("account-handle", handle).Msg("Failed to split up account handle") - return nil, err - } - - acc := Account{} - res := s.db.Where("name = ?", name).Where("server = ?", server).First(&acc) - if res.Error != nil { - if errors.Is(res.Error, gorm.ErrRecordNotFound) { - log.Info().Str("account-handle", handle).Msg("Account with handle not found") - } else { - log.Error().Err(err).Str("account-handle", handle).Msg("Failed to get account with handle") - } - return nil, res.Error - } - log.Info().Str("account-handle", handle).Msg("Found account, also inserting into cache") - if err = s.cache.Set(cacheUserIdToAccPrefix+acc.ID, &acc); err != nil { - log.Warn(). - Err(err). - Str("account-handle", handle). - Msg("Found account but failed to insert into cache") - } - if err = s.cache.Set(cacheUserHandleToIdPrefix+strings.TrimLeft(handle, "@"), acc.ID); err != nil { - log.Warn(). - Err(err). - Str("account-handle", handle). - Msg("Failed to store handle to id in cache") - } - return &acc, nil -} - -// Find an account given a specific ID -func (s *Storage) FindAccountById(id string) (*Account, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Str("account-id", id).Msg("Looking for account by id") - log.Debug().Str("account-id", id).Msg("First trying to hit cache") - acc, err := s.cacheAccIdToData(id) - if err == nil { - log.Info().Str("account-id", id).Msg("Found account in cache") - return acc, nil - } else if !errors.Is(err, errCacheNotFound) { - log.Error().Err(err).Str("account-id", id).Msg("Error while looking for account in cache") - return nil, err - } - - log.Debug().Str("account-id", id).Msg("Didn't hit account in cache, checking db") - acc = &Account{} - res := s.db.Where(Account{ID: id}).First(acc) - if res.Error != nil { - if errors.Is(res.Error, gorm.ErrRecordNotFound) { - log.Warn().Str("account-id", id).Msg("Account not found") - return nil, ErrEntryNotFound - } else { - log.Error().Err(res.Error).Str("account-id", id).Msg("Failed to look for account") - return nil, res.Error - } - } - log.Info().Str("account-id", id).Msg("Found account in db, also adding to cache") - if err = s.cache.Set(cacheUserIdToAccPrefix+id, acc); err != nil { - log.Warn().Err(err).Str("account-id", id).Msg("Failed to add account to cache") - } - return acc, nil -} - -func (s *Storage) FindLocalAccountByUsername(username string) (*Account, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Str("account-username", username).Msg("Looking for local account") - log.Debug().Str("account-username", username).Msg("Checking cache first") - - // Try and find the account in cache first - cacheAccId, err := s.cacheLocalUsernameToAccUid(username) - if err == nil { - log.Info().Str("account-username", username).Msg("Hit account handle in cache") - // Then always load via id since unique key access should be faster than string matching - return s.FindAccountById(*cacheAccId) - } else { - if err != errCacheNotFound { - log.Error().Err(err).Str("account-username", username).Msg("Problem while checking cache for account") - return nil, err - } - } - - // Failed to find in cache, go the slow route of hitting the db - log.Debug().Str("account-username", username).Msg("Didn't hit account in cache, going to db") - - acc := Account{} - res := s.db.Where("username = ?", username). - Where("server_id = ?", serverSelf.ID). - First(&acc) - if res.Error != nil { - if errors.Is(res.Error, gorm.ErrRecordNotFound) { - log.Info(). - Str("account-username", username). - Msg("Local account with username not found") - } else { - log.Error().Err(err).Str("account-username", username).Msg("Failed to get local account with username") - } - return nil, ErrEntryNotFound - } - log.Info().Str("account-username", username).Msg("Found account, also inserting into cache") - if err = s.cache.Set(cacheUserIdToAccPrefix+acc.ID, &acc); err != nil { - log.Warn(). - Err(err). - Str("account-username", username). - Msg("Found account but failed to insert into cache") - } - if err = s.cache.Set(cacheLocalUsernameToIdPrefix+username, acc.ID); err != nil { - log.Warn(). - Err(err). - Str("account-username", username). - Msg("Failed to store local username to id in cache") - } - return &acc, nil -} - -func (s *Storage) FindAccountByPasskeyId(pkeyId []byte) (*Account, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Bytes("account-passkey-id", pkeyId).Msg("Looking for account") - log.Debug().Bytes("account-passkey-id", pkeyId).Msg("Checking cache first") - - // Try and find the account in cache first - cacheAccId, err := s.cachePkeyIdToAccId(pkeyId) - if err == nil { - log.Info().Bytes("account-passkey-id", pkeyId).Msg("Hit passkey id in cache") - // Then always load via id since unique key access should be faster than string matching - return s.FindAccountById(*cacheAccId) - } else { - if err != errCacheNotFound { - log.Error().Err(err).Bytes("account-passkey-id", pkeyId).Msg("Problem while checking cache for account") - return nil, err - } - } - - // Failed to find in cache, go the slow route of hitting the db - log.Debug().Bytes("account-passkey-id", pkeyId).Msg("Didn't hit account in cache, going to db") - - acc := Account{} - res := s.db.Where("web_authn_id = ?", pkeyId). - First(&acc) - if res.Error != nil { - if res.Error == gorm.ErrRecordNotFound { - log.Info(). - Bytes("account-passkey-id", pkeyId). - Msg("Local account with passkey id not found") - return nil, ErrEntryNotFound - } else { - log.Error().Err(res.Error).Bytes("account-passkey-id", pkeyId).Msg("Failed to get local account with passkey id") - return nil, res.Error - } - } - log.Info().Bytes("account-passkey-id", pkeyId).Msg("Found account, also inserting into cache") - // if err = s.cache.Set(cacheUserIdToAccPrefix+acc.ID, &acc); err != nil { - // log.Warn(). - // Err(err). - // Bytes("account-passkey-id", pkeyId). - // Msg("Found account but failed to insert into cache") - // } - // if err = s.cache.Set(cachePasskeyIdToAccIdPrefix+string(pkeyId), acc.ID); err != nil { - // log.Warn(). - // Err(err). - // Bytes("account-passkey-id", pkeyId). - // Msg("Failed to store local username to id in cache") - // } - return &acc, nil -} - -// Update a given account in storage and cache -func (s *Storage) UpdateAccount(acc *Account) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - // If the account is nil or doesn't have an id, error out - if acc == nil || acc.ID == "" { - return ErrInvalidData - } - res := s.db.Save(acc) - if res.Error != nil { - return res.Error - } - if err := s.cache.Set(cacheUserIdToAccPrefix+acc.ID, acc); err != nil { - return err - } - return nil -} - -// Create a new empty account for future use -func (s *Storage) NewEmptyAccount() (*Account, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Msg("Creating new empty account") - acc := Account{} - // Generate the 64 bit id for passkey and webauthn stuff - log.Debug().Msg("Creating webauthn id for new account") - data := make([]byte, 64) - c, err := rand.Read(data) - for err != nil || c != len(data) || c < 64 { - data = make([]byte, 64) - c, err = rand.Read(data) - } - log.Debug().Msg("Random webauthn id for new account created") - acc.ID = uuid.NewString() - - accountRole, err := s.NewEmptyRole(acc.ID) - if err != nil { - return nil, fmt.Errorf("failed to generate account role for new account: %w", err) - } - accountRole.IsUserRole = true - if err = s.UpdateRole(accountRole); err != nil { - return nil, fmt.Errorf("failed to generate account role for new account: %w", err) - } - - acc.WebAuthnId = data - acc.Relations = []uint{} - acc.Tags = []string{} - acc.Gender = []string{} - acc.CustomFields = []uint{} - acc.IdentifiesAs = []Being{} - acc.PasskeyCredentials = []webauthn.Credential{} - acc.Roles = []string{DefaultUserRole.Name, accountRole.Name} - acc.Icon = "placeholder" - log.Debug().Any("account", &acc).Msg("Saving new account in db") - res := s.db.Save(&acc) - if res.Error != nil { - log.Error().Err(res.Error).Msg("Failed to safe new account") - return nil, res.Error - } - log.Info().Str("account-id", acc.ID).Msg("Created new account") - return &acc, nil -} - -// Create a new local account using the given handle -// The handle in this case is only the part before the domain (example: @bob@example.com would have a handle of bob) -// It also sets up a bunch of values that tend to be obvious for local accounts -func (s *Storage) NewLocalAccount(handle string) (*Account, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Trace().Caller().Send() - log.Debug().Str("account-handle", handle).Msg("Creating new local account") - acc, err := s.NewEmptyAccount() - if err != nil { - log.Error().Err(err).Msg("Failed to create empty account for use") - return nil, err - } - acc.Username = handle - // acc.Server = serverSelf - acc.ServerId = serverSelf.ID - acc.DisplayName = handle - - publicKey, privateKey, err := ed25519.GenerateKey(nil) - if err != nil { - log.Error().Err(err).Msg("Failed to generate key pair for new local account") - return nil, err - } - acc.PrivateKey = privateKey - acc.PublicKey = publicKey - - log.Debug(). - Str("account-handle", handle). - Str("account-id", acc.ID). - Msg("Saving new local account") - res := s.db.Save(acc) - if res.Error != nil { - log.Error().Err(res.Error).Any("account-full", acc).Msg("Failed to save local account") - return nil, res.Error - } - log.Info(). - Str("account-handle", handle). - Str("account-id", acc.ID). - Msg("Created new local account") - return acc, nil -} - -func (s *Storage) DeleteAccount(accId string) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Delete(&Account{ID: accId}).Error -} - -// ---- Section WebAuthn.User -// Implements the webauthn.User interface for interaction with passkeys - -func (a *Account) WebAuthnID() []byte { - defer shared.Untrace(shared.Trace(&log.Logger)) - return a.WebAuthnId -} - -func (u *Account) WebAuthnName() string { - defer shared.Untrace(shared.Trace(&log.Logger)) - return u.Username -} - -func (u *Account) WebAuthnDisplayName() string { - defer shared.Untrace(shared.Trace(&log.Logger)) - return u.DisplayName -} - -func (u *Account) WebAuthnCredentials() []webauthn.Credential { - defer shared.Untrace(shared.Trace(&log.Logger)) - return u.PasskeyCredentials -} - -func (u *Account) WebAuthnIcon() string { - defer shared.Untrace(shared.Trace(&log.Logger)) - return "" -} - -// ---- Section passkey.User -// Implements the passkey.User interface - -func (u *Account) PutCredential(new webauthn.Credential) { - defer shared.Untrace(shared.Trace(&log.Logger)) - u.PasskeyCredentials = append(u.PasskeyCredentials, new) -} - -// Section passkey.UserStore -// Implements the passkey.UserStore interface - -func (s *Storage) GetOrCreateUser(userID string) passkey.User { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug(). - Str("account-handle", userID). - Msg("Looking for or creating account for passkey stuff") - acc := &Account{} - res := s.db.Where(Account{Username: userID, ServerId: serverSelf.ID}). - First(acc) - if errors.Is(res.Error, gorm.ErrRecordNotFound) { - log.Debug().Str("account-handle", userID) - var err error - acc, err = s.NewLocalAccount(userID) - if err != nil { - log.Error(). - Err(err). - Str("account-handle", userID). - Msg("Failed to create new account for webauthn request") - return nil - } - } - return acc -} - -func (s *Storage) GetUserByWebAuthnId(id []byte) passkey.User { - defer shared.Untrace(shared.Trace(&log.Logger)) - log.Debug().Bytes("webauthn-id", id).Msg("Looking for account with webauthn id") - acc := Account{} - res := s.db.Where(Account{WebAuthnId: id}).First(&acc) - if res.Error != nil { - log.Error(). - Err(res.Error). - Bytes("webauthn-id", id). - Msg("Failed to find user with webauthn ID") - return nil - } - log.Info().Msg("Found account with given webauthn id") - return &acc -} - -func (s *Storage) SaveUser(rawUser passkey.User) { - defer shared.Untrace(shared.Trace(&log.Logger)) - user, ok := rawUser.(*Account) - if !ok { - log.Error().Any("raw-user", rawUser).Msg("Failed to cast raw user to db account") - } - s.db.Save(user) -} diff --git a/storage/userIdentType.go b/storage/userIdentType.go deleted file mode 100644 index 9b48844..0000000 --- a/storage/userIdentType.go +++ /dev/null @@ -1,21 +0,0 @@ -package storage - -import "git.mstar.dev/mstar/goutils/sliceutils" - -// What kind of being a user identifies as -type Being string - -const ( - BEING_HUMAN = Being("human") - BEING_CAT = Being("cat") - BEING_FOX = Being("fox") - BEING_DOG = Being("dog") - BEING_ROBOT = Being("robot") - BEING_DOLL = Being("doll") -) - -var allBeings = []Being{BEING_HUMAN, BEING_CAT, BEING_FOX, BEING_DOG, BEING_ROBOT, BEING_DOLL} - -func IsValidBeing(toCheck string) bool { - return sliceutils.Contains(allBeings, Being(toCheck)) -} diff --git a/storage/userInfoFields.go b/storage/userInfoFields.go deleted file mode 100644 index 3c66b90..0000000 --- a/storage/userInfoFields.go +++ /dev/null @@ -1,71 +0,0 @@ -package storage - -import ( - "time" - - "github.com/rs/zerolog/log" - "gorm.io/gorm" - - "git.mstar.dev/mstar/linstrom/shared" -) - -// Describes a custom attribute field for accounts -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 - BelongsTo string // Id of account this info field belongs to -} - -// TODO: Add functions to store, load, update and delete these - -func (s *Storage) FindUserFieldById(id uint) (*UserInfoField, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - entry := UserInfoField{} - err := s.db.First(&entry, id).Error - switch err { - case nil: - return &entry, nil - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - default: - return nil, err - } -} - -func (s *Storage) FindMultipleUserFieldsById(ids []uint) ([]UserInfoField, error) { - defer shared.Untrace(shared.Trace(&log.Logger)) - entries := []UserInfoField{} - err := s.db.Where(ids).Find(&entries).Error - switch err { - case gorm.ErrRecordNotFound: - return nil, ErrEntryNotFound - case nil: - return entries, nil - default: - return nil, err - } -} - -func (s *Storage) AddNewUserField(name, value, belongsToId string) (*UserInfoField, error) { - // TODO: Implement me - panic("Not implemented") -} - -func (s *Storage) DeleteUserField(id uint) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Delete(UserInfoField{Model: gorm.Model{ID: id}}).Error -} - -func (s *Storage) DeleteAllUserFieldsForAccountId(id string) error { - defer shared.Untrace(shared.Trace(&log.Logger)) - return s.db.Model(&UserInfoField{}). - Where(&UserInfoField{BelongsTo: id}). - Delete(&UserInfoField{}). - Error -}