Serve attachment info alongside with message (rest & ws)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user