94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgerrcode"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
|
|
"telesrv/internal/domain"
|
|
"telesrv/internal/store/postgres/sqlcgen"
|
|
)
|
|
|
|
const (
|
|
peerUsernameTypeUser = "user"
|
|
peerUsernameTypeChannel = "channel"
|
|
)
|
|
|
|
type peerUsernameOwner struct {
|
|
peerType string
|
|
peerID int64
|
|
}
|
|
|
|
func (o peerUsernameOwner) matches(peerType string, peerID int64) bool {
|
|
return o.peerType == peerType && o.peerID == peerID
|
|
}
|
|
|
|
func getPeerUsernameOwner(ctx context.Context, db sqlcgen.DBTX, usernameLower string, forUpdate bool) (peerUsernameOwner, bool, error) {
|
|
if usernameLower == "" {
|
|
return peerUsernameOwner{}, false, nil
|
|
}
|
|
query := `SELECT peer_type, peer_id FROM peer_usernames WHERE username_lower = $1`
|
|
if forUpdate {
|
|
query += ` FOR UPDATE`
|
|
}
|
|
var owner peerUsernameOwner
|
|
err := db.QueryRow(ctx, query, usernameLower).Scan(&owner.peerType, &owner.peerID)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return peerUsernameOwner{}, false, nil
|
|
}
|
|
return peerUsernameOwner{}, false, fmt.Errorf("get peer username owner: %w", err)
|
|
}
|
|
return owner, true, nil
|
|
}
|
|
|
|
func peerUsernameAvailable(ctx context.Context, db sqlcgen.DBTX, usernameLower, peerType string, peerID int64) (bool, error) {
|
|
owner, found, err := getPeerUsernameOwner(ctx, db, usernameLower, false)
|
|
if err != nil || !found {
|
|
return !found, err
|
|
}
|
|
return owner.matches(peerType, peerID), nil
|
|
}
|
|
|
|
func replacePeerUsernameTx(ctx context.Context, tx pgx.Tx, peerType string, peerID int64, usernameLower string) error {
|
|
if usernameLower != "" {
|
|
owner, found, err := getPeerUsernameOwner(ctx, tx, usernameLower, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if found && !owner.matches(peerType, peerID) {
|
|
return domain.ErrUsernameOccupied
|
|
}
|
|
}
|
|
if _, err := tx.Exec(ctx, `DELETE FROM peer_usernames WHERE peer_type = $1 AND peer_id = $2`, peerType, peerID); err != nil {
|
|
return fmt.Errorf("delete peer username: %w", err)
|
|
}
|
|
if usernameLower == "" {
|
|
return nil
|
|
}
|
|
if _, err := tx.Exec(ctx, `
|
|
INSERT INTO peer_usernames (username_lower, peer_type, peer_id)
|
|
VALUES ($1, $2, $3)`, usernameLower, peerType, peerID); err != nil {
|
|
if isUniqueViolation(err) {
|
|
return domain.ErrUsernameOccupied
|
|
}
|
|
return fmt.Errorf("insert peer username: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deletePeerUsernameTx(ctx context.Context, tx pgx.Tx, peerType string, peerID int64) error {
|
|
if _, err := tx.Exec(ctx, `DELETE FROM peer_usernames WHERE peer_type = $1 AND peer_id = $2`, peerType, peerID); err != nil {
|
|
return fmt.Errorf("delete peer username: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isUniqueConstraint(err error, constraintName string) bool {
|
|
var pgErr *pgconn.PgError
|
|
return errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation && pgErr.ConstraintName == constraintName
|
|
}
|