diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..4f13033 --- /dev/null +++ b/config/config.go @@ -0,0 +1,26 @@ +package config + +type ConfigSSL struct { + HandleSSL bool // Whether Linstrom should handle SSL encryption itself + // If Linstrom is to handle SSL, whether it should use LetsEncrypt for certificates + UseLetsEncrypt *bool + // Path to the certificate if Linstrom is to handle SSL while not using LetsEncrypt + CertificateFile *string + // Mail adress to use in case of using LetsEncrypt + AdminMail *string +} + +type ConfigGeneral struct { + Domain string // The domain this server operates under +} + +type ConfigAdmin struct { + Username string + PasswordHash string +} + +type Config struct { + General ConfigGeneral + SSL ConfigSSL + Admin ConfigAdmin +} diff --git a/storage/mediaFile.go b/storage/mediaFile.go index d6e8f3c..5ec406e 100644 --- a/storage/mediaFile.go +++ b/storage/mediaFile.go @@ -13,10 +13,21 @@ type MediaFile struct { 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 // Whether this media has been cached locally // Only really used for user and server icons, 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 LocallyCached bool } + +// Placeholder media file. Acts as placeholder for media file fields that have not been initialised yet but need a value +var placeholderMediaFile = &MediaFile{ + ID: "placeholder", + Remote: false, + Link: "placeholder", // TODO: Replace this with a file path to a staticly included image + Type: "image/png", + LocallyCached: true, +} diff --git a/storage/notes.go b/storage/notes.go index ef68a38..e8011eb 100644 --- a/storage/notes.go +++ b/storage/notes.go @@ -26,3 +26,19 @@ type Note struct { OriginServer string // Url of the origin server. Also the primary key for those Tags []string `gorm:"serializer:json"` // Hashtags } + +var placeholderNote = &Note{ + ID: "placeholder", + Creator: "placeholder", + Remote: false, + RawContent: "placeholder", + ContentWarning: nil, + Attachments: []string{}, + Emotes: []string{}, + RepliesTo: nil, + Quotes: nil, + Target: NOTE_TARGET_HOME, + Pings: []string{}, + OriginServer: "placeholder", + Tags: []string{}, +} diff --git a/storage/remoteServerInfo.go b/storage/remoteServerInfo.go index 8c5ceea..ab2791b 100644 --- a/storage/remoteServerInfo.go +++ b/storage/remoteServerInfo.go @@ -16,3 +16,11 @@ type RemoteServer struct { Icon string // ID of a media file IsSelf bool // Whether this server is yours truly } + +var placeholderServer = &RemoteServer{ + ID: "placeholder", + ServerType: REMOTE_SERVER_LINSTROM, + Name: "placeholder", + Icon: "placeholder", + IsSelf: false, +} diff --git a/storage/storage.go b/storage/storage.go index 636f815..12c92fd 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,23 +1,26 @@ package storage import ( + "fmt" + "github.com/glebarez/sqlite" "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 } +// Build a new storage using sqlite as database backend func NewStorageSqlite(filePath string) (*Storage, error) { db, err := gorm.Open(sqlite.Open(filePath)) if err != nil { return nil, err } - return &Storage{ - db: db, - }, nil + return storageFromEmptyDb(db) } func NewStoragePostgres(dbUrl string) (*Storage, error) { @@ -25,6 +28,35 @@ func NewStoragePostgres(dbUrl string) (*Storage, error) { if err != nil { return nil, err } + return storageFromEmptyDb(db) +} + +func storageFromEmptyDb(db *gorm.DB) (*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 + db.AutoMigrate( + placeholderMediaFile, + placeholderUser(), + placeholderNote, + placeholderServer, + ) + // Afterwards add the placeholder entries for each table. + // FirstOrCreate either creates a new entry or retrieves the first matching one + // We only care about the creation if there is none yet, so no need to carry the result over + if res := db.FirstOrCreate(placeholderMediaFile); res.Error != nil { + return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) + } + if res := db.FirstOrCreate(placeholderUser()); res.Error != nil { + return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) + } + if res := db.FirstOrCreate(placeholderNote); res.Error != nil { + return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) + } + if res := db.FirstOrCreate(placeholderServer); res.Error != nil { + return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) + } + + // And finally, build the actual storage struct return &Storage{ db: db, }, nil diff --git a/storage/user.go b/storage/user.go index ad4dd72..73348e0 100644 --- a/storage/user.go +++ b/storage/user.go @@ -12,7 +12,8 @@ import ( // This can be a bot, remote or not // If remote, this is used for caching the account type User struct { - ID string `gorm:"primarykey"` // ID is the full handle, eg @max@example.com + ID string `gorm:"primarykey"` // ID is a uuid for this account + Handle string // Handle is the full handle, eg @max@example.com CreatedAt time.Time // When this entry was created UpdatedAt time.Time // When this account was last updated. Will also be used for refreshing remote accounts DeletedAt gorm.DeletedAt `gorm:"index"` @@ -29,7 +30,7 @@ type User struct { 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 - PublicKeyPem string // The public key of the account + PublicKeyPem *string // The public key of the account // Whether this account restricts following // If true, the owner must approve of a follow request first RestrictedFollow bool @@ -58,10 +59,34 @@ type User struct { func NewEmptyUser() *User { return &User{ - ID: uuid.NewString(), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Remote: false, - Server: "placeholder", + ID: uuid.NewString(), + Handle: "placeholder", + Remote: false, + Server: "placeholder", + DisplayName: "placeholder", + CustomFields: []uint{}, + Description: "placeholder", + Tags: []string{}, + IsBot: true, + Follows: []string{}, + Followers: []string{}, + Icon: "placeholder", + Background: "placeholder", + Banner: "placeholder", + Indexable: false, + PublicKeyPem: nil, + RestrictedFollow: false, + IdentifiesAs: []Being{BEING_ROBOT}, + Gender: []string{"it", "its"}, + PasswordHash: []byte("placeholder"), + TotpToken: []byte("placeholder"), + Passkeys: map[string]webauthn.Credential{}, + PrivateKeyPem: nil, } } + +func placeholderUser() *User { + tmp := NewEmptyUser() + tmp.ID = "placeholder" + return tmp +}