|
|
|
package themis
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
|
|
"go.opentelemetry.io/otel/codes"
|
|
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Conflict struct {
|
|
|
|
Province string
|
|
|
|
Player string
|
|
|
|
ClaimType ClaimType
|
|
|
|
Claim string
|
|
|
|
ClaimID int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c Conflict) String() string {
|
|
|
|
return fmt.Sprintf("%s owned by #%d %s %s (%s)", c.Province, c.ClaimID, c.ClaimType, c.Claim, c.Player)
|
|
|
|
}
|
|
|
|
|
|
|
|
const conflictQuery string = `WITH claiming AS (
|
|
|
|
SELECT province FROM claimables
|
|
|
|
WHERE claimables.typ = ?
|
|
|
|
AND claimables.name = ?
|
|
|
|
)
|
|
|
|
SELECT claimables.province, claims.player, claims.claim_type, claims.val, claims.id
|
|
|
|
FROM claims
|
|
|
|
INNER JOIN claimables
|
|
|
|
ON claims.claim_type = claimables.typ
|
|
|
|
AND claims.val = claimables.name
|
|
|
|
INNER JOIN claiming
|
|
|
|
ON claiming.province = claimables.province
|
|
|
|
WHERE claims.userid IS NOT ?;`
|
|
|
|
|
|
|
|
func (s *Store) FindConflicts(ctx context.Context, userId, name string, claimType ClaimType) ([]Conflict, error) {
|
|
|
|
ctx, span := tracer.Start(ctx, "find_conflicts", trace.WithAttributes(
|
|
|
|
semconv.DBSystemSqlite,
|
|
|
|
semconv.DBSQLTable("claims"),
|
|
|
|
semconv.DBOperation("select"),
|
|
|
|
attribute.String("user_id", userId),
|
|
|
|
attribute.String("claim_name", name),
|
|
|
|
attribute.Stringer("claim_type", claimType),
|
|
|
|
))
|
|
|
|
defer span.End()
|
|
|
|
|
|
|
|
log.Debug().Ctx(ctx).Stringer("claim_type", claimType).Str("userid", userId).Msg("searching for potential conflicts")
|
|
|
|
|
|
|
|
stmt, err := s.db.PrepareContext(ctx, conflictQuery)
|
|
|
|
if err != nil {
|
|
|
|
span.RecordError(err)
|
|
|
|
span.SetStatus(codes.Error, "failed to prepare query")
|
|
|
|
return nil, fmt.Errorf("failed to prepare conflicts query: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
rows, err := stmt.QueryContext(ctx, claimType, name, userId)
|
|
|
|
if err != nil {
|
|
|
|
span.RecordError(err)
|
|
|
|
span.SetStatus(codes.Error, "failed to execute query")
|
|
|
|
return nil, fmt.Errorf("failed to get conflicting provinces: %w", err)
|
|
|
|
}
|
|
|
|
defer stmt.Close()
|
|
|
|
|
|
|
|
conflicts := make([]Conflict, 0)
|
|
|
|
for rows.Next() {
|
|
|
|
var (
|
|
|
|
province string
|
|
|
|
player string
|
|
|
|
sClaimType string
|
|
|
|
claimName string
|
|
|
|
claimId int
|
|
|
|
)
|
|
|
|
err = rows.Scan(&province, &player, &sClaimType, &claimName, &claimId)
|
|
|
|
if err != nil {
|
|
|
|
span.RecordError(err)
|
|
|
|
span.SetStatus(codes.Error, "failed to scan row")
|
|
|
|
return nil, fmt.Errorf("failed to scan row: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ct, err := ClaimTypeFromString(sClaimType)
|
|
|
|
if err != nil {
|
|
|
|
// In case of an error parsing the claim type, simply default to
|
|
|
|
// whatever the database sends; this is a read-only function, the
|
|
|
|
// input validation is assumed to have already been done at insert.
|
|
|
|
ct = ClaimType(sClaimType)
|
|
|
|
}
|
|
|
|
conflicts = append(conflicts, Conflict{
|
|
|
|
Province: province,
|
|
|
|
Player: player,
|
|
|
|
ClaimType: ct,
|
|
|
|
Claim: claimName,
|
|
|
|
ClaimID: claimId,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return conflicts, nil
|
|
|
|
}
|