- Add friend search/list/request/requestCancel/response/expel handlers and schemas - Store friend relationships in user_friend and backfill default friend links at startup - Auto-add the default user as a friend for newly created accounts - Support target user_id in /api profile requests and return target profile data - Make profile accessory_info optional to avoid zero-value accessory payloads - Update login topInfo friend counters from friend relationship state Signed-off-by: Sean Du <do4suki@gmail.com>
380 lines
11 KiB
Go
380 lines
11 KiB
Go
package profile
|
|
|
|
import (
|
|
"errors"
|
|
unitmodel "honoka-chan/internal/model/unit"
|
|
usermodel "honoka-chan/internal/model/user"
|
|
profileapischema "honoka-chan/internal/schema/api/profile"
|
|
"honoka-chan/internal/session"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func profileInfo(ctx *gin.Context, targetUserID int) (res any, err error) {
|
|
ss := session.Get(ctx)
|
|
|
|
targetPref, err := getTargetUserPref(ss, targetUserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
targetUserID = targetPref.UserID
|
|
|
|
unitCount, err := ss.UserEng.Table(new(usermodel.UserUnitData)).
|
|
Where("user_id = ?", targetUserID).
|
|
Count()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lastLoginTime, err := getTargetLastLoginTime(ss, targetUserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
centerUnitOwningUserID, err := getMainDeckCenterUnitOwningUserID(ss, targetUserID, targetPref.UnitOwningUserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
centerUnitInfo, centerRaw, err := getProfileCenterUnitInfo(ss, targetUserID, centerUnitOwningUserID, targetPref.AwardID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
naviUnitInfo, naviRaw, err := getProfileNaviUnitInfo(ss, targetUserID, targetPref.UnitOwningUserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if naviRaw != nil {
|
|
centerUnitInfo.Costume = profileapischema.Costume{
|
|
UnitID: naviRaw.UnitID,
|
|
IsRankMax: naviRaw.IsRankMax,
|
|
IsSigned: naviRaw.IsSigned,
|
|
}
|
|
} else if centerRaw != nil {
|
|
centerUnitInfo.Costume = profileapischema.Costume{
|
|
UnitID: centerRaw.UnitID,
|
|
IsRankMax: centerRaw.IsRankMax,
|
|
IsSigned: centerRaw.IsSigned,
|
|
}
|
|
}
|
|
|
|
res = profileapischema.InfoResp{
|
|
Result: profileapischema.InfoData{
|
|
UserInfo: profileapischema.UserInfo{
|
|
UserID: targetPref.UserID,
|
|
Name: targetPref.UserName,
|
|
Level: targetPref.UserLevel,
|
|
CostMax: 100,
|
|
UnitMax: 5000,
|
|
EnergyMax: targetPref.EffectiveEnergyMax(),
|
|
FriendMax: 99,
|
|
UnitCnt: int(unitCount),
|
|
InviteCode: targetPref.InviteCode,
|
|
ElapsedTimeFromLogin: formatProfileElapsedTime(lastLoginTime),
|
|
Introduction: targetPref.UserDesc,
|
|
},
|
|
CenterUnitInfo: centerUnitInfo,
|
|
NaviUnitInfo: naviUnitInfo,
|
|
IsAlliance: false,
|
|
FriendStatus: 0,
|
|
SettingAwardID: targetPref.AwardID,
|
|
SettingBackgroundID: targetPref.BackgroundID,
|
|
},
|
|
Status: 200,
|
|
CommandNum: false,
|
|
TimeStamp: time.Now().Unix(),
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func getTargetUserPref(ss *session.Session, targetUserID int) (usermodel.UserPref, error) {
|
|
if targetUserID <= 0 || targetUserID == ss.UserID {
|
|
return ss.UserPref, nil
|
|
}
|
|
|
|
pref := usermodel.UserPref{}
|
|
has, err := ss.UserEng.Table(new(usermodel.UserPref)).
|
|
Where("user_id = ?", targetUserID).
|
|
Get(&pref)
|
|
if err != nil {
|
|
return usermodel.UserPref{}, err
|
|
}
|
|
if !has {
|
|
return usermodel.UserPref{}, errors.New("user not found")
|
|
}
|
|
if pref.NeedsProfileMigration() {
|
|
pref.ApplyProfileDefaults()
|
|
_, err = ss.UserEng.Table(new(usermodel.UserPref)).
|
|
Where("user_id = ?", targetUserID).
|
|
Cols(usermodel.UserPrefProfileColumns()...).
|
|
Update(&pref)
|
|
if err != nil {
|
|
return usermodel.UserPref{}, err
|
|
}
|
|
}
|
|
|
|
return pref, nil
|
|
}
|
|
|
|
func getTargetLastLoginTime(ss *session.Session, targetUserID int) (int64, error) {
|
|
if targetUserID == ss.UserID {
|
|
user := usermodel.Users{}
|
|
has, err := ss.UserEng.Table(new(usermodel.Users)).
|
|
Where("user_id = ?", targetUserID).
|
|
Get(&user)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if !has {
|
|
return 0, errors.New("user not found")
|
|
}
|
|
return user.LastLoginTime, nil
|
|
}
|
|
|
|
user := usermodel.Users{}
|
|
has, err := ss.UserEng.Table(new(usermodel.Users)).
|
|
Where("user_id = ?", targetUserID).
|
|
Get(&user)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if !has {
|
|
return 0, errors.New("user not found")
|
|
}
|
|
|
|
return user.LastLoginTime, nil
|
|
}
|
|
|
|
func getMainDeckCenterUnitOwningUserID(ss *session.Session, targetUserID, fallback int) (int, error) {
|
|
var unitOwningUserID int
|
|
has, err := ss.UserEng.Table(new(usermodel.UserDeckUnit)).Alias("udu").
|
|
Join("LEFT", "user_deck ud", "ud.id = udu.user_deck_id").
|
|
Where("udu.user_id = ?", targetUserID).
|
|
Where("ud.main_flag = ?", 1).
|
|
Where("udu.position = ?", 5).
|
|
Cols("udu.unit_owning_user_id").
|
|
Get(&unitOwningUserID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if has && unitOwningUserID > 0 {
|
|
return unitOwningUserID, nil
|
|
}
|
|
return fallback, nil
|
|
}
|
|
|
|
func getProfileCenterUnitInfo(ss *session.Session, targetUserID, unitOwningUserID, awardID int) (profileapischema.CenterUnitInfo, *unitmodel.UnitDataMap, error) {
|
|
unitData, err := getTargetUnitData(ss, targetUserID, unitOwningUserID)
|
|
if err != nil {
|
|
return profileapischema.CenterUnitInfo{}, nil, err
|
|
}
|
|
if unitData == nil {
|
|
return profileapischema.CenterUnitInfo{}, nil, nil
|
|
}
|
|
|
|
accessoryInfo, err := getAccessoryInfo(ss, targetUserID, unitOwningUserID)
|
|
if err != nil {
|
|
return profileapischema.CenterUnitInfo{}, nil, err
|
|
}
|
|
|
|
removeSkillIDs, err := getRemovableSkillIDs(ss, targetUserID, unitOwningUserID)
|
|
if err != nil {
|
|
return profileapischema.CenterUnitInfo{}, nil, err
|
|
}
|
|
|
|
info := profileapischema.CenterUnitInfo{
|
|
UnitOwningUserID: 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: removeSkillIDs,
|
|
AccessoryInfo: accessoryInfo,
|
|
TotalSmile: unitData.Smile,
|
|
TotalCute: unitData.Cute,
|
|
TotalCool: unitData.Cool,
|
|
TotalHp: unitData.MaxHp,
|
|
}
|
|
|
|
return info, unitData, nil
|
|
}
|
|
|
|
func getProfileNaviUnitInfo(ss *session.Session, targetUserID, unitOwningUserID int) (profileapischema.NaviUnitInfo, *unitmodel.UnitDataMap, error) {
|
|
unitData, err := getTargetUnitData(ss, targetUserID, unitOwningUserID)
|
|
if err != nil {
|
|
return profileapischema.NaviUnitInfo{}, nil, err
|
|
}
|
|
if unitData == nil {
|
|
return profileapischema.NaviUnitInfo{}, nil, nil
|
|
}
|
|
|
|
removeSkillIDs, err := getRemovableSkillIDs(ss, targetUserID, unitOwningUserID)
|
|
if err != nil {
|
|
return profileapischema.NaviUnitInfo{}, nil, err
|
|
}
|
|
|
|
info := profileapischema.NaviUnitInfo{
|
|
UnitOwningUserID: unitData.UnitOwningUserID,
|
|
UnitID: unitData.UnitID,
|
|
Exp: unitData.Exp,
|
|
NextExp: 0,
|
|
Level: unitData.Level,
|
|
MaxLevel: unitData.MaxLevel,
|
|
LevelLimitID: unitData.LevelLimitID,
|
|
Rank: unitData.Rank,
|
|
MaxRank: unitData.MaxRank,
|
|
Love: unitData.Love,
|
|
MaxLove: unitData.MaxLove,
|
|
UnitSkillExp: unitData.UnitSkillExp,
|
|
UnitSkillLevel: unitData.UnitSkillLevel,
|
|
MaxHp: unitData.MaxHp,
|
|
UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
|
|
FavoriteFlag: unitData.FavoriteFlag,
|
|
DisplayRank: unitData.DisplayRank,
|
|
IsRankMax: unitData.IsRankMax,
|
|
IsLoveMax: unitData.IsLoveMax,
|
|
IsLevelMax: unitData.IsLevelMax,
|
|
IsSigned: unitData.IsSigned,
|
|
IsSkillLevelMax: unitData.IsSkillLevelMax,
|
|
IsRemovableSkillCapacityMax: unitData.IsRemovableSkillCapacityMax,
|
|
InsertDate: "2016-10-11 10:33:03",
|
|
TotalSmile: unitData.Smile,
|
|
TotalCute: unitData.Cute,
|
|
TotalCool: unitData.Cool,
|
|
TotalHp: unitData.MaxHp,
|
|
RemovableSkillIds: removeSkillIDs,
|
|
}
|
|
|
|
return info, unitData, nil
|
|
}
|
|
|
|
func getTargetUnitData(ss *session.Session, targetUserID, unitOwningUserID int) (*unitmodel.UnitDataMap, error) {
|
|
if unitOwningUserID <= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
unitData := unitmodel.UnitDataMap{}
|
|
has, err := ss.GetBasicUnitInfo().
|
|
Where("a.user_id = ?", targetUserID).
|
|
Where("a.unit_owning_user_id = ?", unitOwningUserID).
|
|
Get(&unitData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, nil
|
|
}
|
|
|
|
return &unitData, nil
|
|
}
|
|
|
|
func getAccessoryInfo(ss *session.Session, targetUserID, unitOwningUserID int) (*profileapischema.AccessoryInfo, error) {
|
|
if unitOwningUserID <= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var accessoryOwningID int
|
|
has, err := ss.UserEng.Table("user_accessory_wear").
|
|
Where("unit_owning_user_id = ? AND user_id = ?", unitOwningUserID, targetUserID).
|
|
Cols("accessory_owning_user_id").
|
|
Get(&accessoryOwningID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has || accessoryOwningID <= 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 = ?", accessoryOwningID).
|
|
Cols("accessory_id,exp").
|
|
Get(&accessoryData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, nil
|
|
}
|
|
|
|
return &profileapischema.AccessoryInfo{
|
|
AccessoryOwningUserID: accessoryOwningID,
|
|
AccessoryID: accessoryData.AccessoryID,
|
|
Exp: accessoryData.Exp,
|
|
NextExp: 0,
|
|
Level: 8,
|
|
MaxLevel: 8,
|
|
RankUpCount: 4,
|
|
FavoriteFlag: true,
|
|
}, nil
|
|
}
|
|
|
|
func getRemovableSkillIDs(ss *session.Session, targetUserID, unitOwningUserID int) ([]int, error) {
|
|
removeSkillIDs := []int{}
|
|
if unitOwningUserID <= 0 {
|
|
return removeSkillIDs, nil
|
|
}
|
|
|
|
err := ss.UserEng.Table("user_unit_skill_equip").
|
|
Where("unit_owning_user_id = ? AND user_id = ?", unitOwningUserID, targetUserID).
|
|
Cols("unit_removable_skill_id").
|
|
Find(&removeSkillIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return removeSkillIDs, nil
|
|
}
|
|
|
|
func formatProfileElapsedTime(ts int64) string {
|
|
if ts <= 0 {
|
|
return "刚刚"
|
|
}
|
|
|
|
delta := time.Since(time.Unix(ts, 0))
|
|
if delta < time.Minute {
|
|
return "刚刚"
|
|
}
|
|
if delta < time.Hour {
|
|
return timeDurationInt(delta.Minutes()) + "分钟前"
|
|
}
|
|
if delta < 24*time.Hour {
|
|
return timeDurationInt(delta.Hours()) + "小时前"
|
|
}
|
|
return timeDurationInt(delta.Hours()/24) + "天前"
|
|
}
|
|
|
|
func timeDurationInt(value float64) string {
|
|
return strconv.Itoa(int(value))
|
|
}
|