Moew weork done
This commit is contained in:
parent
814316ab1e
commit
07d98d1ef5
10 changed files with 147 additions and 16 deletions
1
outgoingEventQueue/queue.go
Normal file
1
outgoingEventQueue/queue.go
Normal file
|
@ -0,0 +1 @@
|
|||
package outgoingeventqueue
|
|
@ -7,15 +7,21 @@ import (
|
|||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// various prefixes for accessing items in the cache (since it's a simple key-value store)
|
||||
const (
|
||||
cacheUserHandleToIdPrefix = "acc-name-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
|
||||
func (s *Storage) cacheHandleToAccUid(handle string) (*string, error) {
|
||||
// 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) {
|
||||
// Where to put the data (in case it's found)
|
||||
var target string
|
||||
found, err := s.cache.Get(cacheUserHandleToIdPrefix+strings.TrimLeft(handle, "@"), &target)
|
||||
|
@ -33,7 +39,10 @@ func (s *Storage) cacheHandleToAccUid(handle string) (*string, error) {
|
|||
}
|
||||
|
||||
// Find an account's data in cache using a given account id
|
||||
func (s *Storage) cacheAccIdToData(id string) (*Account, error) {
|
||||
// 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) {
|
||||
var target Account
|
||||
found, err := s.cache.Get(cacheUserIdToAccPrefix+id, &target)
|
||||
if !found {
|
||||
|
@ -45,3 +54,20 @@ func (s *Storage) cacheAccIdToData(id string) (*Account, error) {
|
|||
}
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package storage
|
||||
|
||||
type NotImplementedError struct{}
|
||||
import "errors"
|
||||
|
||||
func (n NotImplementedError) Error() string {
|
||||
type ErrNotImplemented struct{}
|
||||
|
||||
func (n ErrNotImplemented) Error() string {
|
||||
return "Not implemented yet"
|
||||
}
|
||||
|
||||
var ErrEntryNotFound = errors.New("entry not found")
|
||||
|
|
|
@ -6,6 +6,10 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MediaFile represents a file containing some media
|
||||
// This media may be stored locally via a file system or S3 bucket
|
||||
// or remote on a different server
|
||||
// Additionally, it contains some useful data for more easily working with it
|
||||
type MediaFile struct {
|
||||
ID string `gorm:"primarykey"` // The unique ID of this media file
|
||||
CreatedAt time.Time // When this entry was created
|
||||
|
@ -15,13 +19,18 @@ type MediaFile struct {
|
|||
// If not null, this entry is marked as deleted
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
Remote bool // whether the attachment is a remote one
|
||||
Link string // url if remote attachment, identifier if local
|
||||
Type string // What media type this is, eg image/png
|
||||
// Always an url, either an absolute path to a local file or an url to a remote file
|
||||
Link string
|
||||
Type string // What media type this is following mime types, eg image/png
|
||||
// Whether this media has been cached locally
|
||||
// Only really used for user and server icons, not attachments
|
||||
// Used for user and server icons as well as emotes, not attachments
|
||||
// If true, Link will be read as file path. url otherwise
|
||||
// Reason: Attachments would take way to much space considering that they are often only loaded a few times at most
|
||||
// And caching a file for those few times would be a waste of storage
|
||||
// Caching user and server icons locally however should reduce burden on remote servers by quite a bit though
|
||||
// TODO: Decide later during usage if attachment caching would be a good idea
|
||||
LocallyCached bool
|
||||
// Descriptive name for a media file
|
||||
// Emote name for example or servername.filetype for a server's icon
|
||||
Name string
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@ import (
|
|||
"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 NoteTarget
|
||||
|
||||
// What feed a note is targeting (public, home, followers or dm)
|
||||
type NoteTarget uint8
|
||||
|
||||
|
@ -14,7 +18,7 @@ const (
|
|||
// 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 = NoteTarget(1 << iota)
|
||||
NOTE_TARGET_HOME = NoteTarget(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
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
@ -31,23 +39,85 @@ type Note struct {
|
|||
}
|
||||
|
||||
func (s *Storage) FindNoteById(id string) (*Note, error) {
|
||||
// TODO: Implement me
|
||||
panic("not implemented")
|
||||
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) {
|
||||
// TODO: Implement me
|
||||
panic("not implemented")
|
||||
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) {
|
||||
// TODO: Implement me
|
||||
panic("not implemented")
|
||||
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) {
|
||||
// TODO: Implement me
|
||||
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 {
|
||||
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) CreateNote() (*Note, error) {
|
||||
// TODO: Think of good arguments and implement me
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Update, create, delete
|
||||
func (s *Storage) DeleteNote(id string) {
|
||||
s.cache.Delete(cacheNoteIdToNotePrefix + id)
|
||||
s.db.Delete(Note{ID: id})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
|
@ -13,12 +16,15 @@ type PasskeySession struct {
|
|||
|
||||
// ---- Section SessionStore
|
||||
|
||||
// Generate some id for a new session. Just returns a new uuid
|
||||
func (s *Storage) GenSessionID() (string, error) {
|
||||
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) {
|
||||
log.Debug().Str("id", sessionId).Msg("Looking for passkey session")
|
||||
session := PasskeySession{}
|
||||
|
@ -30,6 +36,7 @@ func (s *Storage) GetSession(sessionId string) (*webauthn.SessionData, bool) {
|
|||
return &session.Data, true
|
||||
}
|
||||
|
||||
// Save (or update) a session with the new data
|
||||
func (s *Storage) SaveSession(token string, data *webauthn.SessionData) {
|
||||
log.Debug().Str("id", token).Any("webauthn-data", data).Msg("Saving passkey session")
|
||||
session := PasskeySession{
|
||||
|
@ -39,6 +46,8 @@ func (s *Storage) SaveSession(token string, data *webauthn.SessionData) {
|
|||
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) {
|
||||
log.Debug().Str("id", token).Msg("Deleting passkey session (if one exists)")
|
||||
s.db.Delete(&PasskeySession{ID: token})
|
||||
|
|
|
@ -5,6 +5,9 @@ import (
|
|||
"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
|
||||
|
|
|
@ -191,6 +191,7 @@ func (s *Storage) UpdateAccount(acc *Account) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Create a new empty account for future use
|
||||
func (s *Storage) NewEmptyAccount() (*Account, error) {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Msg("Creating new empty account")
|
||||
|
@ -219,6 +220,9 @@ func (s *Storage) NewEmptyAccount() (*Account, error) {
|
|||
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) {
|
||||
log.Trace().Caller().Send()
|
||||
log.Debug().Str("account-handle", handle).Msg("Creating new local account")
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
|
Loading…
Reference in a new issue