More API progress
This time mainly helper functions for converting an account and associated types into their API representation
This commit is contained in:
parent
873f52d64f
commit
a653477e7f
8 changed files with 201 additions and 7 deletions
4
go.mod
4
go.mod
|
@ -14,7 +14,9 @@ require (
|
||||||
github.com/gabriel-vasile/mimetype v1.4.5
|
github.com/gabriel-vasile/mimetype v1.4.5
|
||||||
github.com/gen2brain/avif v0.3.2
|
github.com/gen2brain/avif v0.3.2
|
||||||
github.com/go-webauthn/webauthn v0.11.2
|
github.com/go-webauthn/webauthn v0.11.2
|
||||||
|
github.com/google/jsonapi v1.0.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e
|
github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e
|
||||||
github.com/redis/go-redis/v9 v9.0.2
|
github.com/redis/go-redis/v9 v9.0.2
|
||||||
github.com/rs/zerolog v1.33.0
|
github.com/rs/zerolog v1.33.0
|
||||||
|
@ -55,8 +57,6 @@ require (
|
||||||
github.com/go-webauthn/x v0.1.14 // indirect
|
github.com/go-webauthn/x v0.1.14 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/google/go-tpm v0.9.1 // indirect
|
github.com/google/go-tpm v0.9.1 // indirect
|
||||||
github.com/google/jsonapi v1.0.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
|
|
|
@ -3,7 +3,10 @@ package server
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/jsonapi"
|
||||||
"github.com/rs/zerolog/hlog"
|
"github.com/rs/zerolog/hlog"
|
||||||
|
"gitlab.com/mstarongitlab/goutils/other"
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// No create account. That happens during passkey registration
|
// No create account. That happens during passkey registration
|
||||||
|
@ -11,8 +14,45 @@ import (
|
||||||
func linstromGetAccount(w http.ResponseWriter, r *http.Request) {
|
func linstromGetAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
store := StorageFromRequest(r)
|
store := StorageFromRequest(r)
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
|
accId := AccountIdFromRequest(r)
|
||||||
|
acc, err := store.FindAccountById(accId)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
// Ok, do nothing
|
||||||
|
case storage.ErrEntryNotFound:
|
||||||
|
other.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")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdDbFailure,
|
||||||
|
"Failed to get account from storage",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
// TODO: Check if caller is actually allowed to view the account requested.
|
||||||
|
|
||||||
|
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")
|
||||||
|
other.HttpErr(
|
||||||
|
w,
|
||||||
|
HttpErrIdConverionFailure,
|
||||||
|
"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) {}
|
func linstromUpdateAccount(w http.ResponseWriter, r *http.Request) {}
|
||||||
func linstromDeleteAccount(w http.ResponseWriter, r *http.Request) {}
|
func linstromDeleteAccount(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
|
||||||
|
|
109
server/apiLinstromTypeHelpers.go
Normal file
109
server/apiLinstromTypeHelpers.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/mstarongitlab/goutils/sliceutils"
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertAccountStorageToLinstrom(
|
||||||
|
acc *storage.Account,
|
||||||
|
store *storage.Storage,
|
||||||
|
) (*linstromAccount, error) {
|
||||||
|
storageServer, err := store.FindRemoteServerById(acc.ServerId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiServer, err := convertServerStorageToLinstrom(storageServer, store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
storageIcon, err := store.GetMediaMetadataById(acc.Icon)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
storageBanner, err := store.GetMediaMetadataById(acc.Banner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
storageFields, err := store.FindMultipleUserFieldsById(acc.CustomFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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),
|
||||||
|
Banner: convertMediaMetadataStorageToLinstrom(storageBanner),
|
||||||
|
FollowerIds: acc.Followers,
|
||||||
|
FollowingIds: acc.Follows,
|
||||||
|
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, 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ type linstromNote struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type linstromOriginServer struct {
|
type linstromOriginServer struct {
|
||||||
Id int `jsonapi:"primary,origins"`
|
Id uint `jsonapi:"primary,origins"`
|
||||||
CreatedAt time.Time `jsonapi:"attr,created_at"`
|
CreatedAt time.Time `jsonapi:"attr,created_at"`
|
||||||
UpdatedAt *time.Time `jsonapi:"attr,updated_at,omitempty"`
|
UpdatedAt *time.Time `jsonapi:"attr,updated_at,omitempty"`
|
||||||
ServerType string `jsonapi:"attr,server_type"` // one of "Linstrom", "Mastodon", "Plemora", "Misskey" or "Wafrn"
|
ServerType string `jsonapi:"attr,server_type"` // one of "Linstrom", "Mastodon", "Plemora", "Misskey" or "Wafrn"
|
||||||
|
|
|
@ -18,4 +18,5 @@ const (
|
||||||
HttpErrIdBadRequest
|
HttpErrIdBadRequest
|
||||||
HttpErrIdAlreadyExists
|
HttpErrIdAlreadyExists
|
||||||
HttpErrIdNotFound
|
HttpErrIdNotFound
|
||||||
|
HttpErrIdConverionFailure
|
||||||
)
|
)
|
||||||
|
|
|
@ -13,7 +13,7 @@ type RemoteServer struct {
|
||||||
IsSelf bool // Whether this server is yours truly
|
IsSelf bool // Whether this server is yours truly
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) FindRemoteServer(url string) (*RemoteServer, error) {
|
func (s *Storage) FindRemoteServerByDomain(url string) (*RemoteServer, error) {
|
||||||
server := RemoteServer{}
|
server := RemoteServer{}
|
||||||
err := s.db.Where("domain = ?").First(&server).Error
|
err := s.db.Where("domain = ?").First(&server).Error
|
||||||
switch err {
|
switch err {
|
||||||
|
@ -40,12 +40,25 @@ func (s *Storage) FindRemoteServerByDisplayName(displayName string) (*RemoteServ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Storage) FindRemoteServerById(id uint) (*RemoteServer, error) {
|
||||||
|
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
|
// Create a new remote server
|
||||||
func (s *Storage) NewRemoteServer(
|
func (s *Storage) NewRemoteServer(
|
||||||
url, displayName, icon string,
|
url, displayName, icon string,
|
||||||
serverType RemoteServerType,
|
serverType RemoteServerType,
|
||||||
) (*RemoteServer, error) {
|
) (*RemoteServer, error) {
|
||||||
_, err := s.FindRemoteServer(url)
|
_, err := s.FindRemoteServerByDomain(url)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
return nil, ErrEntryAlreadyExists
|
return nil, ErrEntryAlreadyExists
|
||||||
|
|
|
@ -39,8 +39,8 @@ type Account struct {
|
||||||
Description string // The description of a user account
|
Description string // The description of a user account
|
||||||
Tags []string `gorm:"serializer:json"` // Hashtags
|
Tags []string `gorm:"serializer:json"` // Hashtags
|
||||||
IsBot bool // Whether to mark this account as a script controlled one
|
IsBot bool // Whether to mark this account as a script controlled one
|
||||||
Follows []string `gorm:"serializer:json"` // List of handles this account follows
|
Follows []string `gorm:"serializer:json"` // List of account ids this account follows
|
||||||
Followers []string `gorm:"serializer:json"` // List of handles that follow this account
|
Followers []string `gorm:"serializer:json"` // List of account ids that follow this account
|
||||||
Icon string // ID of a media file used as icon
|
Icon string // ID of a media file used as icon
|
||||||
Background string // ID of a media file used as background image
|
Background string // ID of a media file used as background image
|
||||||
Banner string // ID of a media file used as banner
|
Banner string // ID of a media file used as banner
|
||||||
|
|
|
@ -20,3 +20,34 @@ type UserInfoField struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add functions to store, load, update and delete these
|
// TODO: Add functions to store, load, update and delete these
|
||||||
|
|
||||||
|
func (s *Storage) FindUserFieldById(id uint) (*UserInfoField, error) {
|
||||||
|
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) {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue