diff --git a/wger/core/api/views.py b/wger/core/api/views.py index 9e2af7f22..e305ad649 100644 --- a/wger/core/api/views.py +++ b/wger/core/api/views.py @@ -516,6 +516,15 @@ def upload_powersync_data(request): ps_manager.handle_update_log(payload=data['data'], user_id=user_id) elif http_verb == 'DELETE': ps_manager.handle_delete_log(payload=data['data'], user_id=user_id) + + case 'manager_workoutsession': + if http_verb == 'PUT': + ps_manager.handle_create_session(payload=data['data'], user_id=user_id) + elif http_verb == 'PATCH': + ps_manager.handle_update_session(payload=data['data'], user_id=user_id) + elif http_verb == 'DELETE': + ps_manager.handle_delete_session(payload=data['data'], user_id=user_id) + case _: logger.warning('Received unknown PowerSync table') raise ValueError('Unknown PowerSync table') diff --git a/wger/manager/migrations/0024_workoutlog_uuid.py b/wger/manager/migrations/0024_workoutlog_uuid.py index 5dfbf0b11..ffba838ca 100644 --- a/wger/manager/migrations/0024_workoutlog_uuid.py +++ b/wger/manager/migrations/0024_workoutlog_uuid.py @@ -6,18 +6,28 @@ from django.db import migrations, models def gen_uuids(apps, schema_editor): WorkoutLog = apps.get_model('manager', 'WorkoutLog') + WorkoutSession = apps.get_model('manager', 'WorkoutSession') for entry in WorkoutLog.objects.all(): entry.uuid = uuid.uuid4() entry.save(update_fields=['uuid']) -class Migration(migrations.Migration): + for entry in WorkoutSession.objects.all(): + entry.uuid = uuid.uuid4() + entry.save(update_fields=['uuid']) + +class Migration(migrations.Migration): dependencies = [ ('manager', '0023_change_validators'), ] operations = [ + migrations.AddField( + model_name='workoutsession', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False, null=True), + ), migrations.AddField( model_name='workoutlog', name='uuid', @@ -29,6 +39,11 @@ class Migration(migrations.Migration): reverse_code=migrations.RunPython.noop, ), # Set uuid fields to non-nullable + migrations.AlterField( + model_name='workoutsession', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=True, null=False), + ), migrations.AlterField( model_name='workoutlog', name='uuid', diff --git a/wger/manager/models/log.py b/wger/manager/models/log.py index 5060d5d0d..fab93c4e8 100644 --- a/wger/manager/models/log.py +++ b/wger/manager/models/log.py @@ -16,6 +16,7 @@ # Standard Library import datetime +from uuid import uuid4 # Django from django.contrib.auth.models import User @@ -49,6 +50,12 @@ class WorkoutLog(models.Model): objects = WorkoutLogManager() + uuid = models.UUIDField( + default=uuid4, + editable=True, + null=False, + ) + date = models.DateTimeField( verbose_name=_('Date'), default=datetime.datetime.now, diff --git a/wger/manager/models/session.py b/wger/manager/models/session.py index 76011c2c5..f1342018c 100644 --- a/wger/manager/models/session.py +++ b/wger/manager/models/session.py @@ -16,6 +16,7 @@ # Standard Library import datetime +from uuid import uuid4 # Django from django.contrib.auth.models import User @@ -53,6 +54,12 @@ class WorkoutSession(models.Model): See note in weight.models.WeightEntry about why this is not editable=False """ + uuid = models.UUIDField( + default=uuid4, + editable=True, + null=False, + ) + routine = models.ForeignKey( 'Routine', on_delete=models.CASCADE, diff --git a/wger/manager/powersync.py b/wger/manager/powersync.py index ba14c179d..4e7b604b2 100644 --- a/wger/manager/powersync.py +++ b/wger/manager/powersync.py @@ -17,8 +17,14 @@ import logging # wger -from wger.manager.api.serializers import WorkoutLogSerializer -from wger.manager.models import WorkoutLog +from wger.manager.api.serializers import ( + WorkoutLogSerializer, + WorkoutSessionSerializer, +) +from wger.manager.models import ( + WorkoutLog, + WorkoutSession, +) from wger.weight.api.serializers import WeightEntrySerializer from wger.weight.models import WeightEntry @@ -69,3 +75,48 @@ def handle_delete_log(payload: dict[str, any], user_id: int) -> None: logger.warning(f'WorkoutLog with UUID {payload["uuid"]} not found for delete.') return entry.delete() + + +def handle_update_session(payload: dict[str, any], user_id: int) -> None: + """Handle a push event from PowerSync""" + logger.debug( + f'Received PowerSync payload for update: {payload}', + ) + entry = WorkoutSession.objects.get(uuid=payload['id'], user_id=user_id) + + if not entry: + logger.warning( + f'WorkoutSession with UUID {payload["id"]} and user {user_id} not found for update.' + ) + return + + serializer = WorkoutSessionSerializer(entry, data=payload, partial=True) + if serializer.is_valid(): + serializer.save() + logger.info(f'Updated WorkoutSession {entry.pk} (uuid={entry.uuid}) for user {user_id}') + else: + logger.warning(f'PowerSync update validation failed: {serializer.errors}') + + +def handle_create_session(payload: dict[str, any], user_id: int) -> None: + """Handle a create event from PowerSync""" + logger.debug( + f'Received PowerSync payload for create: {payload}', + ) + serializer = WorkoutSessionSerializer(data=payload) + if serializer.is_valid(): + serializer.save(user_id=user_id) + else: + logger.warning(f'PowerSync create validation failed: {serializer.errors}') + + +def handle_delete_session(payload: dict[str, any], user_id: int) -> None: + """Handle a delete event from PowerSync""" + logger.debug( + f'Received PowerSync payload for delete: {payload}', + ) + entry = WorkoutSession.objects.get(uuid=payload['id'], user_id=user_id) + if not entry: + logger.warning(f'WorkoutSession with UUID {payload["uuid"]} not found for delete.') + return + entry.delete()