Codebase hardening: 102 fixes across 35+ files

Deep audit identified 106 findings; 102 fixed, 4 deferred. Covers 8 areas:

- Settings & deploy: env-gated DEBUG/SECRET_KEY, HTTPS headers, gunicorn, celery worker
- Auth (registered_user): password write_only, request.data fixes, transaction safety, proper HTTP status codes
- Workout app: IDOR protection, get_object_or_404, prefetch_related N+1 fixes, transaction.atomic
- Video/scripts: path traversal sanitization, HLS trigger guard, auth on cache wipe
- Models (exercise/equipment/muscle/superset): null-safe __str__, stable IDs, prefetch support
- Generator views: helper for registered_user lookup, logger.exception, bulk_update, transaction wrapping
- Generator core (rules/selector/generator): push-pull ratio, type affinity normalization, modality checks, side-pair exact match, word-boundary regex, equipment cache clearing
- Generator services (plan_builder/analyzer/normalizer): transaction.atomic, muscle cache, bulk_update, glutes classification fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-27 22:29:14 -06:00
parent 63b57a83ab
commit c80c66c2e5
58 changed files with 3363 additions and 1049 deletions

View File

@@ -34,11 +34,13 @@ class CompleteWorkoutSerializer(serializers.ModelSerializer):
difficulty=validated_data['difficulty'],
total_time=validated_data['total_time'],
workout_start_time=validated_data['workout_start_time'],
notes=validated_data['notes']
# Fix #5: KeyError 'notes' - use .get() with default
notes=validated_data.get('notes', '')
)
if "health_kit_workout_uuid" in validated_data:
completed_workout.workout_uuid = validated_data['health_kit_workout_uuid']
completed_workout.save()
# Fix #6: wrong attribute name - model field is health_kit_workout_uuid
completed_workout.health_kit_workout_uuid = validated_data['health_kit_workout_uuid']
completed_workout.save()
return completed_workout
class WorkoutSerializer(serializers.ModelSerializer):
@@ -53,25 +55,48 @@ class WorkoutSerializer(serializers.ModelSerializer):
fields = '__all__'
# depth = 1
def get_muscles(self, obj):
def get_muscles(self, obj):
# Fix #16: Use prefetched data when available, fall back to query
if hasattr(obj, '_prefetched_objects_cache') and 'superset_set' in obj._prefetched_objects_cache:
exercise_ids = []
for superset in obj.superset_set.all():
for se in superset.supersetexercise_set.all():
exercise_ids.append(se.exercise_id)
if not exercise_ids:
return []
muscles_names = ExerciseMuscle.objects.filter(exercise__id__in=exercise_ids).values_list('muscle__name', flat=True)
return list(set(muscles_names))
superset_ids = Superset.objects.filter(workout=obj).values_list('id')
exercise_ids = SupersetExercise.objects.filter(superset__id__in=superset_ids).values_list('exercise__id')
muscles_names = ExerciseMuscle.objects.filter(exercise__id__in=exercise_ids).values_list('muscle__name', flat=True)
return list(set(muscles_names))
# muscles_names = ExerciseMuscle.objects.filter(exercise__id__in=exercises).values_list('muscle__name', flat=True)
# return list(set(muscles_names))
def get_equipment(self, obj):
def get_equipment(self, obj):
# Fix #16: Use prefetched data when available, fall back to query
if hasattr(obj, '_prefetched_objects_cache') and 'superset_set' in obj._prefetched_objects_cache:
exercise_ids = []
for superset in obj.superset_set.all():
for se in superset.supersetexercise_set.all():
exercise_ids.append(se.exercise_id)
if not exercise_ids:
return []
equipment_names = WorkoutEquipment.objects.filter(exercise__id__in=exercise_ids).values_list('equipment__name', flat=True)
return list(set(equipment_names))
superset_ids = Superset.objects.filter(workout=obj).values_list('id')
exercise_ids = SupersetExercise.objects.filter(superset__id__in=superset_ids).values_list('exercise__id')
exercise_ids = SupersetExercise.objects.filter(superset__id__in=superset_ids).values_list('exercise__id')
equipment_names = WorkoutEquipment.objects.filter(exercise__id__in=exercise_ids).values_list('equipment__name', flat=True)
return list(set(equipment_names))
def get_exercise_count(self, obj):
def get_exercise_count(self, obj):
# Fix #16: Use prefetched data when available, fall back to query
returnValue = 0
if hasattr(obj, '_prefetched_objects_cache') and 'superset_set' in obj._prefetched_objects_cache:
for superset in obj.superset_set.all():
exercise_count = len(superset.supersetexercise_set.all())
returnValue += (superset.rounds * exercise_count)
return returnValue
supersets = Superset.objects.filter(workout=obj)
for superset in supersets:
for superset in supersets:
exercise_count = SupersetExercise.objects.filter(superset=superset).count()
returnValue += (superset.rounds * exercise_count)
return returnValue
@@ -106,8 +131,7 @@ class WorkoutDetailSerializer(serializers.ModelSerializer):
return data
def get_registered_user(self, obj):
objs = RegisteredUser.objects.get(pk=obj.registered_user.pk)
data = GetRegisteredUserSerializer(objs, many=False).data
data = GetRegisteredUserSerializer(obj.registered_user, many=False).data
return data
class GetCompleteWorkoutSerializer(serializers.ModelSerializer):
@@ -142,5 +166,5 @@ class POSTPlannedWorkoutSerializer(serializers.ModelSerializer):
workout=validated_data['workout'],
on_date=validated_data['on_date']
)
planned_workout.save()
# Fix #18: removed redundant save() right after create()
return planned_workout