More work on the API. Primarely defining jsonapi types
This commit is contained in:
parent
4f4d05a335
commit
0ed50aca60
13 changed files with 165 additions and 48 deletions
|
@ -31,6 +31,14 @@ func setupLinstromApiV1Router() http.Handler {
|
||||||
// Pinning
|
// Pinning
|
||||||
router.HandleFunc("POST /note/{noteId}/pin", linstromPinNote)
|
router.HandleFunc("POST /note/{noteId}/pin", linstromPinNote)
|
||||||
router.HandleFunc("DELETE /note/{noteId}/pin", linstromUnpinNote)
|
router.HandleFunc("DELETE /note/{noteId}/pin", linstromUnpinNote)
|
||||||
|
// Reports
|
||||||
|
router.HandleFunc("POST /note/{noteId}/report", linstromReportNote)
|
||||||
|
router.HandleFunc("DELETE /note/{noteId}/report", linstromRetractReportNote)
|
||||||
|
// Admin
|
||||||
|
router.HandleFunc("POST /note/{noteId}/admin/cw", linstromForceCWNote)
|
||||||
|
|
||||||
|
// Event streams
|
||||||
|
router.HandleFunc("/streams", linstromEventStream)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// Notes
|
// Notes
|
||||||
func linstromGetNote(w http.ResponseWriter, r *http.Request) {}
|
func linstromGetNote(w http.ResponseWriter, r *http.Request) {
|
||||||
|
store := StorageFromRequest(r)
|
||||||
|
noteId := NoteIdFromRequest(r)
|
||||||
|
note, err := store.FindNoteById(noteId)
|
||||||
|
}
|
||||||
func linstromUpdateNote(w http.ResponseWriter, r *http.Request) {}
|
func linstromUpdateNote(w http.ResponseWriter, r *http.Request) {}
|
||||||
func linstromNewNote(w http.ResponseWriter, r *http.Request) {}
|
func linstromNewNote(w http.ResponseWriter, r *http.Request) {}
|
||||||
func linstromDeleteNote(w http.ResponseWriter, r *http.Request) {}
|
func linstromDeleteNote(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
@ -25,5 +31,15 @@ func linstromAddQuote(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
|
||||||
// No delete quote since quotes are their own notes with an extra attribute
|
// No delete quote since quotes are their own notes with an extra attribute
|
||||||
|
|
||||||
|
// Pinning
|
||||||
func linstromPinNote(w http.ResponseWriter, r *http.Request) {}
|
func linstromPinNote(w http.ResponseWriter, r *http.Request) {}
|
||||||
func linstromUnpinNote(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) {}
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/rs/zerolog/hlog"
|
||||||
|
)
|
||||||
|
|
||||||
// TODO: Decide where to put data stream handlers
|
// TODO: Decide where to put data stream handlers
|
||||||
|
|
||||||
|
var websocketUpgrader = websocket.Upgrader{}
|
||||||
|
|
||||||
// Entrypoint for a new stream will be in here at least
|
// 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)
|
||||||
|
}
|
||||||
|
|
81
server/apiLinstromTypes.go
Normal file
81
server/apiLinstromTypes.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
// Contains types used by the Linstrom API. Types comply with the jsonapi spec
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type linstromOriginServer struct {
|
||||||
|
Id int `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", ""
|
||||||
|
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,medias"`
|
||||||
|
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"`
|
||||||
|
Banner *linstromMediaMetadata `jsonapi:"relation,banner"`
|
||||||
|
FollowerIds []string `jsonapi:"attr,follows_ids"`
|
||||||
|
FollowingIds []string `jsonapi:"attr,following_ids"`
|
||||||
|
Indexable bool `jsonapi:"attr,indexable"`
|
||||||
|
RestrictedFollow bool `jsonapi:"attr,restricted_follow"`
|
||||||
|
IdentifiesAs []string `jsonapi:"attr,identifies_as"`
|
||||||
|
Pronouns []string `jsonapi:"attr,pronouns"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type linstromCustomAccountField struct {
|
||||||
|
Id uint
|
||||||
|
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"`
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"gitlab.com/mstarongitlab/goutils/other"
|
|
||||||
"gitlab.com/mstarongitlab/linstrom/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StorageFromRequest(w http.ResponseWriter, r *http.Request) *storage.Storage {
|
|
||||||
store, ok := r.Context().Value(ContextKeyStorage).(*storage.Storage)
|
|
||||||
if !ok {
|
|
||||||
other.HttpErr(
|
|
||||||
w,
|
|
||||||
HttpErrIdMissingContextValue,
|
|
||||||
"Missing storage reference",
|
|
||||||
http.StatusInternalServerError,
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return store
|
|
||||||
}
|
|
|
@ -18,16 +18,15 @@ func placeholderEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStorageFromRequest(w http.ResponseWriter, r *http.Request) *storage.Storage {
|
func StorageFromRequest(r *http.Request) *storage.Storage {
|
||||||
store, ok := r.Context().Value(ContextKeyStorage).(*storage.Storage)
|
store, ok := r.Context().Value(ContextKeyStorage).(*storage.Storage)
|
||||||
if !ok {
|
if !ok {
|
||||||
other.HttpErr(
|
hlog.FromRequest(r).Fatal().Msg("Failed to get storage reference from context")
|
||||||
w,
|
|
||||||
HttpErrIdMissingContextValue,
|
|
||||||
"Missing storage in context",
|
|
||||||
http.StatusInternalServerError,
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NoteIdFromRequest(r *http.Request) string {
|
||||||
|
return r.PathValue("noteId")
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,10 @@ type MediaMetadata struct {
|
||||||
// Descriptive name for a media file
|
// Descriptive name for a media file
|
||||||
// Emote name for example or servername.filetype for a server's icon
|
// Emote name for example or servername.filetype for a server's icon
|
||||||
Name string
|
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(location, mediaType, name string) (*MediaMetadata, error) {
|
func (s *Storage) NewMediaMetadata(location, mediaType, name string) (*MediaMetadata, error) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
var ErrUnknownImageType = errors.New("unknown image format")
|
var ErrUnknownImageType = errors.New("unknown image format")
|
||||||
|
|
||||||
func Compress(dataReader io.Reader, mimeType *string) (io.Reader, error) {
|
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)
|
data, err := io.ReadAll(dataReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -51,7 +52,11 @@ func compressVideo(dataIn []byte, subType string) (dataOut []byte, err error) {
|
||||||
panic("Implement me")
|
panic("Implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func compressImage(dataIn []byte, subType string, maxSizeX, maxSizeY uint) (dataOut []byte, err error) {
|
func compressImage(
|
||||||
|
dataIn []byte,
|
||||||
|
subType string,
|
||||||
|
maxSizeX, maxSizeY uint,
|
||||||
|
) (dataOut []byte, err error) {
|
||||||
imageSize := image.Rect(0, 0, int(maxSizeX), int(maxSizeY))
|
imageSize := image.Rect(0, 0, int(maxSizeX), int(maxSizeY))
|
||||||
dst := image.NewRGBA(imageSize)
|
dst := image.NewRGBA(imageSize)
|
||||||
var sourceImage image.Image
|
var sourceImage image.Image
|
||||||
|
|
|
@ -10,15 +10,15 @@ import (
|
||||||
//go:generate stringer -type NoteTarget
|
//go:generate stringer -type NoteTarget
|
||||||
|
|
||||||
// What feed a note is targeting (public, home, followers or dm)
|
// What feed a note is targeting (public, home, followers or dm)
|
||||||
type NoteTarget uint8
|
type NoteAccessLevel uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The note is intended for the public
|
// The note is intended for the public
|
||||||
NOTE_TARGET_PUBLIC NoteTarget = 0
|
NOTE_TARGET_PUBLIC NoteAccessLevel = 0
|
||||||
// The note is intended only for the home screen
|
// The note is intended only for the home screen
|
||||||
// not really any idea what the difference is compared to public
|
// 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
|
// 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 NoteAccessLevel = 1 << iota
|
||||||
// The note is intended only for followers
|
// The note is intended only for followers
|
||||||
NOTE_TARGET_FOLLOWERS
|
NOTE_TARGET_FOLLOWERS
|
||||||
// The note is intended only for a DM to one or more targets
|
// The note is intended only for a DM to one or more targets
|
||||||
|
@ -26,16 +26,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Converts the NoteTarget value into a type the DB can use
|
// Converts the NoteTarget value into a type the DB can use
|
||||||
func (n *NoteTarget) Value() (driver.Value, error) {
|
func (n *NoteAccessLevel) Value() (driver.Value, error) {
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts the raw value from the DB into a NoteTarget
|
// Converts the raw value from the DB into a NoteTarget
|
||||||
func (n *NoteTarget) Scan(value any) error {
|
func (n *NoteAccessLevel) Scan(value any) error {
|
||||||
vBig, ok := value.(int64)
|
vBig, ok := value.(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("not an int64")
|
return errors.New("not an int64")
|
||||||
}
|
}
|
||||||
*n = NoteTarget(vBig)
|
*n = NoteAccessLevel(vBig)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,15 +28,15 @@ type Note struct {
|
||||||
// Raw content of the note. So without additional formatting applied
|
// Raw content of the note. So without additional formatting applied
|
||||||
// Might already have formatting applied beforehand from the origin server
|
// Might already have formatting applied beforehand from the origin server
|
||||||
RawContent string
|
RawContent string
|
||||||
ContentWarning *string // Content warnings of the note, if it contains any
|
ContentWarning *string // Content warnings of the note, if it contains any
|
||||||
Attachments []string `gorm:"serializer:json"` // Links to attachments
|
Attachments []string `gorm:"serializer:json"` // List of Ids for mediaFiles
|
||||||
Emotes []string `gorm:"serializer:json"` // Emotes used in that message
|
Emotes []string `gorm:"serializer:json"` // Emotes used in that message
|
||||||
RepliesTo *string // Url of the message this replies to
|
RepliesTo *string // Url of the message this replies to
|
||||||
Quotes *string // url of the message this note quotes
|
Quotes *string // url of the message this note quotes
|
||||||
Target NoteTarget // Where to send this message to (public, home, followers, dm)
|
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
|
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
|
OriginServer string // Url of the origin server. Also the primary key for those
|
||||||
Tags []string `gorm:"serializer:json"` // Hashtags
|
Tags []string `gorm:"serializer:json"` // Hashtags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) FindNoteById(id string) (*Note, error) {
|
func (s *Storage) FindNoteById(id string) (*Note, error) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ const (
|
||||||
_NoteTarget_name_3 = "NOTE_TARGET_DM"
|
_NoteTarget_name_3 = "NOTE_TARGET_DM"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i NoteTarget) String() string {
|
func (i NoteAccessLevel) String() string {
|
||||||
switch {
|
switch {
|
||||||
case i == 0:
|
case i == 0:
|
||||||
return _NoteTarget_name_0
|
return _NoteTarget_name_0
|
||||||
|
|
|
@ -19,6 +19,8 @@ const (
|
||||||
REMOTE_SERVER_MISSKEY = RemoteServerType("Misskey")
|
REMOTE_SERVER_MISSKEY = RemoteServerType("Misskey")
|
||||||
// Includes Akkoma
|
// Includes Akkoma
|
||||||
REMOTE_SERVER_PLEMORA = RemoteServerType("Plemora")
|
REMOTE_SERVER_PLEMORA = RemoteServerType("Plemora")
|
||||||
|
// Wafrn is a new entry
|
||||||
|
REMOTE_SERVER_WAFRN = RemoteServerType("Wafrn")
|
||||||
// And of course, yours truly
|
// And of course, yours truly
|
||||||
REMOTE_SERVER_LINSTROM = RemoteServerType("Linstrom")
|
REMOTE_SERVER_LINSTROM = RemoteServerType("Linstrom")
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,11 @@ type UserInfoField struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
LastUrlCheckDate *time.Time // Used if the value is an url to somewhere. Empty if value is not an url
|
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
|
// TODO: Add functions to store, load, update and delete these
|
||||||
|
|
Loading…
Reference in a new issue