Skip to main content

Integração de Exercícios

Este guia fornece exemplos práticos para integrar sua aplicação com o sistema de exercícios do FitLocus.

Visão Geral

A integração com o sistema de exercícios do FitLocus inclui:

  • Criação e gerenciamento de exercícios
  • Categorização e filtragem de exercícios
  • Upload e gerenciamento de mídias de exercícios
  • Registro e acompanhamento de execuções de exercícios

Requisitos Técnicos

Para integrar com o sistema de exercícios do FitLocus, você precisará:
  • Backend: Java 21, Spring Boot, Spring Data JPA
  • Frontend: Axios/fetch, biblioteca de upload de arquivos

Exemplos de Integração

Backend (Java 21 + Spring Boot)

Modelo de Exercício

@Entity
@Table(name = "exercises")
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Exercise {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(columnDefinition = "TEXT")
    private String description;

    @Enumerated(EnumType.STRING)
    @Column(name = "category", nullable = false)
    private ExerciseCategoryEnum category;

    @Column(name = "image_url")
    private String imageUrl;

    @Column(name = "video_url")
    private String videoUrl;

    @Column(name = "created_by")
    private Long createdBy;

    @Column(name = "is_public")
    private Boolean isPublic;

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

Serviço de Exercício

@Service
@Transactional
public class ExerciseService {
    private final ExerciseRepository exerciseRepository;
    private final FileStorageService fileStorageService;

    public ExerciseService(ExerciseRepository exerciseRepository, 
                          FileStorageService fileStorageService) {
        this.exerciseRepository = exerciseRepository;
        this.fileStorageService = fileStorageService;
    }

    public ExerciseDTO getExerciseById(Long id, Long userId) {
        Exercise exercise = exerciseRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Exercício não encontrado"));
        
        // Verificar se o usuário tem acesso ao exercício
        if (!exercise.getIsPublic() && !exercise.getCreatedBy().equals(userId)) {
            throw new AccessDeniedException("Acesso negado ao exercício");
        }
        
        return ExerciseDTO.fromEntity(exercise);
    }

    public List<ExerciseDTO> getAllExercises(Long userId) {
        List<Exercise> exercises = exerciseRepository.findAvailableExercises(userId);
        return exercises.stream()
                .map(ExerciseDTO::fromEntity)
                .collect(Collectors.toList());
    }

    // Outros métodos omitidos para brevidade
}

Frontend (React + TypeScript)

Serviço de Exercício

// src/services/exercise.service.ts
import api from './api';

export interface Exercise {
  id: number;
  name: string;
  description?: string;
  category: 'PEITO' | 'COSTAS' | 'PERNAS' | 'OMBROS' | 'BICEPS' | 'TRICEPS' | 'ABDOMEN' | 'GLUTEOS' | 'CARDIO' | 'ALONGAMENTO' | 'OUTRO';
  imageUrl?: string;
  videoUrl?: string;
  createdBy: number;
  isPublic: boolean;
  createdAt: string;
  updatedAt: string;
}

class ExerciseService {
  async getExerciseById(id: number): Promise<Exercise> {
    const response = await api.get<Exercise>(`/exercises/${id}`);
    return response.data;
  }

  async getAllExercises(): Promise<Exercise[]> {
    const response = await api.get<Exercise[]>('/exercises');
    return response.data;
  }

  // Outros métodos omitidos para brevidade
}

export default new ExerciseService();

Hook de Exercício

// src/hooks/useExercises.ts
import { useState, useEffect, useCallback } from 'react';
import exerciseService, { Exercise } from '../services/exercise.service';

export const useExercises = () => {
  const [exercises, setExercises] = useState<Exercise[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const fetchExercises = useCallback(async () => {
    setLoading(true);
    setError(null);

    try {
      const data = await exerciseService.getAllExercises();
      setExercises(data);
    } catch (err: any) {
      setError(err.response?.data?.message || 'Erro ao carregar exercícios');
      console.error(err);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    fetchExercises();
  }, [fetchExercises]);

  // Outros métodos omitidos para brevidade

  return {
    exercises,
    loading,
    error,
    fetchExercises,
    // Outros métodos
  };
};

Componente de Lista de Exercícios

// src/components/ExerciseList.tsx
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { useExercises } from '../hooks/useExercises';

const ExerciseList: React.FC = () => {
  const { exercises, loading, error, fetchExercises } = useExercises();
  const [selectedCategory, setSelectedCategory] = useState('TODOS');
  const [searchQuery, setSearchQuery] = useState('');

  // Implementação omitida para brevidade

  return (
    <div className="exercise-list">
      <div className="exercise-list-header">
        <h2>Exercícios</h2>
        <Link to="/exercises/new" className="button">Novo Exercício</Link>
      </div>
      
      {/* Filtros e lista de exercícios */}
      
      {exercises.length === 0 ? (
        <div className="empty-state">
          <p>Nenhum exercício encontrado.</p>
          <Link to="/exercises/new" className="button">Criar Exercício</Link>
        </div>
      ) : (
        <div className="exercise-grid">
          {exercises.map(exercise => (
            <ExerciseCard key={exercise.id} exercise={exercise} />
          ))}
        </div>
      )}
    </div>
  );
};

export default ExerciseList;

Melhores Práticas

Segurança

  1. Validação de Dados:
    • Valide todos os dados de entrada no backend e frontend
    • Utilize bibliotecas como Yup, Zod ou Bean Validation
  2. Controle de Acesso:
    • Implemente verificações de autorização em todos os endpoints
    • Utilize anotações como @PreAuthorize no Spring Security

Performance

  1. Paginação:
    • Implemente paginação para listas grandes de exercícios
    • Utilize Page<T> do Spring Data para paginação no backend
  2. Otimização de Imagens e Vídeos:
    • Utilize serviços de CDN para entrega de mídia
    • Implemente carregamento lazy de imagens e vídeos

Solução de Problemas

Erros Comuns

ErroCausa ProvávelSolução
404 Not FoundExercício não encontradoVerificar ID do exercício
403 ForbiddenPermissões insuficientesVerificar se o usuário tem acesso ao exercício
400 Bad RequestDados inválidosVerificar validação de dados

Recursos Adicionais