package services import ( "context" "errors" "git.aiterp.net/rpdata/api/models" "git.aiterp.net/rpdata/api/models/changekeys" "git.aiterp.net/rpdata/api/repositories" "git.aiterp.net/rpdata/api/services/loaders" "log" "sort" "strings" "sync/atomic" "time" ) type CharacterService struct { characters repositories.CharacterRepository loader *loaders.CharacterLoader changeService *ChangeService authService *AuthService logService *LogService refreshSoftLock uint32 } // Find uses the loader to find the character by the ID. func (s *CharacterService) Find(ctx context.Context, id string) (*models.Character, error) { return s.loader.Load(id) } // Find uses the loader to find the character by the ID. func (s *CharacterService) FindNick(ctx context.Context, nick string) (*models.Character, error) { return s.characters.FindNick(ctx, nick) } // List lists the characters. If the only filter active is `IDs`, the loader is used to batch together requests. func (s *CharacterService) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) { if len(filter.IDs) > 0 && len(filter.Names) == 0 && len(filter.Nicks) == 0 && filter.Author == nil && filter.Search == nil { characters, errs := s.loader.LoadAll(filter.IDs) if len(characters) == 0 && len(errs) > 0 { if errs[0] == repositories.ErrNotFound { return []*models.Character{}, nil } else { return nil, errs[0] } } if err := ctx.Err(); err != nil { return nil, err } var badIndices []int for i, character := range characters { if character == nil { badIndices = append(badIndices, i-len(badIndices)) } } for _, index := range badIndices { characters = append(characters[:index], characters[index+1:]...) } sort.Slice(characters, func(i, j int) bool { if len(characters[i].ID) > len(characters[j].ID) { return true } if len(characters[i].ID) < len(characters[j].ID) { return false } return strings.Compare(characters[i].ID, characters[j].ID) < 0 }) return characters, nil } characters, err := s.characters.List(ctx, filter) if err != nil { return nil, err } sort.Slice(characters, func(i, j int) bool { if len(characters[i].ID) > len(characters[j].ID) { return false } if len(characters[i].ID) < len(characters[j].ID) { return true } return strings.Compare(characters[i].ID, characters[j].ID) < 0 }) return characters, nil } func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, author, description string) (*models.Character, error) { token := s.authService.TokenFromContext(ctx) if token == nil { return nil, ErrUnauthenticated } if name == "" { return nil, errors.New("name cannot be empty") } // Insert nick into existing if character already exists. if character, err := s.characters.FindName(ctx, name); err == nil && character.Name == name { return s.AddNick(ctx, character.ID, nick) } if author == "" { author = token.UserID } if shortName == "" { split := strings.SplitN(name, " ", 2) shortName = split[0] } character := &models.Character{ Name: name, ShortName: shortName, Author: author, Nicks: []string{nick}, Description: description, } err := s.authService.CheckPermission(ctx, "add", character) if err != nil { return nil, err } character, err = s.characters.Insert(ctx, *character) if err != nil { return nil, err } s.changeService.Submit(ctx, "Character", "add", true, changekeys.Listed(character), character) go s.refreshLogs() return character, nil } func (s *CharacterService) Update(ctx context.Context, id string, name, shortName, description *string) (*models.Character, error) { character, err := s.characters.Find(ctx, id) if err != nil { return nil, err } err = s.authService.CheckPermission(ctx, "edit", character) if err != nil { return nil, err } character, err = s.characters.Update(ctx, *character, models.CharacterUpdate{ Name: name, ShortName: shortName, Description: description, }) if err != nil { return nil, err } s.loader.Clear(character.ID) s.loader.Prime(character.ID, character) s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character) return character, nil } func (s *CharacterService) AddNick(ctx context.Context, id string, nick string) (*models.Character, error) { character, err := s.characters.Find(ctx, id) if err != nil { return nil, err } err = s.authService.CheckPermission(ctx, "edit", character) if err != nil { return nil, err } character, err = s.characters.AddNick(ctx, *character, nick) if err != nil { return nil, err } s.loader.Clear(character.ID) s.loader.Prime(character.ID, character) s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character) go s.refreshLogs() return character, nil } func (s *CharacterService) RemoveNick(ctx context.Context, id string, nick string) (*models.Character, error) { character, err := s.characters.Find(ctx, id) if err != nil { return nil, err } err = s.authService.CheckPermission(ctx, "edit", character) if err != nil { return nil, err } character, err = s.characters.RemoveNick(ctx, *character, nick) if err != nil { return nil, err } s.loader.Clear(character.ID) s.loader.Prime(character.ID, character) s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character) go s.refreshLogs() return character, nil } func (s *CharacterService) Delete(ctx context.Context, id string) (*models.Character, error) { character, err := s.characters.Find(ctx, id) if err != nil { return nil, err } err = s.authService.CheckPermission(ctx, "edit", character) if err != nil { return nil, err } err = s.characters.Delete(ctx, *character) if err != nil { return nil, err } s.loader.Clear(character.ID) s.changeService.Submit(ctx, "Character", "remove", true, changekeys.Listed(character), character) go s.refreshLogs() return character, nil } func (s *CharacterService) refreshLogs() { if !atomic.CompareAndSwapUint32(&s.refreshSoftLock, 0, 1) { return } time.Sleep(time.Second * 60) atomic.StoreUint32(&s.refreshSoftLock, 0) err := s.logService.RefreshAllLogCharacters(context.Background()) if err != nil { log.Println("Failed to refersh log characters:", err) return } }