⬆️ Switch to Paperclip
This commit is contained in:
		| @@ -1,127 +1,30 @@ | ||||
| package services | ||||
|  | ||||
| import ( | ||||
| 	"mime/multipart" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"context" | ||||
|  | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/database" | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/models" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/spf13/viper" | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/grpc" | ||||
| 	pcpb "git.solsynth.dev/hydrogen/paperclip/pkg/grpc/proto" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func GetAttachmentByID(id uint) (models.Attachment, error) { | ||||
| 	var attachment models.Attachment | ||||
| 	if err := database.C.Where(models.Attachment{ | ||||
| 		BaseModel: models.BaseModel{ID: id}, | ||||
| 	}).First(&attachment).Error; err != nil { | ||||
| 		return attachment, err | ||||
| 	} | ||||
| 	return attachment, nil | ||||
| func GetAttachmentByID(id uint) (*pcpb.Attachment, error) { | ||||
| 	return grpc.Attachments.GetAttachment(context.Background(), &pcpb.AttachmentLookupRequest{ | ||||
| 		Id: lo.ToPtr(uint64(id)), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func GetAttachmentByUUID(fileId string) (models.Attachment, error) { | ||||
| 	var attachment models.Attachment | ||||
| 	if err := database.C.Where(models.Attachment{ | ||||
| 		FileID: fileId, | ||||
| 	}).First(&attachment).Error; err != nil { | ||||
| 		return attachment, err | ||||
| 	} | ||||
| 	return attachment, nil | ||||
| func GetAttachmentByUUID(uuid string) (*pcpb.Attachment, error) { | ||||
| 	return grpc.Attachments.GetAttachment(context.Background(), &pcpb.AttachmentLookupRequest{ | ||||
| 		Uuid: &uuid, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func GetAttachmentByHashcode(hashcode string) (models.Attachment, error) { | ||||
| 	var attachment models.Attachment | ||||
| 	if err := database.C.Where(models.Attachment{ | ||||
| 		Hashcode: hashcode, | ||||
| 	}).First(&attachment).Error; err != nil { | ||||
| 		return attachment, err | ||||
| 	} | ||||
| 	return attachment, nil | ||||
| } | ||||
|  | ||||
| func NewAttachment(user models.Account, header *multipart.FileHeader, hashcode string) (models.Attachment, error) { | ||||
| 	var attachment models.Attachment | ||||
| 	existsAttachment, err := GetAttachmentByHashcode(hashcode) | ||||
| 	if err != nil { | ||||
| 		// Upload the new file | ||||
| 		attachment = models.Attachment{ | ||||
| 			FileID:   uuid.NewString(), | ||||
| 			Filesize: header.Size, | ||||
| 			Filename: header.Filename, | ||||
| 			Hashcode: hashcode, | ||||
| 			Mimetype: "unknown/unknown", | ||||
| 			Type:     models.AttachmentOthers, | ||||
| 			AuthorID: user.ID, | ||||
| 		} | ||||
|  | ||||
| 		// Open file | ||||
| 		file, err := header.Open() | ||||
| 		if err != nil { | ||||
| 			return attachment, err | ||||
| 		} | ||||
| 		defer file.Close() | ||||
|  | ||||
| 		// Detect mimetype | ||||
| 		fileHeader := make([]byte, 512) | ||||
| 		_, err = file.Read(fileHeader) | ||||
| 		if err != nil { | ||||
| 			return attachment, err | ||||
| 		} | ||||
| 		attachment.Mimetype = http.DetectContentType(fileHeader) | ||||
|  | ||||
| 		switch strings.Split(attachment.Mimetype, "/")[0] { | ||||
| 		case "image": | ||||
| 			attachment.Type = models.AttachmentPhoto | ||||
| 		case "video": | ||||
| 			attachment.Type = models.AttachmentVideo | ||||
| 		case "audio": | ||||
| 			attachment.Type = models.AttachmentAudio | ||||
| 		default: | ||||
| 			attachment.Type = models.AttachmentOthers | ||||
| 		} | ||||
| 	} else { | ||||
| 		// Instant upload, build link with the exists file | ||||
| 		attachment = models.Attachment{ | ||||
| 			FileID:   existsAttachment.FileID, | ||||
| 			Filesize: header.Size, | ||||
| 			Filename: header.Filename, | ||||
| 			Hashcode: hashcode, | ||||
| 			Mimetype: existsAttachment.Mimetype, | ||||
| 			Type:     existsAttachment.Type, | ||||
| 			AuthorID: user.ID, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Save into database | ||||
| 	err = database.C.Save(&attachment).Error | ||||
|  | ||||
| 	return attachment, err | ||||
| } | ||||
|  | ||||
| func DeleteAttachment(item models.Attachment) error { | ||||
| 	var dupeCount int64 | ||||
| 	if err := database.C. | ||||
| 		Where(&models.Attachment{Hashcode: item.Hashcode}). | ||||
| 		Model(&models.Attachment{}). | ||||
| 		Count(&dupeCount).Error; err != nil { | ||||
| 		dupeCount = -1 | ||||
| 	} | ||||
|  | ||||
| 	if err := database.C.Delete(&item).Error; err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if dupeCount != -1 && dupeCount <= 1 { | ||||
| 		// Safe for deletion the physics file | ||||
| 		basepath := viper.GetString("content") | ||||
| 		fullpath := filepath.Join(basepath, item.FileID) | ||||
|  | ||||
| 		os.Remove(fullpath) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| func CheckAttachmentByIDExists(id uint, usage string) bool { | ||||
| 	_, err := grpc.Attachments.CheckAttachmentExists(context.Background(), &pcpb.AttachmentLookupRequest{ | ||||
| 		Id:    lo.ToPtr(uint64(id)), | ||||
| 		Usage: &usage, | ||||
| 	}) | ||||
|  | ||||
| 	return err == nil | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package services | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/database" | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/models" | ||||
| ) | ||||
| @@ -19,6 +20,18 @@ func ListChannelMember(channelId uint) ([]models.ChannelMember, error) { | ||||
| 	return members, nil | ||||
| } | ||||
|  | ||||
| func GetChannelMember(user models.Account, channelId uint) (models.ChannelMember, error) { | ||||
| 	var member models.ChannelMember | ||||
|  | ||||
| 	if err := database.C. | ||||
| 		Where(&models.ChannelMember{AccountID: user.ID, ChannelID: channelId}). | ||||
| 		Find(&member).Error; err != nil { | ||||
| 		return member, err | ||||
| 	} | ||||
|  | ||||
| 	return member, nil | ||||
| } | ||||
|  | ||||
| func AddChannelMemberWithCheck(user models.Account, target models.Channel) error { | ||||
| 	if _, err := GetAccountFriend(user.ID, target.AccountID, 1); err != nil { | ||||
| 		return fmt.Errorf("you only can invite your friends to your channel") | ||||
|   | ||||
| @@ -80,11 +80,9 @@ func GetAvailableChannel(id uint, user models.Account) (models.Channel, models.C | ||||
|  | ||||
| func ListChannel(realmId ...uint) ([]models.Channel, error) { | ||||
| 	var channels []models.Channel | ||||
| 	tx := database.C.Preload("Account") | ||||
| 	tx := database.C.Preload("Account").Preload("Realm") | ||||
| 	if len(realmId) > 0 { | ||||
| 		tx = tx.Where("realm_id = ?", realmId) | ||||
| 	} else { | ||||
| 		tx = tx.Where("realm_id IS NULL") | ||||
| 	} | ||||
| 	if err := tx.Find(&channels).Error; err != nil { | ||||
| 		return channels, err | ||||
| @@ -95,7 +93,7 @@ func ListChannel(realmId ...uint) ([]models.Channel, error) { | ||||
|  | ||||
| func ListChannelWithUser(user models.Account, realmId ...uint) ([]models.Channel, error) { | ||||
| 	var channels []models.Channel | ||||
| 	tx := database.C.Where(&models.Channel{AccountID: user.ID}) | ||||
| 	tx := database.C.Where(&models.Channel{AccountID: user.ID}).Preload("Realm") | ||||
| 	if len(realmId) > 0 { | ||||
| 		tx = tx.Where("realm_id = ?", realmId) | ||||
| 	} else { | ||||
| @@ -121,7 +119,7 @@ func ListAvailableChannel(user models.Account, realmId ...uint) ([]models.Channe | ||||
| 		return item.ChannelID | ||||
| 	}) | ||||
|  | ||||
| 	tx := database.C.Where("id IN ?", idx) | ||||
| 	tx := database.C.Preload("Realm").Where("id IN ?", idx) | ||||
| 	if len(realmId) > 0 { | ||||
| 		tx = tx.Where("realm_id = ?", realmId) | ||||
| 	} else { | ||||
| @@ -134,23 +132,8 @@ func ListAvailableChannel(user models.Account, realmId ...uint) ([]models.Channe | ||||
| 	return channels, nil | ||||
| } | ||||
|  | ||||
| func NewChannel(user models.Account, alias, name, description string, isEncrypted bool, realmId ...uint) (models.Channel, error) { | ||||
| 	channel := models.Channel{ | ||||
| 		Alias:       alias, | ||||
| 		Name:        name, | ||||
| 		Description: description, | ||||
| 		IsEncrypted: isEncrypted, | ||||
| 		AccountID:   user.ID, | ||||
| 		Members: []models.ChannelMember{ | ||||
| 			{AccountID: user.ID}, | ||||
| 		}, | ||||
| 	} | ||||
| 	if len(realmId) > 0 { | ||||
| 		channel.RealmID = &realmId[0] | ||||
| 	} | ||||
|  | ||||
| func NewChannel(channel models.Channel) (models.Channel, error) { | ||||
| 	err := database.C.Save(&channel).Error | ||||
|  | ||||
| 	return channel, err | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| package services | ||||
|  | ||||
| import ( | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/database" | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/models" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/database" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| ) | ||||
|  | ||||
| func DoAutoDatabaseCleanup() { | ||||
| @@ -16,18 +16,10 @@ func DoAutoDatabaseCleanup() { | ||||
| 	for _, model := range database.DatabaseAutoActionRange { | ||||
| 		tx := database.C.Unscoped().Delete(model, "deleted_at >= ?", deadline) | ||||
| 		if tx.Error != nil { | ||||
| 			log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...") | ||||
| 			log.Error().Err(tx.Error).Msg("An error occurred when running database cleanup...") | ||||
| 		} | ||||
| 		count += tx.RowsAffected | ||||
| 	} | ||||
|  | ||||
| 	// Clean up outdated chat history | ||||
| 	tx := database.C.Unscoped().Delete(&models.Message{}, "created_at < ?", time.Now().Add(30*24*time.Hour)) | ||||
| 	if tx.Error != nil { | ||||
| 		log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...") | ||||
| 	} else { | ||||
| 		count += tx.RowsAffected | ||||
| 	} | ||||
|  | ||||
| 	log.Debug().Int64("affected", count).Msg("Clean up entire database accomplished.") | ||||
| } | ||||
|   | ||||
| @@ -1,24 +1,33 @@ | ||||
| package services | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
|  | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/models" | ||||
| 	"github.com/gofiber/contrib/websocket" | ||||
| ) | ||||
|  | ||||
| var wsConn = make(map[uint]map[*websocket.Conn]bool) | ||||
| var ( | ||||
| 	wsMutex sync.Mutex | ||||
| 	wsConn  = make(map[uint]map[*websocket.Conn]bool) | ||||
| ) | ||||
|  | ||||
| func ClientRegister(user models.Account, conn *websocket.Conn) { | ||||
| 	wsMutex.Lock() | ||||
| 	if wsConn[user.ID] == nil { | ||||
| 		wsConn[user.ID] = make(map[*websocket.Conn]bool) | ||||
| 	} | ||||
| 	wsConn[user.ID][conn] = true | ||||
| 	wsMutex.Unlock() | ||||
| } | ||||
|  | ||||
| func ClientUnregister(user models.Account, conn *websocket.Conn) { | ||||
| 	wsMutex.Lock() | ||||
| 	if wsConn[user.ID] == nil { | ||||
| 		wsConn[user.ID] = make(map[*websocket.Conn]bool) | ||||
| 	} | ||||
| 	delete(wsConn[user.ID], conn) | ||||
| 	wsMutex.Unlock() | ||||
| } | ||||
|  | ||||
| func PushCommand(userId uint, task models.UnifiedCommand) { | ||||
|   | ||||
| @@ -2,11 +2,11 @@ package services | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/database" | ||||
| 	"git.solsynth.dev/hydrogen/messaging/pkg/models" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 	"github.com/rs/zerolog/log" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func CountMessage(channel models.Channel) int64 { | ||||
| @@ -31,7 +31,6 @@ func ListMessage(channel models.Channel, take int, offset int) ([]models.Message | ||||
| 			ChannelID: channel.ID, | ||||
| 		}).Limit(take).Offset(offset). | ||||
| 		Order("created_at DESC"). | ||||
| 		Preload("Attachments"). | ||||
| 		Preload("ReplyTo"). | ||||
| 		Preload("ReplyTo.Sender"). | ||||
| 		Preload("ReplyTo.Sender.Account"). | ||||
| @@ -54,7 +53,6 @@ func GetMessage(channel models.Channel, id uint) (models.Message, error) { | ||||
| 		Preload("ReplyTo"). | ||||
| 		Preload("ReplyTo.Sender"). | ||||
| 		Preload("ReplyTo.Sender.Account"). | ||||
| 		Preload("Attachments"). | ||||
| 		Preload("Sender"). | ||||
| 		Preload("Sender.Account"). | ||||
| 		First(&message).Error; err != nil { | ||||
| @@ -78,9 +76,6 @@ func GetMessageWithPrincipal(channel models.Channel, member models.ChannelMember | ||||
| } | ||||
|  | ||||
| func NewMessage(message models.Message) (models.Message, error) { | ||||
| 	var decodedContent fiber.Map | ||||
| 	_ = jsoniter.Unmarshal(message.Content, &decodedContent) | ||||
|  | ||||
| 	var members []models.ChannelMember | ||||
| 	if err := database.C.Save(&message).Error; err != nil { | ||||
| 		return message, err | ||||
| @@ -91,20 +86,39 @@ func NewMessage(message models.Message) (models.Message, error) { | ||||
| 		message, _ = GetMessage(message.Channel, message.ID) | ||||
| 		for _, member := range members { | ||||
| 			if member.ID != message.Sender.ID { | ||||
| 				// TODO Check the mentioned status | ||||
| 				if member.Notify == models.NotifyLevelAll { | ||||
| 					displayText := "*encrypted message*" | ||||
| 					if decodedContent["algorithm"] == "plain" { | ||||
| 						displayText, _ = decodedContent["value"].(string) | ||||
| 					} | ||||
| 					err = NotifyAccount(member.Account, | ||||
| 						fmt.Sprintf("New Message #%s", channel.Alias), | ||||
| 						fmt.Sprintf("%s: %s", message.Sender.Account.Name, displayText), | ||||
| 						true, | ||||
| 					) | ||||
| 					if err != nil { | ||||
| 						log.Warn().Err(err).Msg("An error occurred when trying notify user.") | ||||
| 				switch member.Notify { | ||||
| 				case models.NotifyLevelNone: | ||||
| 					continue | ||||
| 				case models.NotifyLevelMentioned: | ||||
| 					if val, ok := message.Content["metioned_users"]; ok { | ||||
| 						if usernames, ok := val.([]string); ok { | ||||
| 							if lo.Contains(usernames, member.Account.Name) { | ||||
| 								break | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				var displayText string | ||||
| 				if message.Content["algorithm"] == "plain" { | ||||
| 					displayText, _ = message.Content["value"].(string) | ||||
| 				} else { | ||||
| 					displayText = "*encrypted message*" | ||||
| 				} | ||||
|  | ||||
| 				if len(displayText) == 0 { | ||||
| 					displayText = fmt.Sprintf("%d attachment(s)", len(message.Attachments)) | ||||
| 				} | ||||
|  | ||||
| 				err = NotifyAccount(member.Account, | ||||
| 					fmt.Sprintf("New Message #%s", channel.Alias), | ||||
| 					fmt.Sprintf("%s: %s", message.Sender.Account.Name, displayText), | ||||
| 					true, | ||||
| 				) | ||||
| 				if err != nil { | ||||
| 					log.Warn().Err(err).Msg("An error occurred when trying notify user.") | ||||
| 				} | ||||
| 			} | ||||
| 			PushCommand(member.AccountID, models.UnifiedCommand{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user