mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-10-23 05:11:57 +00:00
fix: new incoming messages having 0 attachments in API responses, upload attachments first before sending out websocket updates.
- refactor: stringutils package remove unncessary `string` in function names. - minor refactors.
This commit is contained in:
@@ -26,7 +26,7 @@ func handleOIDCLogin(r *fastglue.Request) error {
|
||||
}
|
||||
|
||||
// Set a state and save it in the session, to prevent CSRF attacks.
|
||||
state, err := stringutil.RandomAlNumString(32)
|
||||
state, err := stringutil.RandomAlphanumeric(32)
|
||||
if err != nil {
|
||||
app.lo.Error("error generating state", "error", err)
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error generating state.", nil, envelope.GeneralError)
|
||||
|
@@ -133,7 +133,7 @@ func handleMediaUpload(r *fastglue.Request) error {
|
||||
}
|
||||
|
||||
// Insert in DB.
|
||||
media, err := app.media.Insert(disposition, srcFileName, srcContentType, "" /**content_id**/, linkedModel, uuid.String(), 0, int(srcFileSize), meta)
|
||||
media, err := app.media.Insert(disposition, srcFileName, srcContentType, "" /**content_id**/, null.NewString(linkedModel, linkedModel != ""), uuid.String(), null.Int{} /**model_id**/, int(srcFileSize), meta)
|
||||
if err != nil {
|
||||
cleanUp = true
|
||||
app.lo.Error("error inserting metadata into database", "error", err)
|
||||
|
@@ -51,7 +51,7 @@ func handleGetMessages(r *fastglue.Request) error {
|
||||
for j := range messages[i].Attachments {
|
||||
messages[i].Attachments[j].URL = app.media.GetURL(messages[i].Attachments[j].UUID)
|
||||
}
|
||||
messages[i].HideCSAT()
|
||||
messages[i].CensorCSATContent()
|
||||
}
|
||||
return r.SendEnvelope(envelope.PageResults{
|
||||
Total: total,
|
||||
@@ -86,7 +86,8 @@ func handleGetMessage(r *fastglue.Request) error {
|
||||
return sendErrorEnvelope(r, err)
|
||||
}
|
||||
|
||||
message.HideCSAT()
|
||||
// Redact CSAT survey link
|
||||
message.CensorCSATContent()
|
||||
|
||||
for j := range message.Attachments {
|
||||
message.Attachments[j].URL = app.media.GetURL(message.Attachments[j].UUID)
|
||||
@@ -145,14 +146,14 @@ func handleSendMessage(r *fastglue.Request) error {
|
||||
|
||||
if err := r.Decode(&req, "json"); err != nil {
|
||||
app.lo.Error("error unmarshalling message request", "error", err)
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "error decoding request", nil, "")
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "error decoding request", nil, envelope.GeneralError)
|
||||
}
|
||||
|
||||
for _, id := range req.Attachments {
|
||||
m, err := app.media.Get(id)
|
||||
if err != nil {
|
||||
app.lo.Error("error fetching media", "error", err)
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error fetching media", nil, "")
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error fetching media", nil, envelope.GeneralError)
|
||||
}
|
||||
media = append(media, m)
|
||||
}
|
||||
|
@@ -142,7 +142,12 @@ func handleUpdateCurrentUser(r *fastglue.Request) error {
|
||||
|
||||
// Reset ptr.
|
||||
file.Seek(0, 0)
|
||||
media, err := app.media.UploadAndInsert(srcFileName, srcContentType, "" /**content_id**/, mmodels.ModelUser, user.ID, file, int(srcFileSize), null.NewString("", false) /**disposition**/, []byte("{}") /**meta**/)
|
||||
linkedModel := null.StringFrom(mmodels.ModelUser)
|
||||
linkedID := null.IntFrom(user.ID)
|
||||
disposition := null.NewString("", false)
|
||||
contentID := ""
|
||||
meta := []byte("{}")
|
||||
media, err := app.media.UploadAndInsert(srcFileName, srcContentType, contentID, linkedModel, linkedID, file, int(srcFileSize), disposition, meta)
|
||||
if err != nil {
|
||||
app.lo.Error("error uploading file", "error", err)
|
||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error uploading file", nil, envelope.GeneralError)
|
||||
|
@@ -329,7 +329,7 @@ func (a *Auth) DestroySession(r *fastglue.Request) error {
|
||||
|
||||
// generateCSRFToken creates a random base64 encoded str.
|
||||
func generateCSRFToken() (string, error) {
|
||||
b, err := stringutil.RandomAlNumString(30)
|
||||
b, err := stringutil.RandomAlphanumeric(32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@@ -107,7 +107,7 @@ type mediaStore interface {
|
||||
GetByModel(id int, model string) ([]mmodels.Media, error)
|
||||
ContentIDExists(contentID string) (bool, error)
|
||||
Upload(fileName, contentType string, content io.ReadSeeker) (string, error)
|
||||
UploadAndInsert(fileName, contentType, contentID, modelType string, modelID int, content io.ReadSeeker, fileSize int, disposition null.String, meta []byte) (mmodels.Media, error)
|
||||
UploadAndInsert(fileName, contentType, contentID string, modelType null.String, modelID null.Int, content io.ReadSeeker, fileSize int, disposition null.String, meta []byte) (mmodels.Media, error)
|
||||
}
|
||||
|
||||
type inboxStore interface {
|
||||
|
@@ -294,11 +294,10 @@ func (m *Manager) SendPrivateNote(media []mmodels.Media, senderID int, conversat
|
||||
|
||||
// SendReply inserts a reply message in a conversation.
|
||||
func (m *Manager) SendReply(media []mmodels.Media, senderID int, conversationUUID, content string, cc, bcc []string, meta map[string]interface{}) error {
|
||||
// Strip empty strings bc and cc.
|
||||
cc = stringutil.RemoveEmptyStrings(cc)
|
||||
bcc = stringutil.RemoveEmptyStrings(bcc)
|
||||
cc = stringutil.RemoveEmpty(cc)
|
||||
bcc = stringutil.RemoveEmpty(bcc)
|
||||
|
||||
// Set cc and bcc in meta.
|
||||
// Save cc and bcc as JSON in meta.
|
||||
if len(cc) > 0 {
|
||||
meta["cc"] = cc
|
||||
}
|
||||
@@ -331,11 +330,12 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
||||
message.Status = MessageStatusSent
|
||||
}
|
||||
|
||||
// Handle empty meta.
|
||||
if message.Meta == "" || message.Meta == "null" {
|
||||
message.Meta = "{}"
|
||||
}
|
||||
|
||||
// Generate text content.
|
||||
// Convert HTML content to text for search.
|
||||
message.TextContent = stringutil.HTML2Text(message.Content)
|
||||
|
||||
// Insert Message.
|
||||
@@ -355,7 +355,7 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update conversation last message details in conversation.
|
||||
// Update conversation last message details in conversation metadata.
|
||||
m.UpdateConversationLastMessage(message.ConversationID, message.ConversationUUID, message.TextContent, message.CreatedAt)
|
||||
|
||||
// Broadcast new message.
|
||||
@@ -494,17 +494,17 @@ func (m *Manager) processIncomingMessage(in models.IncomingMessage) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert message.
|
||||
if err = m.InsertMessage(&in.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upload message attachments.
|
||||
if err := m.uploadMessageAttachments(&in.Message); err != nil {
|
||||
// Log error but continue processing.
|
||||
m.lo.Error("error uploading message attachments", "message_source_id", in.Message.SourceID, "error", err)
|
||||
}
|
||||
|
||||
// Insert message.
|
||||
if err = m.InsertMessage(&in.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Evaluate automation rules for this conversation.
|
||||
if isNewConversation {
|
||||
m.automation.EvaluateNewConversationRules(in.Message.ConversationUUID)
|
||||
@@ -615,17 +615,18 @@ func (m *Manager) uploadMessageAttachments(message *models.Message) []error {
|
||||
|
||||
// Sanitize filename and upload.
|
||||
attachment.Name = stringutil.SanitizeFilename(attachment.Name)
|
||||
reader := bytes.NewReader(attachment.Content)
|
||||
attachReader := bytes.NewReader(attachment.Content)
|
||||
media, err := m.mediaStore.UploadAndInsert(
|
||||
attachment.Name,
|
||||
attachment.ContentType,
|
||||
attachment.ContentID,
|
||||
mmodels.ModelMessages,
|
||||
message.ID,
|
||||
reader,
|
||||
/** Linking media to message happens later **/
|
||||
null.String{}, /** modelType */
|
||||
null.Int{}, /** modelID **/
|
||||
attachReader,
|
||||
attachment.Size,
|
||||
null.StringFrom(attachment.Disposition),
|
||||
[]byte("{}"),
|
||||
[]byte("{}"), /** meta **/
|
||||
)
|
||||
if err != nil {
|
||||
uploadErr = append(uploadErr, err)
|
||||
@@ -640,6 +641,7 @@ func (m *Manager) uploadMessageAttachments(message *models.Message) []error {
|
||||
m.lo.Error("error uploading thumbnail", "error", err)
|
||||
}
|
||||
}
|
||||
message.Media = append(message.Media, media)
|
||||
}
|
||||
return uploadErr
|
||||
}
|
||||
|
@@ -121,8 +121,8 @@ type Message struct {
|
||||
Total int `db:"total" json:"-"`
|
||||
}
|
||||
|
||||
// HideCSAT hides the CSAT message.
|
||||
func (m *Message) HideCSAT() {
|
||||
// CensorCSATContent redacts the content of a CSAT message to prevent leaking the CSAT survey public link.
|
||||
func (m *Message) CensorCSATContent() {
|
||||
var meta map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(m.Meta), &meta); err != nil {
|
||||
return
|
||||
|
@@ -73,7 +73,7 @@ type queries struct {
|
||||
}
|
||||
|
||||
// UploadAndInsert uploads file on storage and inserts an entry in db.
|
||||
func (m *Manager) UploadAndInsert(srcFilename, contentType, contentID, modelType string, modelID int, content io.ReadSeeker, fileSize int, disposition null.String, meta []byte) (models.Media, error) {
|
||||
func (m *Manager) UploadAndInsert(srcFilename, contentType, contentID string, modelType null.String, modelID null.Int, content io.ReadSeeker, fileSize int, disposition null.String, meta []byte) (models.Media, error) {
|
||||
var uuid = uuid.New()
|
||||
_, err := m.Upload(uuid.String(), contentType, content)
|
||||
if err != nil {
|
||||
@@ -99,7 +99,7 @@ func (m *Manager) Upload(fileName, contentType string, content io.ReadSeeker) (s
|
||||
}
|
||||
|
||||
// Insert inserts media details into the database and returns the inserted media record.
|
||||
func (m *Manager) Insert(disposition null.String, fileName, contentType, contentID, modelType, uuid string, modelID int, fileSize int, meta []byte) (models.Media, error) {
|
||||
func (m *Manager) Insert(disposition null.String, fileName, contentType, contentID string, modelType null.String, uuid string, modelID null.Int, fileSize int, meta []byte) (models.Media, error) {
|
||||
var id int
|
||||
if err := m.queries.Insert.QueryRow(m.store.Name(), fileName, contentType, fileSize, meta, modelID, modelType, disposition, contentID, uuid).Scan(&id); err != nil {
|
||||
m.lo.Error("error inserting media", "error", err)
|
||||
@@ -155,8 +155,8 @@ func (m *Manager) GetURL(name string) string {
|
||||
// Attach associates a media file with a specific model by its ID and model name.
|
||||
func (m *Manager) Attach(id int, model string, modelID int) error {
|
||||
if _, err := m.queries.Attach.Exec(id, model, modelID); err != nil {
|
||||
m.lo.Error("error attaching media to model", "model", model, "model_id", modelID, "error", err)
|
||||
return err
|
||||
m.lo.Error("error attaching media to model", "model", model, "model_id", modelID, "media_id", id, "error", err)
|
||||
return fmt.Errorf("attaching media;%d to model:%s model_id:%d: %w", id, model, modelID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -41,8 +41,8 @@ func SanitizeFilename(fName string) string {
|
||||
return filepath.Base(name)
|
||||
}
|
||||
|
||||
// RandomAlNumString generates a random alphanumeric string of length n.
|
||||
func RandomAlNumString(n int) (string, error) {
|
||||
// RandomAlphanumeric generates a random alphanumeric string of length n.
|
||||
func RandomAlphanumeric(n int) (string, error) {
|
||||
const dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
bytes := make([]byte, n)
|
||||
@@ -58,8 +58,8 @@ func RandomAlNumString(n int) (string, error) {
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// RandomNumericString generates a random numeric string of length n.
|
||||
func RandomNumericString(n int) (string, error) {
|
||||
// RandomNumeric generates a random numeric string of length n.
|
||||
func RandomNumeric(n int) (string, error) {
|
||||
const dictionary = "0123456789"
|
||||
|
||||
bytes := make([]byte, n)
|
||||
@@ -84,8 +84,8 @@ func GetPathFromURL(u string) (string, error) {
|
||||
return parsedURL.Path, nil
|
||||
}
|
||||
|
||||
// RemoveEmptyStrings removes empty strings from a slice of strings.
|
||||
func RemoveEmptyStrings(s []string) []string {
|
||||
// RemoveEmpty removes empty strings from a slice of strings.
|
||||
func RemoveEmpty(s []string) []string {
|
||||
var r []string
|
||||
for _, str := range s {
|
||||
if str != "" {
|
||||
@@ -93,4 +93,4 @@ func RemoveEmptyStrings(s []string) []string {
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ const (
|
||||
// Built-in templates names stored in the database.
|
||||
TmplConversationAssigned = "Conversation assigned"
|
||||
|
||||
// Built-in templates in fetched from memory stored in `static` directory.
|
||||
// Built-in templates fetched from memory stored in `static` directory.
|
||||
TmplResetPassword = "reset-password"
|
||||
TmplWelcome = "welcome"
|
||||
|
||||
@@ -29,7 +29,7 @@ func (m *Manager) RenderWithBaseTemplate(data any, content string) (string, erro
|
||||
defaultTmpl, err := m.getDefaultOutgoingEmailTemplate()
|
||||
if err != nil {
|
||||
if err == ErrTemplateNotFound {
|
||||
m.lo.Warn("default outgoing email template not found, rendering content without base")
|
||||
m.lo.Warn("default outgoing email template not found, rendering content any template")
|
||||
return content, nil
|
||||
}
|
||||
return "", err
|
||||
@@ -95,7 +95,7 @@ func (m *Manager) RenderNamedTemplate(name string, data any) (string, string, er
|
||||
defaultTmpl, err := m.getDefaultOutgoingEmailTemplate()
|
||||
if err != nil {
|
||||
if err == ErrTemplateNotFound {
|
||||
m.lo.Warn("default outgoing email template not found, rendering content without base")
|
||||
m.lo.Warn("default outgoing email template not found, rendering content any template")
|
||||
content, err := executeContentTemplate(tmpl.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
|
@@ -250,7 +250,7 @@ func (u *Manager) GetEmail(id int) (string, error) {
|
||||
|
||||
// SetResetPasswordToken sets a reset password token for a user and returns the token.
|
||||
func (u *Manager) SetResetPasswordToken(id int) (string, error) {
|
||||
token, err := stringutil.RandomAlNumString(32)
|
||||
token, err := stringutil.RandomAlphanumeric(32)
|
||||
if err != nil {
|
||||
u.lo.Error("error generating reset password token", "error", err)
|
||||
return "", envelope.NewError(envelope.GeneralError, "Error generating reset password token", nil)
|
||||
@@ -300,7 +300,7 @@ func (u *Manager) verifyPassword(pwd []byte, pwdHash string) error {
|
||||
|
||||
// generatePassword generates a random password and returns its bcrypt hash.
|
||||
func (u *Manager) generatePassword() ([]byte, error) {
|
||||
password, _ := stringutil.RandomAlNumString(70)
|
||||
password, _ := stringutil.RandomAlphanumeric(70)
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
u.lo.Error("error generating bcrypt password", "error", err)
|
||||
|
@@ -304,10 +304,10 @@ CREATE TABLE media (
|
||||
store "media_store" NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
content_type TEXT NOT NULL,
|
||||
content_id TEXT NULL,
|
||||
model_id INT NULL,
|
||||
model_type TEXT NULL,
|
||||
disposition media_disposition NULL,
|
||||
content_id TEXT NULL,
|
||||
"size" INT NULL,
|
||||
meta jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
CONSTRAINT constraint_media_on_filename CHECK (length(filename) <= 1000),
|
||||
|
Reference in New Issue
Block a user