using GymLogAI.Application.Interfaces; using GymLogAI.Core.Abstractions; using GymLogAI.Core.Entities; using GymLogAI.Core.Enums; namespace GymLogAI.Application.Handlers; public sealed class ParseTelegramMessageHandler( ITelegramMessageRepository telegramMessageRepository, IWorkoutRepository workoutRepository, IExerciseRepository exerciseRepository, IWorkoutParser workoutParser, IEmbeddingService embeddingService, IClock clock, IIdGenerator idGenerator) { public async Task HandleAsync(Guid telegramMessageId, CancellationToken cancellationToken) { var message = await telegramMessageRepository.GetByIdAsync(telegramMessageId, cancellationToken); if (message is null) { return false; } message.ParsingStatus = ParsingStatus.Processing; message.ParsingError = null; await telegramMessageRepository.UpdateAsync(message, cancellationToken); try { var parsedWorkout = await workoutParser.ParseAsync(message.Text, cancellationToken); if (parsedWorkout is null || parsedWorkout.Exercises.Count == 0) { message.ParsingStatus = ParsingStatus.Failed; message.ParsingError = "Unable to parse workout from message."; message.ParsedAtUtc = clock.UtcNow; await telegramMessageRepository.UpdateAsync(message, cancellationToken); return false; } var workout = new Workout { Id = idGenerator.New(), UserId = message.UserId, SourceTelegramMessageId = message.Id, PerformedAtUtc = parsedWorkout.PerformedAtUtc, Title = parsedWorkout.Title, Notes = parsedWorkout.Notes, SourceType = WorkoutSourceType.Telegram, CreatedAtUtc = clock.UtcNow }; foreach (var parsedExercise in parsedWorkout.Exercises.OrderBy(x => x.Order)) { var exercise = await exerciseRepository.GetByNameAsync(parsedExercise.Name, cancellationToken) ?? await exerciseRepository.AddAsync(new Exercise { Id = idGenerator.New(), Name = parsedExercise.Name.Trim(), CreatedAtUtc = clock.UtcNow, Embedding = await embeddingService.GenerateEmbeddingAsync(parsedExercise.Name, cancellationToken) }, cancellationToken); var workoutExercise = new WorkoutExercise { Id = idGenerator.New(), WorkoutId = workout.Id, ExerciseId = exercise.Id, Order = parsedExercise.Order, Notes = parsedExercise.Notes }; foreach (var parsedSet in parsedExercise.Sets.OrderBy(x => x.Order)) { workoutExercise.Sets.Add(new ExerciseSet { Id = idGenerator.New(), WorkoutExerciseId = workoutExercise.Id, Order = parsedSet.Order, Repetitions = parsedSet.Repetitions, WeightKg = parsedSet.WeightKg, DurationSeconds = parsedSet.DurationSeconds, DistanceMeters = parsedSet.DistanceMeters, Notes = parsedSet.Notes }); } workout.WorkoutExercises.Add(workoutExercise); } await workoutRepository.AddAsync(workout, cancellationToken); message.WorkoutId = workout.Id; message.ParsingStatus = ParsingStatus.Parsed; message.ParsedAtUtc = clock.UtcNow; await telegramMessageRepository.UpdateAsync(message, cancellationToken); return true; } catch (Exception ex) { message.ParsingStatus = ParsingStatus.Failed; message.ParsingError = ex.Message; message.ParsedAtUtc = clock.UtcNow; await telegramMessageRepository.UpdateAsync(message, cancellationToken); return false; } } }