GraphQL API and utilities for the rpdata project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
6.3 KiB

  1. package services
  2. import (
  3. "context"
  4. "errors"
  5. "git.aiterp.net/rpdata/api/models"
  6. "git.aiterp.net/rpdata/api/models/changekeys"
  7. "git.aiterp.net/rpdata/api/repositories"
  8. "git.aiterp.net/rpdata/api/services/loaders"
  9. "log"
  10. "sort"
  11. "strings"
  12. "sync/atomic"
  13. "time"
  14. )
  15. type CharacterService struct {
  16. characters repositories.CharacterRepository
  17. loader *loaders.CharacterLoader
  18. changeService *ChangeService
  19. authService *AuthService
  20. logService *LogService
  21. refreshSoftLock uint32
  22. }
  23. // Find uses the loader to find the character by the ID.
  24. func (s *CharacterService) Find(ctx context.Context, id string) (*models.Character, error) {
  25. return s.loader.Load(id)
  26. }
  27. // Find uses the loader to find the character by the ID.
  28. func (s *CharacterService) FindNick(ctx context.Context, nick string) (*models.Character, error) {
  29. return s.characters.FindNick(ctx, nick)
  30. }
  31. // List lists the characters. If the only filter active is `IDs`, the loader is used to batch together requests.
  32. func (s *CharacterService) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
  33. if len(filter.IDs) > 0 && len(filter.Names) == 0 && len(filter.Nicks) == 0 && filter.Author == nil && filter.Search == nil {
  34. characters, errs := s.loader.LoadAll(filter.IDs)
  35. if len(characters) == 0 && len(errs) > 0 {
  36. if errs[0] == repositories.ErrNotFound {
  37. return []*models.Character{}, nil
  38. } else {
  39. return nil, errs[0]
  40. }
  41. }
  42. if err := ctx.Err(); err != nil {
  43. return nil, err
  44. }
  45. var badIndices []int
  46. for i, character := range characters {
  47. if character == nil {
  48. badIndices = append(badIndices, i-len(badIndices))
  49. }
  50. }
  51. for _, index := range badIndices {
  52. characters = append(characters[:index], characters[index+1:]...)
  53. }
  54. sort.Slice(characters, func(i, j int) bool {
  55. if len(characters[i].ID) > len(characters[j].ID) {
  56. return true
  57. }
  58. if len(characters[i].ID) < len(characters[j].ID) {
  59. return false
  60. }
  61. return strings.Compare(characters[i].ID, characters[j].ID) < 0
  62. })
  63. return characters, nil
  64. }
  65. characters, err := s.characters.List(ctx, filter)
  66. if err != nil {
  67. return nil, err
  68. }
  69. sort.Slice(characters, func(i, j int) bool {
  70. if len(characters[i].ID) > len(characters[j].ID) {
  71. return false
  72. }
  73. if len(characters[i].ID) < len(characters[j].ID) {
  74. return true
  75. }
  76. return strings.Compare(characters[i].ID, characters[j].ID) < 0
  77. })
  78. return characters, nil
  79. }
  80. func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, author, description string) (*models.Character, error) {
  81. token := s.authService.TokenFromContext(ctx)
  82. if token == nil {
  83. return nil, ErrUnauthenticated
  84. }
  85. if name == "" {
  86. return nil, errors.New("name cannot be empty")
  87. }
  88. // Insert nick into existing if character already exists.
  89. if character, err := s.characters.FindName(ctx, name); err == nil && character.Name == name {
  90. return s.AddNick(ctx, character.ID, nick)
  91. }
  92. if author == "" {
  93. author = token.UserID
  94. }
  95. if shortName == "" {
  96. split := strings.SplitN(name, " ", 2)
  97. shortName = split[0]
  98. }
  99. character := &models.Character{
  100. Name: name,
  101. ShortName: shortName,
  102. Author: author,
  103. Nicks: []string{nick},
  104. Description: description,
  105. }
  106. err := s.authService.CheckPermission(ctx, "add", character)
  107. if err != nil {
  108. return nil, err
  109. }
  110. character, err = s.characters.Insert(ctx, *character)
  111. if err != nil {
  112. return nil, err
  113. }
  114. s.changeService.Submit(ctx, "Character", "add", true, changekeys.Listed(character), character)
  115. go s.refreshLogs()
  116. return character, nil
  117. }
  118. func (s *CharacterService) Update(ctx context.Context, id string, name, shortName, description *string) (*models.Character, error) {
  119. character, err := s.characters.Find(ctx, id)
  120. if err != nil {
  121. return nil, err
  122. }
  123. err = s.authService.CheckPermission(ctx, "edit", character)
  124. if err != nil {
  125. return nil, err
  126. }
  127. character, err = s.characters.Update(ctx, *character, models.CharacterUpdate{
  128. Name: name,
  129. ShortName: shortName,
  130. Description: description,
  131. })
  132. if err != nil {
  133. return nil, err
  134. }
  135. s.loader.Clear(character.ID)
  136. s.loader.Prime(character.ID, character)
  137. s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character)
  138. return character, nil
  139. }
  140. func (s *CharacterService) AddNick(ctx context.Context, id string, nick string) (*models.Character, error) {
  141. character, err := s.characters.Find(ctx, id)
  142. if err != nil {
  143. return nil, err
  144. }
  145. err = s.authService.CheckPermission(ctx, "edit", character)
  146. if err != nil {
  147. return nil, err
  148. }
  149. character, err = s.characters.AddNick(ctx, *character, nick)
  150. if err != nil {
  151. return nil, err
  152. }
  153. s.loader.Clear(character.ID)
  154. s.loader.Prime(character.ID, character)
  155. s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character)
  156. go s.refreshLogs()
  157. return character, nil
  158. }
  159. func (s *CharacterService) RemoveNick(ctx context.Context, id string, nick string) (*models.Character, error) {
  160. character, err := s.characters.Find(ctx, id)
  161. if err != nil {
  162. return nil, err
  163. }
  164. err = s.authService.CheckPermission(ctx, "edit", character)
  165. if err != nil {
  166. return nil, err
  167. }
  168. character, err = s.characters.RemoveNick(ctx, *character, nick)
  169. if err != nil {
  170. return nil, err
  171. }
  172. s.loader.Clear(character.ID)
  173. s.loader.Prime(character.ID, character)
  174. s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character)
  175. go s.refreshLogs()
  176. return character, nil
  177. }
  178. func (s *CharacterService) Delete(ctx context.Context, id string) (*models.Character, error) {
  179. character, err := s.characters.Find(ctx, id)
  180. if err != nil {
  181. return nil, err
  182. }
  183. err = s.authService.CheckPermission(ctx, "edit", character)
  184. if err != nil {
  185. return nil, err
  186. }
  187. err = s.characters.Delete(ctx, *character)
  188. if err != nil {
  189. return nil, err
  190. }
  191. s.loader.Clear(character.ID)
  192. s.changeService.Submit(ctx, "Character", "remove", true, changekeys.Listed(character), character)
  193. go s.refreshLogs()
  194. return character, nil
  195. }
  196. func (s *CharacterService) refreshLogs() {
  197. if !atomic.CompareAndSwapUint32(&s.refreshSoftLock, 0, 1) {
  198. return
  199. }
  200. time.Sleep(time.Second * 60)
  201. atomic.StoreUint32(&s.refreshSoftLock, 0)
  202. err := s.logService.RefreshAllLogCharacters(context.Background())
  203. if err != nil {
  204. log.Println("Failed to refersh log characters:", err)
  205. return
  206. }
  207. }