Implement greet inbox and outbox

- Add user_greet model and startup table sync
- Implement /greet/user and /greet/delete handlers
- Implement noticeFriendGreeting inbox and noticeUserGreetingHistory outbox
- Return peer profile and center unit info for greeting notices
- Update login topInfo friend greet counters from unread inbox messages

Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
2026-06-05 01:54:59 +08:00
parent d48293be44
commit 101f7e8a3f
16 changed files with 641 additions and 15 deletions
+180
View File
@@ -0,0 +1,180 @@
package notice
import (
unitmodel "honoka-chan/internal/model/unit"
usermodel "honoka-chan/internal/model/user"
noticeschema "honoka-chan/internal/schema/notice"
"honoka-chan/internal/session"
"strconv"
"time"
)
type greetingPeerRow struct {
UserID int `xorm:"user_id"`
UserName string `xorm:"user_name"`
UserLevel int `xorm:"user_level"`
AwardID int `xorm:"award_id"`
CenterUnitOwningUserID int `xorm:"center_unit_owning_user_id"`
}
func getGreetingPeer(ss *session.Session, userID int) (noticeschema.GreetingPeer, error) {
row := greetingPeerRow{}
has, err := ss.UserEng.Table(new(usermodel.UserPref)).Alias("up").
Join("LEFT", "user_deck ud", "ud.user_id = up.user_id AND ud.main_flag = 1").
Join("LEFT", "user_deck_unit udu", "udu.user_deck_id = ud.id AND udu.position = 5").
Select(`
up.user_id,
up.user_name,
up.user_level,
up.award_id,
COALESCE(udu.unit_owning_user_id, up.unit_owning_user_id) AS center_unit_owning_user_id
`).
Where("up.user_id = ?", userID).
Get(&row)
if err != nil {
return noticeschema.GreetingPeer{}, err
}
if !has {
return noticeschema.GreetingPeer{}, nil
}
centerUnitInfo, err := buildGreetingCenterUnitInfo(ss, userID, row.CenterUnitOwningUserID, row.AwardID)
if err != nil {
return noticeschema.GreetingPeer{}, err
}
return noticeschema.GreetingPeer{
UserData: noticeschema.GreetingUserData{
UserID: row.UserID,
Name: row.UserName,
Level: row.UserLevel,
},
CenterUnitInfo: centerUnitInfo,
SettingAwardID: row.AwardID,
}, nil
}
func buildGreetingCenterUnitInfo(ss *session.Session, userID, unitOwningUserID, awardID int) (noticeschema.GreetingCenterUnitInfo, error) {
info := noticeschema.GreetingCenterUnitInfo{
SettingAwardID: awardID,
RemovableSkillIds: []int{},
}
if unitOwningUserID <= 0 {
return info, nil
}
unitData := unitmodel.UnitDataMap{}
has, err := ss.GetBasicUnitInfo().
Where("a.user_id = ?", userID).
Where("a.unit_owning_user_id = ?", unitOwningUserID).
Get(&unitData)
if err != nil {
return info, err
}
if !has {
return info, nil
}
accessoryInfo, err := getGreetingAccessoryInfo(ss, userID, unitOwningUserID)
if err != nil {
return info, err
}
removableSkillIDs := []int{}
err = ss.UserEng.Table(new(usermodel.UserUnitSkillEquip)).
Where("user_id = ? AND unit_owning_user_id = ?", userID, unitOwningUserID).
Cols("unit_removable_skill_id").
Find(&removableSkillIDs)
if err != nil {
return info, err
}
return noticeschema.GreetingCenterUnitInfo{
UnitOwningUserID: int64(unitData.UnitOwningUserID),
UnitID: unitData.UnitID,
Exp: unitData.Exp,
NextExp: 0,
Level: unitData.Level,
LevelLimitID: unitData.LevelLimitID,
MaxLevel: unitData.MaxLevel,
Rank: unitData.Rank,
MaxRank: unitData.MaxRank,
Love: unitData.Love,
MaxLove: unitData.MaxLove,
UnitSkillLevel: unitData.UnitSkillLevel,
MaxHp: unitData.MaxHp,
FavoriteFlag: unitData.FavoriteFlag,
DisplayRank: unitData.DisplayRank,
UnitSkillExp: unitData.UnitSkillExp,
UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
Attribute: unitData.Attribute,
Smile: unitData.Smile,
Cute: unitData.Cute,
Cool: unitData.Cool,
IsLoveMax: unitData.IsLoveMax,
IsLevelMax: unitData.IsLevelMax,
IsRankMax: unitData.IsRankMax,
IsSigned: unitData.IsSigned,
IsSkillLevelMax: unitData.IsSkillLevelMax,
SettingAwardID: awardID,
RemovableSkillIds: removableSkillIDs,
AccessoryInfo: accessoryInfo,
}, nil
}
func getGreetingAccessoryInfo(ss *session.Session, userID, unitOwningUserID int) (*noticeschema.GreetingAccessoryInfo, error) {
accessoryWear := usermodel.UserAccessoryWear{}
has, err := ss.UserEng.Table(new(usermodel.UserAccessoryWear)).
Where("user_id = ? AND unit_owning_user_id = ?", userID, unitOwningUserID).
Get(&accessoryWear)
if err != nil {
return nil, err
}
if !has || accessoryWear.AccessoryOwningUserID <= 0 {
return nil, nil
}
accessoryData := struct {
AccessoryID int `xorm:"accessory_id"`
Exp int `xorm:"exp"`
}{}
has, err = ss.MainEng.Table("common_accessory_m").
Where("accessory_owning_user_id = ?", accessoryWear.AccessoryOwningUserID).
Cols("accessory_id,exp").
Get(&accessoryData)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return &noticeschema.GreetingAccessoryInfo{
AccessoryOwningUserID: accessoryWear.AccessoryOwningUserID,
AccessoryID: accessoryData.AccessoryID,
Exp: accessoryData.Exp,
NextExp: 0,
Level: 8,
MaxLevel: 8,
RankUpCount: 4,
FavoriteFlag: true,
}, nil
}
func formatGreetingElapsedTime(ts int64) string {
if ts <= 0 {
return "刚刚"
}
delta := time.Since(time.Unix(ts, 0))
if delta < time.Minute {
return "刚刚"
}
if delta < time.Hour {
return strconv.Itoa(int(delta.Minutes())) + "分钟前"
}
if delta < 24*time.Hour {
return strconv.Itoa(int(delta.Hours())) + "小时前"
}
return strconv.Itoa(int(delta.Hours())/24) + "天前"
}