🎉 Initial Commit

This commit is contained in:
2024-03-26 23:05:13 +08:00
commit 99f85a8b76
55 changed files with 2827 additions and 0 deletions

28
pkg/services/accounts.go Normal file
View File

@ -0,0 +1,28 @@
package services
import (
"context"
"time"
"git.solsynth.dev/hydrogen/identity/pkg/grpc/proto"
"git.solsynth.dev/hydrogen/messaging/pkg/grpc"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/spf13/viper"
)
func NotifyAccount(user models.Account, subject, content string, links ...*proto.NotifyLink) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
_, err := grpc.Notify.NotifyUser(ctx, &proto.NotifyRequest{
ClientId: viper.GetString("identity.client_id"),
ClientSecret: viper.GetString("identity.client_secret"),
Subject: subject,
Content: content,
Links: links,
RecipientId: uint64(user.ID),
IsImportant: false,
})
return err
}

127
pkg/services/attachments.go Normal file
View File

@ -0,0 +1,127 @@
package services
import (
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/google/uuid"
"github.com/spf13/viper"
)
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 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 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
}

71
pkg/services/auth.go Normal file
View File

@ -0,0 +1,71 @@
package services
import (
"context"
"errors"
"fmt"
"time"
"git.solsynth.dev/hydrogen/identity/pkg/grpc/proto"
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/grpc"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"gorm.io/gorm"
)
func LinkAccount(userinfo *proto.Userinfo) (models.Account, error) {
var account models.Account
if userinfo == nil {
return account, fmt.Errorf("remote userinfo was not found")
}
if err := database.C.Where(&models.Account{
ExternalID: uint(userinfo.Id),
}).First(&account).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
account = models.Account{
Name: userinfo.Name,
Nick: userinfo.Nick,
Avatar: userinfo.Avatar,
Banner: userinfo.Banner,
Description: userinfo.GetDescription(),
EmailAddress: userinfo.Email,
PowerLevel: 0,
ExternalID: uint(userinfo.Id),
}
return account, database.C.Save(&account).Error
}
return account, err
}
account.Name = userinfo.Name
account.Nick = userinfo.Nick
account.Avatar = userinfo.Avatar
account.Banner = userinfo.Banner
account.Description = userinfo.GetDescription()
account.EmailAddress = userinfo.Email
err := database.C.Save(&account).Error
return account, err
}
func Authenticate(atk, rtk string) (models.Account, string, string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
var err error
var user models.Account
reply, err := grpc.Auth.Authenticate(ctx, &proto.AuthRequest{
AccessToken: atk,
RefreshToken: &rtk,
})
if err != nil {
return user, reply.GetAccessToken(), reply.GetRefreshToken(), err
} else if !reply.IsValid {
return user, reply.GetAccessToken(), reply.GetRefreshToken(), fmt.Errorf("invalid authorization context")
}
user, err = LinkAccount(reply.Userinfo)
return user, reply.GetAccessToken(), reply.GetRefreshToken(), err
}

110
pkg/services/channels.go Normal file
View File

@ -0,0 +1,110 @@
package services
import (
"git.solsynth.dev/hydrogen/messaging/pkg/database"
"git.solsynth.dev/hydrogen/messaging/pkg/models"
"github.com/samber/lo"
)
func ListChannel() ([]models.Channel, error) {
var channels []models.Channel
if err := database.C.Find(&channels).Error; err != nil {
return channels, err
}
return channels, nil
}
func ListChannelWithUser(user models.Account) ([]models.Channel, error) {
var channels []models.Channel
if err := database.C.Where(&models.Channel{AccountID: user.ID}).Find(&channels).Error; err != nil {
return channels, err
}
return channels, nil
}
func ListChannelIsAvailable(user models.Account) ([]models.Channel, error) {
var channels []models.Channel
var members []models.ChannelMember
if err := database.C.Where(&models.ChannelMember{
AccountID: user.ID,
}).Find(&members).Error; err != nil {
return channels, err
}
idx := lo.Map(members, func(item models.ChannelMember, index int) uint {
return item.ChannelID
})
if err := database.C.Where("id IN ?", idx).Find(&channels).Error; err != nil {
return channels, err
}
return channels, nil
}
func NewChannel(user models.Account, name, description string) (models.Channel, error) {
channel := models.Channel{
Name: name,
Description: description,
AccountID: user.ID,
Members: []models.ChannelMember{
{AccountID: user.ID},
},
}
err := database.C.Save(&channel).Error
return channel, err
}
func ListChannelMember(channelId uint) ([]models.ChannelMember, error) {
var members []models.ChannelMember
if err := database.C.
Where(&models.ChannelMember{ChannelID: channelId}).
Preload("Account").
Find(&members).Error; err != nil {
return members, err
}
return members, nil
}
func InviteChannelMember(user models.Account, target models.Channel) error {
member := models.ChannelMember{
ChannelID: target.ID,
AccountID: user.ID,
}
err := database.C.Save(&member).Error
return err
}
func KickChannelMember(user models.Account, target models.Channel) error {
var member models.ChannelMember
if err := database.C.Where(&models.ChannelMember{
ChannelID: target.ID,
AccountID: user.ID,
}).First(&member).Error; err != nil {
return err
}
return database.C.Delete(&member).Error
}
func EditChannel(channel models.Channel, name, description string) (models.Channel, error) {
channel.Name = name
channel.Description = description
err := database.C.Save(&channel).Error
return channel, err
}
func DeleteChannel(channel models.Channel) error {
return database.C.Delete(&channel).Error
}

51
pkg/services/mailer.go Normal file
View File

@ -0,0 +1,51 @@
package services
import (
"crypto/tls"
"fmt"
"net/smtp"
"net/textproto"
"github.com/jordan-wright/email"
"github.com/spf13/viper"
)
func SendMail(target string, subject string, content string) error {
mail := &email.Email{
To: []string{target},
From: viper.GetString("mailer.name"),
Subject: subject,
Text: []byte(content),
Headers: textproto.MIMEHeader{},
}
return mail.SendWithTLS(
fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")),
smtp.PlainAuth(
"",
viper.GetString("mailer.username"),
viper.GetString("mailer.password"),
viper.GetString("mailer.smtp_host"),
),
&tls.Config{ServerName: viper.GetString("mailer.smtp_host")},
)
}
func SendMailHTML(target string, subject string, content string) error {
mail := &email.Email{
To: []string{target},
From: viper.GetString("mailer.name"),
Subject: subject,
HTML: []byte(content),
Headers: textproto.MIMEHeader{},
}
return mail.SendWithTLS(
fmt.Sprintf("%s:%d", viper.GetString("mailer.smtp_host"), viper.GetInt("mailer.smtp_port")),
smtp.PlainAuth(
"",
viper.GetString("mailer.username"),
viper.GetString("mailer.password"),
viper.GetString("mailer.smtp_host"),
),
&tls.Config{ServerName: viper.GetString("mailer.smtp_host")},
)
}