package postgres import ( "context" "database/sql" "errors" "fmt" "git.aiterp.net/rpdata/api/database/postgres/psqlcore" "git.aiterp.net/rpdata/api/models" "strconv" "strings" ) var ErrNickConflict = errors.New("nick already in use by another character") type characterRepository struct { insertWithIDs bool db *sql.DB } func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) { row, err := psqlcore.New(r.db).SelectCharacterByID(ctx, id) if err != nil { return nil, err } return r.character(row), nil } func (r *characterRepository) FindNick(ctx context.Context, nick string) (*models.Character, error) { row, err := psqlcore.New(r.db).SelectCharacterByNick(ctx, nick) if err != nil { return nil, err } return r.character(psqlcore.SelectCharacterByIDRow(row)), nil } func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) { row, err := psqlcore.New(r.db).SelectCharacterByName(ctx, name) if err != nil { return nil, err } return r.character(psqlcore.SelectCharacterByIDRow(row)), nil } func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) { params := psqlcore.SelectCharactersParams{ LimitSize: 0, } if filter.IDs != nil { params.FilterID = true params.Ids = filter.IDs } if filter.Nicks != nil { params.FilterNick = true params.Nicks = filter.Nicks } if filter.Names != nil { params.FilterName = true params.Names = filter.Names } if filter.Author != nil { params.FilterAuthor = true params.Author = *filter.Author } if filter.Search != nil { params.FilterSearch = true params.Search = TSQueryFromSearch(*filter.Search) } if filter.Limit > 0 { params.LimitSize = int32(filter.Limit) } rows, err := psqlcore.New(r.db).SelectCharacters(ctx, params) if err != nil { return nil, err } return r.characters(rows), nil } func (r *characterRepository) Insert(ctx context.Context, character models.Character) (*models.Character, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() q := psqlcore.New(tx) if !r.insertWithIDs || character.ID == "" { next, err := q.IncrementCounter(ctx, "data_character_id") if err != nil { return nil, err } character.ID = fmt.Sprintf("C%d", next) } else { n, err := strconv.Atoi(character.ID[1:]) if err != nil { return nil, err } err = q.BumpCounter(ctx, psqlcore.BumpCounterParams{ID: "data_character_id", Value: int32(n)}) if err != nil { return nil, err } } rows, err := q.SelectCharacters(ctx, psqlcore.SelectCharactersParams{ FilterNick: true, Nicks: character.Nicks, LimitSize: 1, }) if err != nil { return nil, fmt.Errorf("failed to select: %s", err) } if len(rows) != 0 { return nil, ErrNickConflict } err = q.InsertCharacter(ctx, psqlcore.InsertCharacterParams{ ID: character.ID, Nicks: character.Nicks, Name: character.Name, ShortName: character.ShortName, Author: character.Author, Description: character.Description, }) if err != nil { return nil, err } err = tx.Commit() if err != nil { return nil, err } return &character, nil } func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) { character.ApplyUpdate(update) err := psqlcore.New(r.db).UpdateCharacter(ctx, psqlcore.UpdateCharacterParams{ ID: character.ID, Name: character.Name, ShortName: character.ShortName, Description: character.Description, }) if err != nil { return nil, err } return &character, nil } func (r *characterRepository) AddNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { return nil, err } defer func() { _ = tx.Rollback() }() q := psqlcore.New(tx) rows, err := q.SelectCharacters(ctx, psqlcore.SelectCharactersParams{ FilterNick: true, Nicks: []string{nick}, LimitSize: 1, }) if err != nil { return nil, err } if len(rows) != 0 { return nil, ErrNickConflict } err = q.AddCharacterNick(ctx, psqlcore.AddCharacterNickParams{ID: character.ID, Nick: nick}) if err != nil { return nil, err } character.Nicks = append(character.Nicks, nick) err = tx.Commit() if err != nil { return nil, err } return &character, nil } func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) { err := psqlcore.New(r.db).RemoveCharacterNick(ctx, psqlcore.RemoveCharacterNickParams{ID: character.ID, Nick: nick}) if err != nil { return nil, err } for i, nick2 := range character.Nicks { if nick2 == nick { character.Nicks = append(character.Nicks[:i], character.Nicks[i+1:]...) break } } return &character, nil } func (r *characterRepository) Delete(ctx context.Context, character models.Character) error { return psqlcore.New(r.db).DeleteCharacter(ctx, character.ID) } func (r *characterRepository) character(row psqlcore.SelectCharacterByIDRow) *models.Character { return &models.Character{ ID: strings.Trim(row.ID, " "), Nicks: row.Nicks, Name: row.Name, ShortName: row.ShortName, Author: row.Author, Description: row.Description, } } func (r *characterRepository) characters(rows []psqlcore.SelectCharactersRow) []*models.Character { results := make([]*models.Character, 0, len(rows)) for _, row := range rows { results = append(results, r.character(psqlcore.SelectCharacterByIDRow(row))) } return results }