Private
Public Access
1
0
Files
Kordophone/server/server.go

324 lines
7.7 KiB
Go
Raw Normal View History

package server
import (
2024-04-07 20:22:38 -07:00
"io"
"os"
"path"
2023-06-19 18:31:05 -07:00
"sort"
"code.severnaya.net/kordophone-mock/v2/data"
"code.severnaya.net/kordophone-mock/v2/model"
2023-06-23 00:44:25 -07:00
"github.com/rs/zerolog/log"
)
2023-12-02 22:23:41 -08:00
const VERSION = "KordophoneMock-2.6"
2023-06-16 23:38:48 -07:00
const (
AUTH_USERNAME = "test"
AUTH_PASSWORD = "test"
)
type Server struct {
2024-04-07 20:22:38 -07:00
version string
conversations []model.Conversation
authTokens []model.AuthToken
attachmentStore model.AttachmentStore
messageStore map[string][]model.Message
updateItems map[int]model.UpdateItem
updateChannels []chan []model.UpdateItem
updateItemSeq int
}
type MessagesQuery struct {
ConversationGUID string
2024-01-05 16:26:19 -08:00
BeforeDate *model.Date
AfterGUID *string
BeforeGUID *string
Limit *int
}
type AuthError struct {
message string
}
func (e *AuthError) Error() string {
return e.message
}
2023-06-19 18:31:05 -07:00
type DatabaseError struct {
message string
}
func (e *DatabaseError) Error() string {
return e.message
}
func NewServer() *Server {
2024-04-07 20:22:38 -07:00
attachmentStorePath := path.Join(os.TempDir(), "kpmock", "attachments")
return &Server{
2024-04-07 20:22:38 -07:00
version: VERSION,
conversations: []model.Conversation{},
authTokens: []model.AuthToken{},
attachmentStore: model.NewAttachmentStore(attachmentStorePath),
messageStore: make(map[string][]model.Message),
updateItems: make(map[int]model.UpdateItem),
updateChannels: []chan []model.UpdateItem{},
updateItemSeq: 0,
}
}
func (s *Server) Version() string {
return s.version
}
func (s *Server) Conversations() []model.Conversation {
return s.conversations
}
2023-06-23 00:32:17 -07:00
func (s *Server) SortedConversations() []model.Conversation {
conversations := s.Conversations()
sort.Slice(conversations, func(i, j int) bool {
return conversations[i].Date.After(conversations[j].Date)
})
return conversations
}
2023-06-19 18:31:05 -07:00
func (s *Server) ConversationForGUID(guid string) (*model.Conversation, error) {
var conversation *model.Conversation = nil
2023-06-23 00:32:17 -07:00
for i := range s.conversations {
c := &s.conversations[i]
2023-06-19 18:31:05 -07:00
if c.Guid == guid {
2023-06-23 00:32:17 -07:00
conversation = c
2023-06-19 18:31:05 -07:00
break
}
}
if conversation != nil {
return conversation, nil
}
return nil, &DatabaseError{message: "Conversation not found"}
}
func (s *Server) AddConversation(c model.Conversation) {
s.conversations = append(s.conversations, c)
}
func (s *Server) PopulateWithTestData() {
numConversations := 100
cs := make([]model.Conversation, numConversations)
for i := 0; i < numConversations; i++ {
cs[i] = data.GenerateRandomConversation()
2023-06-19 18:31:05 -07:00
// Generate messages
convo := &cs[i]
var lastMessage model.Message
for i := 0; i < 100; i++ {
message := data.GenerateRandomMessage(convo.Participants)
s.AppendMessageToConversation(convo, message)
if lastMessage.Date.Before(message.Date) {
lastMessage = message
}
}
// Update last message
convo.LastMessage = lastMessage
2023-06-19 18:31:05 -07:00
convo.LastMessagePreview = lastMessage.Text
}
s.conversations = cs
}
func (s *Server) Authenticate(username string, password string) (*model.AuthToken, error) {
if username != AUTH_USERNAME || password != AUTH_PASSWORD {
return nil, &AuthError{"Invalid username or password"}
}
token, err := model.NewAuthToken(username)
if err != nil {
return nil, err
}
// Register for future auth
s.registerAuthToken(token)
return token, nil
}
func (s *Server) CheckBearerToken(token string) bool {
return s.authenticateToken(token)
}
func (s *Server) PerformMessageQuery(query *MessagesQuery) []model.Message {
messages := s.messageStore[query.ConversationGUID]
// Sort
2023-06-19 18:31:05 -07:00
sort.Slice(messages, func(i int, j int) bool {
return messages[i].Date.Before(messages[j].Date)
})
// Apply before/after filters
// The following code assumes the messages are sorted by date ascending
if query.BeforeGUID != nil {
beforeGUID := *query.BeforeGUID
for i := range messages {
if messages[i].Guid == beforeGUID {
messages = messages[:i]
break
}
}
} else if query.AfterGUID != nil {
afterGUID := *query.AfterGUID
for i := range messages {
if messages[i].Guid == afterGUID {
messages = messages[i+1:]
break
}
}
} else if query.BeforeDate != nil {
beforeDate := *query.BeforeDate
for i := range messages {
if messages[i].Date.Before(beforeDate) {
messages = messages[:i]
break
}
}
}
// Limit
if query.Limit != nil {
limit := *query.Limit
if len(messages) > limit {
messages = messages[len(messages)-limit:]
}
}
2023-06-19 18:31:05 -07:00
return messages
}
func (s *Server) MessagesForConversation(conversation *model.Conversation) []model.Message {
return s.PerformMessageQuery(&MessagesQuery{
ConversationGUID: conversation.Guid,
})
}
2023-06-19 18:31:05 -07:00
func (s *Server) AppendMessageToConversation(conversation *model.Conversation, message model.Message) {
s.messageStore[conversation.Guid] = append(s.messageStore[conversation.Guid], message)
}
2023-06-23 00:44:25 -07:00
func (s *Server) SendMessage(conversation *model.Conversation, message model.Message) {
s.AppendMessageToConversation(conversation, message)
2023-07-19 12:10:25 -06:00
// Update Conversation
ourConversation, _ := s.ConversationForGUID(conversation.Guid)
ourConversation.LastMessagePreview = message.Text
ourConversation.Date = message.Date
2023-06-23 00:44:25 -07:00
log.Info().EmbedObject(message).Msgf("Sent message to conversation %s", conversation.Guid)
}
2023-06-23 00:56:06 -07:00
func (s *Server) ReceiveMessage(conversation *model.Conversation, message model.Message) {
s.AppendMessageToConversation(conversation, message)
2023-07-19 12:10:25 -06:00
// Update conversation
ourConversation, _ := s.ConversationForGUID(conversation.Guid)
ourConversation.LastMessagePreview = message.Text
ourConversation.Date = message.Date
ourConversation.UnreadCount += 1
2023-06-23 00:56:06 -07:00
2023-07-19 11:58:13 -06:00
// Enqueue Update
s.EnqueueUpdateItem(model.UpdateItem{
2023-07-19 12:10:25 -06:00
Conversation: ourConversation,
2023-07-19 11:58:13 -06:00
Message: &message,
})
2023-06-23 00:56:06 -07:00
log.Info().EmbedObject(message).Msgf("Received message from conversation %s", conversation.Guid)
}
2023-07-19 11:58:13 -06:00
func (s *Server) EnqueueUpdateItem(item model.UpdateItem) {
log.Info().EmbedObject(&item).Msg("Enqueuing update item")
s.updateItemSeq += 1
item.MessageSequenceNumber = s.updateItemSeq
s.updateItems[s.updateItemSeq] = item
// Publish to channel
for i := range s.updateChannels {
s.updateChannels[i] <- []model.UpdateItem{item}
2023-07-19 11:58:13 -06:00
}
}
func (s *Server) FetchUpdates(since int) []model.UpdateItem {
items := []model.UpdateItem{}
for i := since; i <= s.updateItemSeq; i++ {
if val, ok := s.updateItems[i]; ok {
items = append(items, val)
}
}
return items
}
func (s *Server) FetchUpdatesBlocking(since int) []model.UpdateItem {
if since < 0 || since >= s.updateItemSeq {
// Wait for updates
log.Info().Msgf("Waiting for updates since %d", since)
updateChannel := make(chan []model.UpdateItem)
s.updateChannels = append(s.updateChannels, updateChannel)
items := <-updateChannel
// Remove channel
for i := range s.updateChannels {
if s.updateChannels[i] == updateChannel {
s.updateChannels = append(s.updateChannels[:i], s.updateChannels[i+1:]...)
break
}
}
2023-07-19 11:58:13 -06:00
return items
} else {
return s.FetchUpdates(since)
}
}
2023-07-19 12:10:25 -06:00
func (s *Server) MarkConversationAsRead(conversation *model.Conversation) {
conversation.UnreadCount = 0
// enqueue update
s.EnqueueUpdateItem(model.UpdateItem{
Conversation: conversation,
})
}
2024-04-07 20:22:38 -07:00
func (s *Server) FetchAttachment(guid string) (io.Reader, error) {
return s.attachmentStore.FetchAttachment(guid)
}
func (s *Server) UploadAttachment(filename string, reader io.Reader) (*string, error) {
return s.attachmentStore.StoreAttachment(filename, reader)
}
func (s *Server) DeleteAttachment(guid string) error {
return s.attachmentStore.DeleteAttachment(guid)
}
// Private
func (s *Server) registerAuthToken(token *model.AuthToken) {
s.authTokens = append(s.authTokens, *token)
}
func (s *Server) authenticateToken(token string) bool {
for _, t := range s.authTokens {
if t.SignedToken == token {
return true
}
}
return false
}