mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
refactor(channelserver): extract Event, Achievement, Shop, and Cafe repositories
Move 22 raw SQL queries from 4 handler files into dedicated repository structs, continuing the repository extraction pattern. Achievement insert uses ON CONFLICT DO NOTHING to eliminate check-then-insert race, and IncrementScore validates the column index to prevent SQL injection.
This commit is contained in:
@@ -3,7 +3,6 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -98,20 +97,11 @@ func GetAchData(id uint8, score int32) Achievement {
|
|||||||
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
|
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
|
||||||
|
|
||||||
var exists int
|
if err := s.server.achievementRepo.EnsureExists(pkt.CharID); err != nil {
|
||||||
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists)
|
s.logger.Error("Failed to ensure achievements record", zap.Error(err))
|
||||||
if err != nil {
|
|
||||||
if _, err := s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID); err != nil {
|
|
||||||
s.logger.Error("Failed to insert achievements record", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var scores [33]int32
|
scores, err := s.server.achievementRepo.GetAllScores(pkt.CharID)
|
||||||
err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0],
|
|
||||||
&scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8],
|
|
||||||
&scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16],
|
|
||||||
&scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24],
|
|
||||||
&scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20))
|
||||||
return
|
return
|
||||||
@@ -165,15 +155,11 @@ func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var exists int
|
if err := s.server.achievementRepo.EnsureExists(s.charID); err != nil {
|
||||||
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists)
|
s.logger.Error("Failed to ensure achievements record", zap.Error(err))
|
||||||
if err != nil {
|
|
||||||
if _, err := s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID); err != nil {
|
|
||||||
s.logger.Error("Failed to insert achievements record", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID); err != nil {
|
if err := s.server.achievementRepo.IncrementScore(s.charID, pkt.AchievementID); err != nil {
|
||||||
s.logger.Error("Failed to update achievement score", zap.Error(err))
|
s.logger.Error("Failed to update achievement score", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if err := s.server.charRepo.ResetCafeTime(s.charID, cafeReset); err != nil {
|
if err := s.server.charRepo.ResetCafeTime(s.charID, cafeReset); err != nil {
|
||||||
s.logger.Error("Failed to reset cafe time", zap.Error(err))
|
s.logger.Error("Failed to reset cafe time", zap.Error(err))
|
||||||
}
|
}
|
||||||
if _, err := s.server.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, s.charID); err != nil {
|
if err := s.server.cafeRepo.ResetAccepted(s.charID); err != nil {
|
||||||
s.logger.Error("Failed to delete accepted cafe bonuses", zap.Error(err))
|
s.logger.Error("Failed to delete accepted cafe bonuses", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,14 +121,7 @@ func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
var count uint32
|
var count uint32
|
||||||
rows, err := s.server.db.Queryx(`
|
rows, err := s.server.cafeRepo.GetBonuses(s.charID)
|
||||||
SELECT cb.id, time_req, item_type, item_id, quantity,
|
|
||||||
(
|
|
||||||
SELECT count(*)
|
|
||||||
FROM cafe_accepted ca
|
|
||||||
WHERE cb.id = ca.cafe_id AND ca.character_id = $1
|
|
||||||
)::int::bool AS claimed
|
|
||||||
FROM cafebonus cb ORDER BY id ASC;`, s.charID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Error getting cafebonus", zap.Error(err))
|
s.logger.Error("Error getting cafebonus", zap.Error(err))
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -160,18 +153,7 @@ func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
var count uint32
|
var count uint32
|
||||||
bf.WriteUint32(0)
|
bf.WriteUint32(0)
|
||||||
rows, err := s.server.db.Queryx(`
|
rows, err := s.server.cafeRepo.GetClaimable(s.charID, TimeAdjusted().Unix()-s.sessionStart)
|
||||||
SELECT c.id, time_req, item_type, item_id, quantity
|
|
||||||
FROM cafebonus c
|
|
||||||
WHERE (
|
|
||||||
SELECT count(*)
|
|
||||||
FROM cafe_accepted ca
|
|
||||||
WHERE c.id = ca.cafe_id AND ca.character_id = $1
|
|
||||||
) < 1 AND (
|
|
||||||
SELECT ch.cafe_time + $2
|
|
||||||
FROM characters ch
|
|
||||||
WHERE ch.id = $1
|
|
||||||
) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart)
|
|
||||||
if err != nil || !mhfcourse.CourseExists(30, s.courses) {
|
if err != nil || !mhfcourse.CourseExists(30, s.courses) {
|
||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
} else {
|
} else {
|
||||||
@@ -195,17 +177,14 @@ func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
|
pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
|
||||||
var cafeBonus CafeBonus
|
|
||||||
for _, cbID := range pkt.CafeBonusID {
|
for _, cbID := range pkt.CafeBonusID {
|
||||||
err := s.server.db.QueryRow(`
|
itemType, quantity, err := s.server.cafeRepo.GetBonusItem(cbID)
|
||||||
SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1
|
|
||||||
`, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if cafeBonus.ItemType == 17 {
|
if itemType == 17 {
|
||||||
_ = addPointNetcafe(s, int(cafeBonus.Quantity))
|
_ = addPointNetcafe(s, int(quantity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err := s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID); err != nil {
|
if err := s.server.cafeRepo.AcceptBonus(cbID, s.charID); err != nil {
|
||||||
s.logger.Error("Failed to insert accepted cafe bonus", zap.Error(err))
|
s.logger.Error("Failed to insert accepted cafe bonus", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,13 +65,12 @@ func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range times {
|
for _, t := range times {
|
||||||
var temp activeFeature
|
temp, err := s.server.eventRepo.GetFeatureWeapon(t)
|
||||||
err := s.server.db.QueryRowx(`SELECT start_time, featured FROM feature_weapon WHERE start_time=$1`, t).StructScan(&temp)
|
|
||||||
if err != nil || temp.StartTime.IsZero() {
|
if err != nil || temp.StartTime.IsZero() {
|
||||||
weapons := token.RNG.Intn(s.server.erupeConfig.GameplayOptions.MaxFeatureWeapons-s.server.erupeConfig.GameplayOptions.MinFeatureWeapons+1) + s.server.erupeConfig.GameplayOptions.MinFeatureWeapons
|
weapons := token.RNG.Intn(s.server.erupeConfig.GameplayOptions.MaxFeatureWeapons-s.server.erupeConfig.GameplayOptions.MinFeatureWeapons+1) + s.server.erupeConfig.GameplayOptions.MinFeatureWeapons
|
||||||
temp = generateFeatureWeapons(weapons, s.server.erupeConfig.RealClientMode)
|
temp = generateFeatureWeapons(weapons, s.server.erupeConfig.RealClientMode)
|
||||||
temp.StartTime = t
|
temp.StartTime = t
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO feature_weapon VALUES ($1, $2)`, temp.StartTime, temp.ActiveFeatures); err != nil {
|
if err := s.server.eventRepo.InsertFeatureWeapon(temp.StartTime, temp.ActiveFeatures); err != nil {
|
||||||
s.logger.Error("Failed to insert feature weapon", zap.Error(err))
|
s.logger.Error("Failed to insert feature weapon", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +137,7 @@ func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
var loginBoosts []loginBoost
|
var loginBoosts []loginBoost
|
||||||
rows, err := s.server.db.Queryx("SELECT week_req, expiration, reset FROM login_boost WHERE char_id=$1 ORDER BY week_req", s.charID)
|
rows, err := s.server.eventRepo.GetLoginBoosts(s.charID)
|
||||||
if err != nil || s.server.erupeConfig.GameplayOptions.DisableLoginBoost {
|
if err != nil || s.server.erupeConfig.GameplayOptions.DisableLoginBoost {
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 35))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 35))
|
||||||
@@ -159,7 +158,7 @@ func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
{WeekReq: 5, Expiration: temp},
|
{WeekReq: 5, Expiration: temp},
|
||||||
}
|
}
|
||||||
for _, boost := range loginBoosts {
|
for _, boost := range loginBoosts {
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO login_boost VALUES ($1, $2, $3, $4)`, s.charID, boost.WeekReq, boost.Expiration, time.Time{}); err != nil {
|
if err := s.server.eventRepo.InsertLoginBoost(s.charID, boost.WeekReq, boost.Expiration, time.Time{}); err != nil {
|
||||||
s.logger.Error("Failed to insert login boost", zap.Error(err))
|
s.logger.Error("Failed to insert login boost", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +169,7 @@ func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if !boost.Reset.IsZero() && boost.Reset.Before(TimeAdjusted()) {
|
if !boost.Reset.IsZero() && boost.Reset.Before(TimeAdjusted()) {
|
||||||
boost.Expiration = TimeWeekStart()
|
boost.Expiration = TimeWeekStart()
|
||||||
boost.Reset = time.Time{}
|
boost.Reset = time.Time{}
|
||||||
if _, err := s.server.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, boost.Expiration, boost.Reset, s.charID, boost.WeekReq); err != nil {
|
if err := s.server.eventRepo.UpdateLoginBoost(s.charID, boost.WeekReq, boost.Expiration, boost.Reset); err != nil {
|
||||||
s.logger.Error("Failed to reset login boost", zap.Error(err))
|
s.logger.Error("Failed to reset login boost", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +214,7 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
expiration = TimeAdjusted().Add(240 * time.Minute)
|
expiration = TimeAdjusted().Add(240 * time.Minute)
|
||||||
}
|
}
|
||||||
bf.WriteUint32(uint32(expiration.Unix()))
|
bf.WriteUint32(uint32(expiration.Unix()))
|
||||||
if _, err := s.server.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, expiration, TimeWeekNext(), s.charID, pkt.BoostWeekUsed); err != nil {
|
if err := s.server.eventRepo.UpdateLoginBoost(s.charID, pkt.BoostWeekUsed, expiration, TimeWeekNext()); err != nil {
|
||||||
s.logger.Error("Failed to use login boost", zap.Error(err))
|
s.logger.Error("Failed to use login boost", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
|
|||||||
@@ -59,10 +59,7 @@ func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem, mode _config.Mode
|
|||||||
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
|
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
|
||||||
var items []ShopItem
|
var items []ShopItem
|
||||||
var temp ShopItem
|
var temp ShopItem
|
||||||
rows, err := s.server.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity,
|
rows, err := s.server.shopRepo.GetShopItems(shopType, shopID, s.charID)
|
||||||
COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity,
|
|
||||||
road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2
|
|
||||||
`, shopType, shopID, s.charID)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.StructScan(&temp)
|
err = rows.StructScan(&temp)
|
||||||
@@ -212,11 +209,7 @@ func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
buyCount := bf.ReadUint32()
|
buyCount := bf.ReadUint32()
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought)
|
if err := s.server.shopRepo.RecordPurchase(s.charID, itemHash, buyCount); err != nil {
|
||||||
VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id)
|
|
||||||
DO UPDATE SET bought = bought + $3
|
|
||||||
WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2
|
|
||||||
`, s.charID, itemHash, buyCount); err != nil {
|
|
||||||
s.logger.Error("Failed to update shop item purchase count", zap.Error(err))
|
s.logger.Error("Failed to update shop item purchase count", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,8 +228,8 @@ type FPointExchange struct {
|
|||||||
|
|
||||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||||
var itemValue, quantity int
|
quantity, itemValue, err := s.server.shopRepo.GetFpointItem(pkt.TradeID)
|
||||||
if err := s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue); err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to read fpoint item cost", zap.Error(err))
|
s.logger.Error("Failed to read fpoint item cost", zap.Error(err))
|
||||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||||
return
|
return
|
||||||
@@ -255,8 +248,8 @@ func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
||||||
var itemValue, quantity int
|
quantity, itemValue, err := s.server.shopRepo.GetFpointItem(pkt.TradeID)
|
||||||
if err := s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue); err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to read fpoint item value", zap.Error(err))
|
s.logger.Error("Failed to read fpoint item value", zap.Error(err))
|
||||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||||
return
|
return
|
||||||
@@ -280,7 +273,7 @@ func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
var exchange FPointExchange
|
var exchange FPointExchange
|
||||||
var exchanges []FPointExchange
|
var exchanges []FPointExchange
|
||||||
var buyables uint16
|
var buyables uint16
|
||||||
rows, err := s.server.db.Queryx(`SELECT id, item_type, item_id, quantity, fpoints, buyable FROM fpoint_items ORDER BY buyable DESC`)
|
rows, err := s.server.shopRepo.GetFpointExchangeList()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err = rows.StructScan(&exchange)
|
err = rows.StructScan(&exchange)
|
||||||
|
|||||||
44
server/channelserver/repo_achievement.go
Normal file
44
server/channelserver/repo_achievement.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AchievementRepository centralizes all database access for the achievements table.
|
||||||
|
type AchievementRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAchievementRepository creates a new AchievementRepository.
|
||||||
|
func NewAchievementRepository(db *sqlx.DB) *AchievementRepository {
|
||||||
|
return &AchievementRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureExists creates an achievements record for the character if one doesn't exist.
|
||||||
|
func (r *AchievementRepository) EnsureExists(charID uint32) error {
|
||||||
|
_, err := r.db.Exec("INSERT INTO achievements (id) VALUES ($1) ON CONFLICT DO NOTHING", charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllScores returns all 33 achievement scores for a character.
|
||||||
|
func (r *AchievementRepository) GetAllScores(charID uint32) ([33]int32, error) {
|
||||||
|
var scores [33]int32
|
||||||
|
err := r.db.QueryRow("SELECT * FROM achievements WHERE id=$1", charID).Scan(&scores[0],
|
||||||
|
&scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8],
|
||||||
|
&scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16],
|
||||||
|
&scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24],
|
||||||
|
&scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32])
|
||||||
|
return scores, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementScore increments the score for a specific achievement column.
|
||||||
|
// achievementID must be in the range [0, 32] to prevent SQL injection.
|
||||||
|
func (r *AchievementRepository) IncrementScore(charID uint32, achievementID uint8) error {
|
||||||
|
if achievementID > 32 {
|
||||||
|
return fmt.Errorf("achievement ID %d out of range [0, 32]", achievementID)
|
||||||
|
}
|
||||||
|
_, err := r.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", achievementID, achievementID), charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
61
server/channelserver/repo_cafe.go
Normal file
61
server/channelserver/repo_cafe.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CafeRepository centralizes all database access for cafe-related tables.
|
||||||
|
type CafeRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCafeRepository creates a new CafeRepository.
|
||||||
|
func NewCafeRepository(db *sqlx.DB) *CafeRepository {
|
||||||
|
return &CafeRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAccepted deletes all accepted cafe bonuses for a character.
|
||||||
|
func (r *CafeRepository) ResetAccepted(charID uint32) error {
|
||||||
|
_, err := r.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBonuses returns all cafe bonuses with their claimed status for a character.
|
||||||
|
func (r *CafeRepository) GetBonuses(charID uint32) (*sqlx.Rows, error) {
|
||||||
|
return r.db.Queryx(`
|
||||||
|
SELECT cb.id, time_req, item_type, item_id, quantity,
|
||||||
|
(
|
||||||
|
SELECT count(*)
|
||||||
|
FROM cafe_accepted ca
|
||||||
|
WHERE cb.id = ca.cafe_id AND ca.character_id = $1
|
||||||
|
)::int::bool AS claimed
|
||||||
|
FROM cafebonus cb ORDER BY id ASC;`, charID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClaimable returns unclaimed cafe bonuses where the character has enough accumulated time.
|
||||||
|
func (r *CafeRepository) GetClaimable(charID uint32, elapsedSec int64) (*sqlx.Rows, error) {
|
||||||
|
return r.db.Queryx(`
|
||||||
|
SELECT c.id, time_req, item_type, item_id, quantity
|
||||||
|
FROM cafebonus c
|
||||||
|
WHERE (
|
||||||
|
SELECT count(*)
|
||||||
|
FROM cafe_accepted ca
|
||||||
|
WHERE c.id = ca.cafe_id AND ca.character_id = $1
|
||||||
|
) < 1 AND (
|
||||||
|
SELECT ch.cafe_time + $2
|
||||||
|
FROM characters ch
|
||||||
|
WHERE ch.id = $1
|
||||||
|
) >= time_req`, charID, elapsedSec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBonusItem returns the item type and quantity for a specific cafe bonus.
|
||||||
|
func (r *CafeRepository) GetBonusItem(bonusID uint32) (itemType, quantity uint32, err error) {
|
||||||
|
err = r.db.QueryRow(`SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1`, bonusID).Scan(&bonusID, &itemType, &quantity)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcceptBonus records that a character has accepted a cafe bonus.
|
||||||
|
func (r *CafeRepository) AcceptBonus(bonusID, charID uint32) error {
|
||||||
|
_, err := r.db.Exec("INSERT INTO cafe_accepted VALUES ($1, $2)", bonusID, charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
47
server/channelserver/repo_event.go
Normal file
47
server/channelserver/repo_event.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventRepository centralizes all database access for event-related tables.
|
||||||
|
type EventRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEventRepository creates a new EventRepository.
|
||||||
|
func NewEventRepository(db *sqlx.DB) *EventRepository {
|
||||||
|
return &EventRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatureWeapon returns the featured weapon bitfield for a given start time.
|
||||||
|
func (r *EventRepository) GetFeatureWeapon(startTime time.Time) (activeFeature, error) {
|
||||||
|
var af activeFeature
|
||||||
|
err := r.db.QueryRowx(`SELECT start_time, featured FROM feature_weapon WHERE start_time=$1`, startTime).StructScan(&af)
|
||||||
|
return af, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertFeatureWeapon stores a new featured weapon entry.
|
||||||
|
func (r *EventRepository) InsertFeatureWeapon(startTime time.Time, features uint32) error {
|
||||||
|
_, err := r.db.Exec(`INSERT INTO feature_weapon VALUES ($1, $2)`, startTime, features)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLoginBoosts returns all login boost rows for a character, ordered by week_req.
|
||||||
|
func (r *EventRepository) GetLoginBoosts(charID uint32) (*sqlx.Rows, error) {
|
||||||
|
return r.db.Queryx("SELECT week_req, expiration, reset FROM login_boost WHERE char_id=$1 ORDER BY week_req", charID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertLoginBoost creates a new login boost entry.
|
||||||
|
func (r *EventRepository) InsertLoginBoost(charID uint32, weekReq uint8, expiration, reset time.Time) error {
|
||||||
|
_, err := r.db.Exec(`INSERT INTO login_boost VALUES ($1, $2, $3, $4)`, charID, weekReq, expiration, reset)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLoginBoost updates expiration and reset for a login boost entry.
|
||||||
|
func (r *EventRepository) UpdateLoginBoost(charID uint32, weekReq uint8, expiration, reset time.Time) error {
|
||||||
|
_, err := r.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, expiration, reset, charID, weekReq)
|
||||||
|
return err
|
||||||
|
}
|
||||||
44
server/channelserver/repo_shop.go
Normal file
44
server/channelserver/repo_shop.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShopRepository centralizes all database access for shop-related tables.
|
||||||
|
type ShopRepository struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShopRepository creates a new ShopRepository.
|
||||||
|
func NewShopRepository(db *sqlx.DB) *ShopRepository {
|
||||||
|
return &ShopRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShopItems returns shop items with per-character purchase counts.
|
||||||
|
func (r *ShopRepository) GetShopItems(shopType uint8, shopID uint32, charID uint32) (*sqlx.Rows, error) {
|
||||||
|
return r.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity,
|
||||||
|
COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity,
|
||||||
|
road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2
|
||||||
|
`, shopType, shopID, charID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordPurchase upserts a purchase record, adding to the bought count.
|
||||||
|
func (r *ShopRepository) RecordPurchase(charID, shopItemID, quantity uint32) error {
|
||||||
|
_, err := r.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought)
|
||||||
|
VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id)
|
||||||
|
DO UPDATE SET bought = bought + $3
|
||||||
|
WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2
|
||||||
|
`, charID, shopItemID, quantity)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFpointItem returns the quantity and fpoints cost for a frontier point item.
|
||||||
|
func (r *ShopRepository) GetFpointItem(tradeID uint32) (quantity, fpoints int, err error) {
|
||||||
|
err = r.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", tradeID).Scan(&quantity, &fpoints)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFpointExchangeList returns all frontier point exchange items ordered by buyable status.
|
||||||
|
func (r *ShopRepository) GetFpointExchangeList() (*sqlx.Rows, error) {
|
||||||
|
return r.db.Queryx(`SELECT id, item_type, item_id, quantity, fpoints, buyable FROM fpoint_items ORDER BY buyable DESC`)
|
||||||
|
}
|
||||||
@@ -58,6 +58,10 @@ type Server struct {
|
|||||||
stampRepo *StampRepository
|
stampRepo *StampRepository
|
||||||
distRepo *DistributionRepository
|
distRepo *DistributionRepository
|
||||||
sessionRepo *SessionRepository
|
sessionRepo *SessionRepository
|
||||||
|
eventRepo *EventRepository
|
||||||
|
achievementRepo *AchievementRepository
|
||||||
|
shopRepo *ShopRepository
|
||||||
|
cafeRepo *CafeRepository
|
||||||
erupeConfig *_config.Config
|
erupeConfig *_config.Config
|
||||||
acceptConns chan net.Conn
|
acceptConns chan net.Conn
|
||||||
deleteConns chan net.Conn
|
deleteConns chan net.Conn
|
||||||
@@ -140,6 +144,10 @@ func NewServer(config *Config) *Server {
|
|||||||
s.stampRepo = NewStampRepository(config.DB)
|
s.stampRepo = NewStampRepository(config.DB)
|
||||||
s.distRepo = NewDistributionRepository(config.DB)
|
s.distRepo = NewDistributionRepository(config.DB)
|
||||||
s.sessionRepo = NewSessionRepository(config.DB)
|
s.sessionRepo = NewSessionRepository(config.DB)
|
||||||
|
s.eventRepo = NewEventRepository(config.DB)
|
||||||
|
s.achievementRepo = NewAchievementRepository(config.DB)
|
||||||
|
s.shopRepo = NewShopRepository(config.DB)
|
||||||
|
s.cafeRepo = NewCafeRepository(config.DB)
|
||||||
|
|
||||||
// Mezeporta
|
// Mezeporta
|
||||||
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
||||||
|
|||||||
Reference in New Issue
Block a user