3
0

Serve attachment info alongside with message (rest & ws)

This commit is contained in:
Denis Arh
2018-09-09 13:09:56 +02:00
parent 0400451823
commit e351c73a0a
11 changed files with 204 additions and 61 deletions
+16 -18
View File
@@ -2,6 +2,7 @@ package repository
import (
"github.com/crusttech/crust/sam/types"
"github.com/jmoiron/sqlx"
"github.com/titpetric/factory"
"time"
)
@@ -9,6 +10,7 @@ import (
type (
Attachment interface {
FindAttachmentByID(id uint64) (*types.Attachment, error)
FindAttachmentByMessageID(IDs ...uint64) (types.MessageAttachmentSet, error)
CreateAttachment(mod *types.Attachment) (*types.Attachment, error)
DeleteAttachmentByID(id uint64) error
BindAttachment(attachmentId, messageId uint64) error
@@ -30,27 +32,23 @@ func (r *repository) FindAttachmentByID(id uint64) (*types.Attachment, error) {
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrAttachmentNotFound)
}
func (r *repository) FindAttachmentByMessageID(id uint64) (*types.Attachment, error) {
sql := "SELECT a.* " +
" FROM attachments AS a" +
" INNER JOIN message_attachment AS ma ON a.id = ma.rel_attachment " +
" WHERE ma.rel_message = ? AND " + sqlAttachmentScope
mod := &types.Attachment{}
func (r *repository) FindAttachmentByMessageID(IDs ...uint64) (rval types.MessageAttachmentSet, err error) {
rval = make([]*types.MessageAttachment, 0)
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrAttachmentNotFound)
}
if len(IDs) == 0 {
return
}
func (r *repository) FindAttachmentByRange(channelID, fromAttachmentID, toAttachmentID uint64) ([]*types.Attachment, error) {
rval := make([]*types.Attachment, 0)
sql := `SELECT a.*, rel_message
FROM attachments AS a
INNER JOIN message_attachment AS ma ON a.id = ma.rel_attachment
WHERE ma.rel_message IN (?) AND ` + sqlAttachmentScope
sql := `
SELECT *
FROM attachments
WHERE id BETWEEN ? AND ?
AND rel_channel = ?
AND deleted_at IS NULL`
return rval, r.db().Select(&rval, sql, fromAttachmentID, toAttachmentID, channelID)
if sql, args, err := sqlx.In(sql, IDs); err != nil {
return nil, err
} else {
return rval, r.db().Select(&rval, sql, args...)
}
}
func (r *repository) CreateAttachment(mod *types.Attachment) (*types.Attachment, error) {
+3 -3
View File
@@ -9,7 +9,7 @@ import (
type (
Message interface {
FindMessageByID(id uint64) (*types.Message, error)
FindMessages(filter *types.MessageFilter) ([]*types.Message, error)
FindMessages(filter *types.MessageFilter) (types.MessageSet, error)
CreateMessage(mod *types.Message) (*types.Message, error)
UpdateMessage(mod *types.Message) (*types.Message, error)
DeleteMessageByID(id uint64) error
@@ -41,9 +41,9 @@ func (r *repository) FindMessageByID(id uint64) (*types.Message, error) {
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrMessageNotFound)
}
func (r *repository) FindMessages(filter *types.MessageFilter) ([]*types.Message, error) {
func (r *repository) FindMessages(filter *types.MessageFilter) (types.MessageSet, error) {
params := make([]interface{}, 0)
rval := make([]*types.Message, 0)
rval := make(types.MessageSet, 0)
sql := sqlMessagesSelect
+1 -1
View File
@@ -18,7 +18,7 @@ func TestMessage(t *testing.T) {
var msg1, msg2 = "Test message v1", "Test message v2"
var mm []*types.Message
var mm types.MessageSet
{
msg.Message = msg1
+41 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/crusttech/crust/sam/types"
"github.com/titpetric/factory"
"io"
"net/http"
"path"
"strings"
)
@@ -100,7 +101,45 @@ func (svc attachment) Create(ctx context.Context, channelId uint64, name string,
})
}
func (svc attachment) makePreview(att *types.Attachment, originalFh io.ReadSeeker) (err error) {
func (svc attachment) extractMeta(att *types.Attachment, file io.ReadSeeker) (err error) {
if _, err = file.Seek(0, 0); err != nil {
return err
}
// Make sure we rewind...
defer file.Seek(0, 0)
// See http.DetectContentType about 512 bytes
var buf = make([]byte, 512)
if _, err = file.Read(buf); err != nil {
return
}
att.Mimetype = http.DetectContentType(buf)
// @todo compare mime with extension (or better, enforce extension from mimetype)
//if extensions, err := mime.ExtensionsByType(att.Mimetype); err == nil {
// extensions[0]
//}
// @todo extract image info so we can provide additional features if needed
//if strings.HasPrefix(att.Mimetype, "image/gif") {
// if cfg, err := gif.DecodeAll(file); err == nil {
// m.Width = cfg.Config.Width
// m.Height = cfg.Config.Height
// m.Animated = cfg.LoopCount > 0 || len(cfg.Delay) > 1
// }
//} else if strings.HasPrefix(att.Mimetype, "image") {
// if cfg, _, err := image.DecodeConfig(file); err == nil {
// m.Width = cfg.Width
// m.Height = cfg.Height
// }
//}
return
}
func (svc attachment) makePreview(att *types.Attachment, original io.ReadSeeker) (err error) {
if true {
return
}
@@ -109,7 +148,7 @@ func (svc attachment) makePreview(att *types.Attachment, originalFh io.ReadSeeke
var ext = "jpg"
att.PreviewUrl = svc.sto.Preview(att.ID, ext)
return svc.sto.Save(att.PreviewUrl, originalFh)
return svc.sto.Save(att.PreviewUrl, original)
}
var _ AttachmentService = &attachment{}
+1 -1
View File
@@ -174,7 +174,7 @@ func (svc channel) Create(ctx context.Context, in *types.Channel) (out *types.Ch
func (svc channel) Update(ctx context.Context, in *types.Channel) (out *types.Channel, err error) {
return out, svc.rpo.BeginWith(ctx, func(r repository.Interfaces) (err error) {
var msgs []*types.Message
var msgs types.MessageSet
// @todo [SECURITY] can user access this channel?
if out, err = r.FindChannelByID(in.ID); err != nil {
+32 -3
View File
@@ -14,7 +14,7 @@ type (
}
MessageService interface {
Find(ctx context.Context, filter *types.MessageFilter) ([]*types.Message, error)
Find(ctx context.Context, filter *types.MessageFilter) (types.MessageSet, error)
Create(ctx context.Context, messages *types.Message) (*types.Message, error)
Update(ctx context.Context, messages *types.Message) (*types.Message, error)
@@ -47,7 +47,7 @@ func Message() *message {
return m
}
func (svc message) Find(ctx context.Context, filter *types.MessageFilter) ([]*types.Message, error) {
func (svc message) Find(ctx context.Context, filter *types.MessageFilter) (mm types.MessageSet, err error) {
// @todo get user from context
var currentUserID uint64 = auth.GetIdentityFromContext(ctx).Identity()
@@ -55,7 +55,36 @@ func (svc message) Find(ctx context.Context, filter *types.MessageFilter) ([]*ty
_ = currentUserID
_ = filter.ChannelID
return svc.rpo.FindMessages(filter)
mm, err = svc.rpo.FindMessages(filter)
if err != nil {
return nil, err
}
{
var ids []uint64
mm.Walk(func(m *types.Message) error {
if m.Type == "attachment" {
ids = append(ids, m.ID)
}
return nil
})
if set, err := svc.rpo.FindAttachmentByMessageID(ids...); err != nil {
return nil, err
} else {
set.Walk(func(a *types.MessageAttachment) error {
if a.MessageID > 0 {
if m := mm.FindById(a.MessageID); m != nil {
m.Attachment = &a.Attachment
}
}
return nil
})
}
}
return
}
func (svc message) Direct(ctx context.Context, recipientID uint64, in *types.Message) (out *types.Message, err error) {
+25 -22
View File
@@ -108,17 +108,21 @@ func (mr *MockRepositoryMockRecorder) FindAttachmentByID(id interface{}) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAttachmentByID", reflect.TypeOf((*MockRepository)(nil).FindAttachmentByID), id)
}
// FindAttachmentByRange mocks base method
func (m *MockRepository) FindAttachmentByRange(channelID, fromAttachmentID, toAttachmentID uint64) ([]*types.Attachment, error) {
ret := m.ctrl.Call(m, "FindAttachmentByRange", channelID, fromAttachmentID, toAttachmentID)
ret0, _ := ret[0].([]*types.Attachment)
// FindAttachmentByMessageID mocks base method
func (m *MockRepository) FindAttachmentByMessageID(IDs ...uint64) (types.MessageAttachmentSet, error) {
varargs := []interface{}{}
for _, a := range IDs {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "FindAttachmentByMessageID", varargs...)
ret0, _ := ret[0].(types.MessageAttachmentSet)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindAttachmentByRange indicates an expected call of FindAttachmentByRange
func (mr *MockRepositoryMockRecorder) FindAttachmentByRange(channelID, fromAttachmentID, toAttachmentID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAttachmentByRange", reflect.TypeOf((*MockRepository)(nil).FindAttachmentByRange), channelID, fromAttachmentID, toAttachmentID)
// FindAttachmentByMessageID indicates an expected call of FindAttachmentByMessageID
func (mr *MockRepositoryMockRecorder) FindAttachmentByMessageID(IDs ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAttachmentByMessageID", reflect.TypeOf((*MockRepository)(nil).FindAttachmentByMessageID), IDs...)
}
// CreateAttachment mocks base method
@@ -134,19 +138,6 @@ func (mr *MockRepositoryMockRecorder) CreateAttachment(mod interface{}) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockRepository)(nil).CreateAttachment), mod)
}
// UpdateAttachment mocks base method
func (m *MockRepository) UpdateAttachment(mod *types.Attachment) (*types.Attachment, error) {
ret := m.ctrl.Call(m, "UpdateAttachment", mod)
ret0, _ := ret[0].(*types.Attachment)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAttachment indicates an expected call of UpdateAttachment
func (mr *MockRepositoryMockRecorder) UpdateAttachment(mod interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAttachment", reflect.TypeOf((*MockRepository)(nil).UpdateAttachment), mod)
}
// DeleteAttachmentByID mocks base method
func (m *MockRepository) DeleteAttachmentByID(id uint64) error {
ret := m.ctrl.Call(m, "DeleteAttachmentByID", id)
@@ -159,6 +150,18 @@ func (mr *MockRepositoryMockRecorder) DeleteAttachmentByID(id interface{}) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAttachmentByID", reflect.TypeOf((*MockRepository)(nil).DeleteAttachmentByID), id)
}
// BindAttachment mocks base method
func (m *MockRepository) BindAttachment(attachmentId, messageId uint64) error {
ret := m.ctrl.Call(m, "BindAttachment", attachmentId, messageId)
ret0, _ := ret[0].(error)
return ret0
}
// BindAttachment indicates an expected call of BindAttachment
func (mr *MockRepositoryMockRecorder) BindAttachment(attachmentId, messageId interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindAttachment", reflect.TypeOf((*MockRepository)(nil).BindAttachment), attachmentId, messageId)
}
// FindChannelByID mocks base method
func (m *MockRepository) FindChannelByID(id uint64) (*types.Channel, error) {
ret := m.ctrl.Call(m, "FindChannelByID", id)
@@ -312,9 +315,9 @@ func (mr *MockRepositoryMockRecorder) FindMessageByID(id interface{}) *gomock.Ca
}
// FindMessages mocks base method
func (m *MockRepository) FindMessages(filter *types.MessageFilter) ([]*types.Message, error) {
func (m *MockRepository) FindMessages(filter *types.MessageFilter) (types.MessageSet, error) {
ret := m.ctrl.Call(m, "FindMessages", filter)
ret0, _ := ret[0].([]*types.Message)
ret0, _ := ret[0].(types.MessageSet)
ret1, _ := ret[1].(error)
return ret0, ret1
}
+18 -1
View File
@@ -6,7 +6,7 @@ import (
type (
Attachment struct {
ID uint64 `db:"id" json:"id,omitempty"`
ID uint64 `db:"id" json:"ID,omitempty"`
UserID uint64 `db:"rel_user" json:"userID,omitempty"`
Url string `db:"url" json:"url,omitempty"`
PreviewUrl string `db:"preview_url"json:"previewUrl,omitempty"`
@@ -17,4 +17,21 @@ type (
UpdatedAt *time.Time `db:"updated_at" json:"updatedAt,omitempty"`
DeletedAt *time.Time `db:"deleted_at" json:"deletedAt,omitempty"`
}
MessageAttachment struct {
Attachment
MessageID uint64 `db:"rel_message" json:"-"`
}
MessageAttachmentSet []*MessageAttachment
)
func (aa MessageAttachmentSet) Walk(w func(*MessageAttachment) error) (err error) {
for i := range aa {
if err = w(aa[i]); err != nil {
return
}
}
return
}
+32 -9
View File
@@ -6,17 +6,20 @@ import (
type (
Message struct {
ID uint64 `json:"id" db:"id"`
Type string `json:"type" db:"type"`
Message string `json:"message" db:"message"`
UserID uint64 `json:"userId" db:"rel_user"`
ChannelID uint64 `json:"channelId" db:"rel_channel"`
ReplyTo uint64 `json:"replyTo" db:"reply_to"`
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"`
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
ID uint64 `json:"id" db:"id"`
Type string `json:"type" db:"type"`
Message string `json:"message" db:"message"`
UserID uint64 `json:"userId" db:"rel_user"`
ChannelID uint64 `json:"channelId" db:"rel_channel"`
ReplyTo uint64 `json:"replyTo" db:"reply_to"`
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"`
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
Attachment *Attachment `json:"attachment,omitempty"`
}
MessageSet []*Message
MessageFilter struct {
Query string
ChannelID uint64
@@ -25,3 +28,23 @@ type (
Limit uint
}
)
func (mm MessageSet) Walk(w func(*Message) error) (err error) {
for i := range mm {
if err = w(mm[i]); err != nil {
return
}
}
return
}
func (mm MessageSet) FindById(ID uint64) *Message {
for i := range mm {
if mm[i].ID == ID {
return mm[i]
}
}
return nil
}
+14
View File
@@ -14,6 +14,8 @@ type (
ChannelID string `json:"cid"`
ReplyTo string `json:"rid"`
Attachment *Attachment `json:"att,omitempty"`
CreatedAt time.Time `json:"cat,omitempty"`
UpdatedAt *time.Time `json:"uat,omitempty"`
}
@@ -30,6 +32,18 @@ type (
MessageDelete struct {
ID string `json:"id"`
}
Attachment struct {
ID string `json:"id"`
UserID string `json:"uid"`
Url string `json:"url"`
PreviewUrl string `json:"prw"`
Size int64 `json:"sze"`
Mimetype string `json:"typ"`
Name string `json:"nme"`
CreatedAt time.Time `json:"cat,omitempty"`
UpdatedAt *time.Time `json:"uat,omitempty"`
}
)
func (p *Message) EncodeMessage() ([]byte, error) {
+21 -1
View File
@@ -14,12 +14,14 @@ func payloadFromMessage(msg *types.Message) *outgoing.Message {
UserID: uint64toa(msg.UserID),
ReplyTo: uint64toa(msg.ReplyTo),
Attachment: payloadFromAttachment(msg.Attachment),
CreatedAt: msg.CreatedAt,
UpdatedAt: msg.UpdatedAt,
}
}
func payloadFromMessages(msg []*types.Message) *outgoing.Messages {
func payloadFromMessages(msg types.MessageSet) *outgoing.Messages {
msgs := make([]*outgoing.Message, len(msg))
for k, m := range msg {
msgs[k] = payloadFromMessage(m)
@@ -70,3 +72,21 @@ func payloadFromUsers(users []*types.User) *outgoing.Users {
retval := outgoing.Users(uu)
return &retval
}
func payloadFromAttachment(in *types.Attachment) *outgoing.Attachment {
if in == nil {
return nil
}
return &outgoing.Attachment{
ID: uint64toa(in.ID),
UserID: uint64toa(in.UserID),
Url: in.Url,
PreviewUrl: in.PreviewUrl,
Size: in.Size,
Mimetype: in.Mimetype,
Name: in.Name,
CreatedAt: in.CreatedAt,
UpdatedAt: in.UpdatedAt,
}
}