297 lines
6.9 KiB
Go
297 lines
6.9 KiB
Go
package listing
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
ErrDependencyUnavailable = errors.New("dependency unavailable")
|
|
ErrInvalidInput = errors.New("invalid listing input")
|
|
ErrListingLocked = errors.New("listing locked")
|
|
ErrMissingTitle = errors.New("missing listing title")
|
|
ErrMissingServerRegion = errors.New("missing server region")
|
|
ErrInvalidPrice = errors.New("invalid listing price")
|
|
ErrInvalidDeposit = errors.New("invalid listing deposit")
|
|
ErrInvalidHafCoin = errors.New("invalid haf coin amount")
|
|
ErrMissingScreenshot = errors.New("missing screenshot")
|
|
)
|
|
|
|
type Service struct {
|
|
repo *Repository
|
|
config ConfigReader
|
|
}
|
|
|
|
type ConfigReader interface {
|
|
FindValue(key string) (string, error)
|
|
}
|
|
|
|
const (
|
|
reviewRequiredConfigKey = "listing.review_required"
|
|
publishOptionsConfigKey = "listing.publish_options"
|
|
defaultFireLevelMin = 38
|
|
)
|
|
|
|
type FireLevelTooLowError struct {
|
|
Min int
|
|
}
|
|
|
|
func (e FireLevelTooLowError) Error() string {
|
|
return "fire level too low"
|
|
}
|
|
|
|
func NewService(repo *Repository, config ConfigReader) *Service {
|
|
return &Service{repo: repo, config: config}
|
|
}
|
|
|
|
func (s *Service) Create(ownerID uint64, req CreateRequest) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
rules, err := s.publishRules()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := validateRequest(req, rules); err != nil {
|
|
return nil, err
|
|
}
|
|
reviewRequired, err := s.reviewRequired()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.Create(ownerID, req, reviewRequired)
|
|
}
|
|
|
|
func (s *Service) Update(ownerID uint64, id uint64, req UpdateRequest) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
rules, err := s.publishRules()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := validateRequest(req, rules); err != nil {
|
|
return nil, err
|
|
}
|
|
reviewRequired, err := s.reviewRequired()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.Update(ownerID, id, req, reviewRequired)
|
|
}
|
|
|
|
func (s *Service) SubmitReview(ownerID uint64, id uint64) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
reviewRequired, err := s.reviewRequired()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.SubmitReview(ownerID, id, reviewRequired)
|
|
}
|
|
|
|
func (s *Service) ListPendingReview() ([]ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.ListPendingReview()
|
|
}
|
|
|
|
func (s *Service) ListAdmin(query AdminListQuery) ([]ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.ListAdmin(query)
|
|
}
|
|
|
|
func (s *Service) FindAdmin(id uint64) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.FindAdmin(id)
|
|
}
|
|
|
|
func (s *Service) AdminOffline(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
if req.Reason == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
return s.repo.AdminOffline(adminID, id, req, meta)
|
|
}
|
|
|
|
func (s *Service) AdminMarkAbnormal(adminID uint64, id uint64, req AdminActionRequest, meta AuditMeta) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
if req.Reason == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
return s.repo.AdminMarkAbnormal(adminID, id, req, meta)
|
|
}
|
|
|
|
func (s *Service) Approve(id uint64) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.Approve(id)
|
|
}
|
|
|
|
func (s *Service) Reject(id uint64, req ReviewRequest) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
if req.Reason == "" {
|
|
return nil, ErrInvalidInput
|
|
}
|
|
return s.repo.Reject(id, req)
|
|
}
|
|
|
|
func (s *Service) Offline(ownerID uint64, id uint64) error {
|
|
if s.repo == nil {
|
|
return ErrDependencyUnavailable
|
|
}
|
|
return s.repo.Offline(ownerID, id)
|
|
}
|
|
|
|
func (s *Service) ListPublic() ([]ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.ListPublic()
|
|
}
|
|
|
|
func (s *Service) ListMine(ownerID uint64) ([]ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.ListMine(ownerID)
|
|
}
|
|
|
|
func (s *Service) FindPublic(id uint64) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.FindPublic(id)
|
|
}
|
|
|
|
func (s *Service) FindPublicCoverKey(id uint64) (string, error) {
|
|
if s.repo == nil {
|
|
return "", ErrDependencyUnavailable
|
|
}
|
|
return s.repo.FindPublicCoverKey(id)
|
|
}
|
|
|
|
func (s *Service) FindPublicScreenshotKey(id uint64, index int) (string, error) {
|
|
if s.repo == nil {
|
|
return "", ErrDependencyUnavailable
|
|
}
|
|
return s.repo.FindPublicScreenshotKey(id, index)
|
|
}
|
|
|
|
func (s *Service) FindMine(ownerID uint64, id uint64) (*ListingDTO, error) {
|
|
if s.repo == nil {
|
|
return nil, ErrDependencyUnavailable
|
|
}
|
|
return s.repo.FindMine(ownerID, id)
|
|
}
|
|
|
|
type publishRules struct {
|
|
FireLevelMin int
|
|
}
|
|
|
|
func validateRequest(req CreateRequest, rules publishRules) error {
|
|
if strings.TrimSpace(req.Title) == "" {
|
|
return ErrMissingTitle
|
|
}
|
|
if strings.TrimSpace(req.ServerRegion) == "" {
|
|
return ErrMissingServerRegion
|
|
}
|
|
if normalizedListingPrice(req) <= 0 {
|
|
return ErrInvalidPrice
|
|
}
|
|
if req.DepositAmount < 0 {
|
|
return ErrInvalidDeposit
|
|
}
|
|
if req.HafCoinAmount < 0 {
|
|
return ErrInvalidHafCoin
|
|
}
|
|
if !hasScreenshotURL(req.ScreenshotURLS) {
|
|
return ErrMissingScreenshot
|
|
}
|
|
if fireLevel, ok := readFireLevel(req.AssetSummary); ok && fireLevel < rules.FireLevelMin {
|
|
return FireLevelTooLowError{Min: rules.FireLevelMin}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readFireLevel(summary map[string]any) (int, bool) {
|
|
if summary == nil {
|
|
return 0, false
|
|
}
|
|
value, ok := summary["fire_level"]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch current := value.(type) {
|
|
case float64:
|
|
return int(current), true
|
|
case int:
|
|
return current, true
|
|
case string:
|
|
parsed, err := strconv.Atoi(strings.TrimSpace(current))
|
|
return parsed, err == nil
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func hasScreenshotURL(urls []string) bool {
|
|
for _, url := range urls {
|
|
if strings.TrimSpace(url) != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Service) reviewRequired() (bool, error) {
|
|
if s.config == nil {
|
|
return false, nil
|
|
}
|
|
value, err := s.config.FindValue(reviewRequiredConfigKey)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
required, err := strconv.ParseBool(strings.TrimSpace(value))
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return required, nil
|
|
}
|
|
|
|
func (s *Service) publishRules() (publishRules, error) {
|
|
rules := publishRules{FireLevelMin: defaultFireLevelMin}
|
|
if s.config == nil {
|
|
return rules, nil
|
|
}
|
|
value, err := s.config.FindValue(publishOptionsConfigKey)
|
|
if err != nil {
|
|
return rules, err
|
|
}
|
|
var raw struct {
|
|
FireLevelMin int `json:"fire_level_min"`
|
|
}
|
|
if err := json.Unmarshal([]byte(value), &raw); err != nil {
|
|
return rules, nil
|
|
}
|
|
if raw.FireLevelMin > 0 {
|
|
rules.FireLevelMin = raw.FireLevelMin
|
|
}
|
|
return rules, nil
|
|
}
|