Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
438 changes: 438 additions & 0 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.example.workoutloggerdemo.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
class SecurityConfig {

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.csrf { it.disable() }
.authorizeHttpRequests { authorize ->
authorize
.requestMatchers("/api/users/register").permitAll()
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/h2-console/**").permitAll() // For development only
.anyRequest().permitAll() // For development, allow all requests without authentication
// In production, use: .anyRequest().authenticated()
}
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.headers { headers ->
headers.frameOptions { it.sameOrigin() } // For H2 console
}

return http.build()
}

@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.example.workoutloggerdemo.controller

import com.example.workoutloggerdemo.model.Athlete
import com.example.workoutloggerdemo.service.AthleteService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException

@RestController
@RequestMapping("/api/athletes")
class AthleteController(private val athleteService: AthleteService) {

@GetMapping
fun getAllAthletes(): ResponseEntity<List<Athlete>> {
return ResponseEntity.ok(athleteService.getAllAthletes())
}

@GetMapping("/{id}")
fun getAthleteById(@PathVariable id: Long): ResponseEntity<Athlete> {
return try {
ResponseEntity.ok(athleteService.findById(id))
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}

@GetMapping("/user/{userId}")
fun getAthleteByUserId(@PathVariable userId: Long): ResponseEntity<Athlete> {
return try {
ResponseEntity.ok(athleteService.findByUserId(userId))
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}

@PostMapping
fun createAthlete(@RequestBody request: CreateAthleteRequest): ResponseEntity<Athlete> {
return try {
val athlete = athleteService.createAthlete(
userId = request.userId,
height = request.height,
weight = request.weight,
fitnessGoals = request.fitnessGoals
)
ResponseEntity.status(HttpStatus.CREATED).body(athlete)
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
}
}

@PutMapping("/{id}")
fun updateAthlete(
@PathVariable id: Long,
@RequestBody request: UpdateAthleteRequest
): ResponseEntity<Athlete> {
return try {
val athlete = athleteService.updateAthlete(
id = id,
height = request.height,
weight = request.weight,
fitnessGoals = request.fitnessGoals
)
ResponseEntity.ok(athlete)
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}

@PutMapping("/user/{userId}")
fun updateAthleteByUserId(
@PathVariable userId: Long,
@RequestBody request: UpdateAthleteRequest
): ResponseEntity<Athlete> {
return try {
val athlete = athleteService.updateAthleteByUserId(
userId = userId,
height = request.height,
weight = request.weight,
fitnessGoals = request.fitnessGoals
)
ResponseEntity.ok(athlete)
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}

@DeleteMapping("/{id}")
fun deleteAthlete(@PathVariable id: Long): ResponseEntity<Unit> {
return try {
athleteService.deleteAthlete(id)
ResponseEntity.noContent().build()
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}
}

data class CreateAthleteRequest(
val userId: Long,
val height: Double? = null,
val weight: Double? = null,
val fitnessGoals: String? = null
)

data class UpdateAthleteRequest(
val height: Double? = null,
val weight: Double? = null,
val fitnessGoals: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example.workoutloggerdemo.controller

import com.example.workoutloggerdemo.service.UserService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException

@RestController
@RequestMapping("/api/auth")
class AuthController(
private val userService: UserService,
private val passwordEncoder: PasswordEncoder
) {

@PostMapping("/login")
fun login(@RequestBody request: LoginRequest): ResponseEntity<LoginResponse> {
try {
val user = userService.findByUsername(request.username)

if (!passwordEncoder.matches(request.password, user.passwordHash)) {
throw BadCredentialsException("Invalid password")
}

// In a real application, we would generate and return a JWT token here
// For simplicity, we'll just return a success message with the user ID

return ResponseEntity.ok(LoginResponse(
userId = user.id!!,
username = user.username,
message = "Login successful"
))

} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid username or password")
} catch (e: BadCredentialsException) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid username or password")
}
}
}

data class LoginRequest(
val username: String,
val password: String
)

data class LoginResponse(
val userId: Long,
val username: String,
val message: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.example.workoutloggerdemo.controller

import com.example.workoutloggerdemo.model.Exercise
import com.example.workoutloggerdemo.model.ExerciseCategory
import com.example.workoutloggerdemo.model.MuscleGroup
import com.example.workoutloggerdemo.service.ExerciseService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException

@RestController
@RequestMapping("/api/exercises")
class ExerciseController(private val exerciseService: ExerciseService) {

@GetMapping
fun getAllExercises(): ResponseEntity<List<Exercise>> {
return ResponseEntity.ok(exerciseService.getAllExercises())
}

@GetMapping("/{id}")
fun getExerciseById(@PathVariable id: Long): ResponseEntity<Exercise> {
return try {
ResponseEntity.ok(exerciseService.findById(id))
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}

@GetMapping("/category/{category}")
fun getExercisesByCategory(@PathVariable category: ExerciseCategory): ResponseEntity<List<Exercise>> {
return ResponseEntity.ok(exerciseService.findByCategory(category))
}

@GetMapping("/user/{userId}/custom")
fun getCustomExercisesForUser(@PathVariable userId: Long): ResponseEntity<List<Exercise>> {
return try {
ResponseEntity.ok(exerciseService.findCustomExercisesForUser(userId))
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
}
}

@GetMapping("/user/{userId}/available")
fun getAvailableExercisesForUser(@PathVariable userId: Long): ResponseEntity<List<Exercise>> {
return try {
ResponseEntity.ok(exerciseService.findAllAvailableForUser(userId))
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
}
}

@GetMapping("/search")
fun searchExercisesByName(@RequestParam searchTerm: String): ResponseEntity<List<Exercise>> {
return ResponseEntity.ok(exerciseService.searchByName(searchTerm))
}

@PostMapping
fun createExercise(@RequestBody request: CreateExerciseRequest): ResponseEntity<Exercise> {
return try {
val exercise = exerciseService.createExercise(
name = request.name,
description = request.description,
category = request.category,
muscleGroups = request.muscleGroups,
isCustom = request.isCustom,
userId = request.userId
)
ResponseEntity.status(HttpStatus.CREATED).body(exercise)
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
}
}

@PutMapping("/{id}")
fun updateExercise(
@PathVariable id: Long,
@RequestBody request: UpdateExerciseRequest
): ResponseEntity<Exercise> {
return try {
val exercise = exerciseService.updateExercise(
id = id,
name = request.name,
description = request.description,
category = request.category,
muscleGroups = request.muscleGroups
)
ResponseEntity.ok(exercise)
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
}
}

@DeleteMapping("/{id}")
fun deleteExercise(@PathVariable id: Long): ResponseEntity<Unit> {
return try {
exerciseService.deleteExercise(id)
ResponseEntity.noContent().build()
} catch (e: NoSuchElementException) {
throw ResponseStatusException(HttpStatus.NOT_FOUND, e.message)
} catch (e: IllegalArgumentException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
}
}
}

data class CreateExerciseRequest(
val name: String,
val description: String,
val category: ExerciseCategory,
val muscleGroups: List<MuscleGroup>,
val isCustom: Boolean = false,
val userId: Long? = null
)

data class UpdateExerciseRequest(
val name: String? = null,
val description: String? = null,
val category: ExerciseCategory? = null,
val muscleGroups: List<MuscleGroup>? = null
)
Loading