Implement friend APIs and target profile support
- 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>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
unitmodel "honoka-chan/internal/model/unit"
|
||||
usermodel "honoka-chan/internal/model/user"
|
||||
profileapischema "honoka-chan/internal/schema/api/profile"
|
||||
@@ -11,148 +12,368 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func profileInfo(ctx *gin.Context) (res any, err error) {
|
||||
func profileInfo(ctx *gin.Context, targetUserID int) (res any, err error) {
|
||||
ss := session.Get(ctx)
|
||||
pref := ss.UserPref
|
||||
displayUserID := pref.UserID
|
||||
if inviteUserID, convErr := strconv.Atoi(pref.InviteCode); convErr == nil && inviteUserID > 0 {
|
||||
displayUserID = inviteUserID
|
||||
|
||||
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 = ?", ss.UserID).Count()
|
||||
Where("user_id = ?", targetUserID).
|
||||
Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unitData := unitmodel.UnitDataMap{}
|
||||
_, err = ss.GetBasicUnitInfo().
|
||||
Where("a.unit_owning_user_id = ?", pref.UnitOwningUserID).Get(&unitData)
|
||||
lastLoginTime, err := getTargetLastLoginTime(ss, targetUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var accessoryOwningId, accessoryId, exp int
|
||||
_, err = ss.UserEng.Table("user_accessory_wear").Where("unit_owning_user_id = ? AND user_id = ?", pref.UnitOwningUserID, ss.UserID).
|
||||
Cols("accessory_owning_user_id").Get(&accessoryOwningId)
|
||||
centerUnitOwningUserID, err := getMainDeckCenterUnitOwningUserID(ss, targetUserID, targetPref.UnitOwningUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = ss.MainEng.Table("common_accessory_m").Where("accessory_owning_user_id = ?", accessoryOwningId).
|
||||
Cols("accessory_id,exp").Get(&accessoryId, &exp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accessoryInfo := profileapischema.AccessoryInfo{
|
||||
AccessoryOwningUserID: accessoryOwningId,
|
||||
AccessoryID: accessoryId,
|
||||
Exp: exp,
|
||||
NextExp: 0,
|
||||
Level: 8,
|
||||
MaxLevel: 8,
|
||||
RankUpCount: 4,
|
||||
FavoriteFlag: true,
|
||||
}
|
||||
|
||||
removeSkillIds := []int{}
|
||||
err = ss.UserEng.Table("user_unit_skill_equip").Where("unit_owning_user_id = ? AND user_id = ?", pref.UnitOwningUserID, ss.UserID).
|
||||
Cols("unit_removable_skill_id").Find(&removeSkillIds)
|
||||
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: displayUserID,
|
||||
Name: pref.UserName,
|
||||
Level: pref.UserLevel,
|
||||
UserID: targetPref.UserID,
|
||||
Name: targetPref.UserName,
|
||||
Level: targetPref.UserLevel,
|
||||
CostMax: 100,
|
||||
UnitMax: 5000,
|
||||
EnergyMax: pref.EffectiveEnergyMax(),
|
||||
EnergyMax: targetPref.EffectiveEnergyMax(),
|
||||
FriendMax: 99,
|
||||
UnitCnt: int(unitCount),
|
||||
InviteCode: pref.InviteCode,
|
||||
ElapsedTimeFromLogin: "14\u5c0f\u65f6\u524d",
|
||||
Introduction: pref.UserDesc,
|
||||
},
|
||||
// TODO: 区分队伍中心卡片和看板卡片
|
||||
CenterUnitInfo: 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: pref.AwardID,
|
||||
RemovableSkillIds: removeSkillIds,
|
||||
AccessoryInfo: accessoryInfo,
|
||||
Costume: profileapischema.Costume{},
|
||||
TotalSmile: unitData.Smile, // TODO: 加成计算
|
||||
TotalCute: unitData.Cute, // 同上
|
||||
TotalCool: unitData.Cool, // 同上
|
||||
TotalHp: unitData.MaxHp,
|
||||
},
|
||||
NaviUnitInfo: 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,
|
||||
IsLoveMax: unitData.IsLoveMax,
|
||||
IsLevelMax: unitData.IsLevelMax,
|
||||
IsRankMax: unitData.IsRankMax,
|
||||
IsSigned: unitData.IsSigned,
|
||||
IsSkillLevelMax: unitData.IsSkillLevelMax,
|
||||
IsRemovableSkillCapacityMax: unitData.IsRemovableSkillCapacityMax,
|
||||
InsertDate: "2016-10-11 10:33:03",
|
||||
TotalSmile: unitData.Smile, // TODO: 加成计算
|
||||
TotalCute: unitData.Cute, // 同上
|
||||
TotalCool: unitData.Cool, // 同上
|
||||
TotalHp: unitData.MaxHp,
|
||||
RemovableSkillIds: removeSkillIds,
|
||||
InviteCode: targetPref.InviteCode,
|
||||
ElapsedTimeFromLogin: formatProfileElapsedTime(lastLoginTime),
|
||||
Introduction: targetPref.UserDesc,
|
||||
},
|
||||
CenterUnitInfo: centerUnitInfo,
|
||||
NaviUnitInfo: naviUnitInfo,
|
||||
IsAlliance: false,
|
||||
FriendStatus: 0,
|
||||
SettingAwardID: pref.AwardID,
|
||||
SettingBackgroundID: pref.BackgroundID,
|
||||
SettingAwardID: targetPref.AwardID,
|
||||
SettingBackgroundID: targetPref.BackgroundID,
|
||||
},
|
||||
Status: 200,
|
||||
CommandNum: false,
|
||||
TimeStamp: time.Now().Unix(),
|
||||
}
|
||||
|
||||
return res, err
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ProfileApi(ctx *gin.Context, action string) (res any, err error) {
|
||||
func ProfileApi(ctx *gin.Context, action string, targetUserID int) (res any, err error) {
|
||||
switch action {
|
||||
case "cardRanking":
|
||||
res, err = cardRanking()
|
||||
case "liveCnt":
|
||||
res, err = liveCnt()
|
||||
case "profileInfo":
|
||||
res, err = profileInfo(ctx)
|
||||
res, err = profileInfo(ctx, targetUserID)
|
||||
default:
|
||||
err = fmt.Errorf("unimplemented action: profile: %s", action)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user