store: scope kind assignments, laps, and samples via activity ownership
None of these three tables gained their own user_id column -- they're always accessed through a specific activity, so ownership is checked via a join/subquery against activities.user_id instead.
This commit is contained in:
@@ -28,8 +28,18 @@ type KindAssignment struct {
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
// InsertKindAssignment appends a new assignment row for an activity.
|
||||
func (db *DB) InsertKindAssignment(ctx context.Context, a KindAssignment) (int64, error) {
|
||||
// InsertKindAssignment appends a new assignment row for an activity owned
|
||||
// by userID.
|
||||
func (db *DB) InsertKindAssignment(ctx context.Context, userID int64, a KindAssignment) (int64, error) {
|
||||
var exists int
|
||||
err := db.QueryRowContext(ctx, `SELECT 1 FROM activities WHERE id = ? AND user_id = ?`, a.ActivityID, userID).Scan(&exists)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, fmt.Errorf("insert kind assignment: activity %d not found for user %d", a.ActivityID, userID)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("insert kind assignment for activity %d (user %d): %w", a.ActivityID, userID, err)
|
||||
}
|
||||
|
||||
res, err := db.ExecContext(ctx, `
|
||||
INSERT INTO kind_assignments (activity_id, workout_kind_id, assignment_source, status, confidence, candidate_kinds_json)
|
||||
VALUES (?,?,?,?,?,?)`,
|
||||
@@ -46,29 +56,37 @@ func scanKindAssignment(row interface{ Scan(...any) error }) (KindAssignment, er
|
||||
return a, err
|
||||
}
|
||||
|
||||
const kindAssignmentColumns = `id, activity_id, workout_kind_id, assignment_source, status, confidence, candidate_kinds_json, created_at`
|
||||
const kindAssignmentColumns = `a.id, a.activity_id, a.workout_kind_id, a.assignment_source, a.status, a.confidence, a.candidate_kinds_json, a.created_at`
|
||||
|
||||
// CurrentAssignment returns the latest assignment for an activity, if any.
|
||||
func (db *DB) CurrentAssignment(ctx context.Context, activityID int64) (KindAssignment, bool, error) {
|
||||
row := db.QueryRowContext(ctx, `SELECT `+kindAssignmentColumns+` FROM current_kind_assignment WHERE activity_id = ?`, activityID)
|
||||
// CurrentAssignment returns the latest assignment for an activity owned by
|
||||
// userID, if any.
|
||||
func (db *DB) CurrentAssignment(ctx context.Context, userID, activityID int64) (KindAssignment, bool, error) {
|
||||
row := db.QueryRowContext(ctx, `
|
||||
SELECT `+kindAssignmentColumns+`
|
||||
FROM current_kind_assignment a
|
||||
JOIN activities ON activities.id = a.activity_id
|
||||
WHERE a.activity_id = ? AND activities.user_id = ?`, activityID, userID)
|
||||
a, err := scanKindAssignment(row)
|
||||
if err == sql.ErrNoRows {
|
||||
return KindAssignment{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return KindAssignment{}, false, fmt.Errorf("current assignment for activity %d: %w", activityID, err)
|
||||
return KindAssignment{}, false, fmt.Errorf("current assignment for activity %d (user %d): %w", activityID, userID, err)
|
||||
}
|
||||
return a, true, nil
|
||||
}
|
||||
|
||||
// ReviewQueue returns activities whose current assignment status is
|
||||
// needs_review, newest first.
|
||||
func (db *DB) ReviewQueue(ctx context.Context) ([]KindAssignment, error) {
|
||||
// ReviewQueue returns userID's activities whose current assignment status
|
||||
// is needs_review, newest first.
|
||||
func (db *DB) ReviewQueue(ctx context.Context, userID int64) ([]KindAssignment, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT `+kindAssignmentColumns+` FROM current_kind_assignment
|
||||
WHERE status = ? ORDER BY created_at DESC`, AssignmentStatusNeedsReview)
|
||||
SELECT `+kindAssignmentColumns+`
|
||||
FROM current_kind_assignment a
|
||||
JOIN activities ON activities.id = a.activity_id
|
||||
WHERE activities.user_id = ? AND a.status = ?
|
||||
ORDER BY a.created_at DESC`, userID, AssignmentStatusNeedsReview)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("review queue: %w", err)
|
||||
return nil, fmt.Errorf("review queue for user %d: %w", userID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
@@ -83,37 +101,44 @@ func (db *DB) ReviewQueue(ctx context.Context) ([]KindAssignment, error) {
|
||||
return assignments, rows.Err()
|
||||
}
|
||||
|
||||
// AllCurrentAssignments returns the latest assignment for every activity
|
||||
// that has one, regardless of status or source -- the basis for deciding
|
||||
// which activities a global reclassify pass is allowed to touch.
|
||||
func (db *DB) AllCurrentAssignments(ctx context.Context) ([]KindAssignment, error) {
|
||||
rows, err := db.QueryContext(ctx, `SELECT `+kindAssignmentColumns+` FROM current_kind_assignment`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("all current assignments: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
assignments := []KindAssignment{}
|
||||
for rows.Next() {
|
||||
a, err := scanKindAssignment(rows)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan kind assignment row: %w", err)
|
||||
}
|
||||
assignments = append(assignments, a)
|
||||
}
|
||||
return assignments, rows.Err()
|
||||
}
|
||||
|
||||
// AssignmentsForKind returns every historical assignment where the given
|
||||
// workout kind was the resolved kind (regardless of source), oldest first --
|
||||
// the basis for progression-over-time charts.
|
||||
func (db *DB) AssignmentsForKind(ctx context.Context, workoutKindID int64) ([]KindAssignment, error) {
|
||||
// AllCurrentAssignments returns the latest assignment for every one of
|
||||
// userID's activities that has one, regardless of status or source -- the
|
||||
// basis for deciding which activities a global reclassify pass may touch.
|
||||
func (db *DB) AllCurrentAssignments(ctx context.Context, userID int64) ([]KindAssignment, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT `+kindAssignmentColumns+` FROM current_kind_assignment
|
||||
WHERE workout_kind_id = ? AND status = ? ORDER BY created_at ASC`,
|
||||
workoutKindID, AssignmentStatusAssigned)
|
||||
SELECT `+kindAssignmentColumns+`
|
||||
FROM current_kind_assignment a
|
||||
JOIN activities ON activities.id = a.activity_id
|
||||
WHERE activities.user_id = ?`, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assignments for kind %d: %w", workoutKindID, err)
|
||||
return nil, fmt.Errorf("all current assignments for user %d: %w", userID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
assignments := []KindAssignment{}
|
||||
for rows.Next() {
|
||||
a, err := scanKindAssignment(rows)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan kind assignment row: %w", err)
|
||||
}
|
||||
assignments = append(assignments, a)
|
||||
}
|
||||
return assignments, rows.Err()
|
||||
}
|
||||
|
||||
// AssignmentsForKind returns every historical assignment (for userID's
|
||||
// activities) where the given workout kind was the resolved kind (regardless
|
||||
// of source), oldest first -- the basis for progression-over-time charts.
|
||||
func (db *DB) AssignmentsForKind(ctx context.Context, userID, workoutKindID int64) ([]KindAssignment, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT `+kindAssignmentColumns+`
|
||||
FROM current_kind_assignment a
|
||||
JOIN activities ON activities.id = a.activity_id
|
||||
WHERE activities.user_id = ? AND a.workout_kind_id = ? AND a.status = ?
|
||||
ORDER BY a.created_at ASC`,
|
||||
userID, workoutKindID, AssignmentStatusAssigned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assignments for kind %d (user %d): %w", workoutKindID, userID, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user