Skip to content

Commit

Permalink
Add Fitbit subscription and notification handling
Browse files Browse the repository at this point in the history
Introduced entities for Fitbit notifications and subscriptions including related service and repository. Implemented REST controller for handling Fitbit notification API with conversion logic for DTOs to domain objects.
  • Loading branch information
MichiBaum committed Nov 16, 2024
1 parent 7620421 commit f183064
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.michibaum.fitness_service.fitbit.subscriptions

import jakarta.persistence.*
import org.hibernate.annotations.UuidGenerator
import java.util.*

@Entity
class FitbitNotification(
@Column(nullable = false, unique = false)
val collectionType: NotificationCollectionType,

@Column(nullable = false, unique = false)
val date: String,

@Column(nullable = false, unique = false)
val ownerId: String,

@Column(nullable = false, unique = false)
val ownerType: String,

@ManyToOne(targetEntity = FitbitSubscription::class, fetch = FetchType.LAZY, optional = false, cascade = [CascadeType.ALL])
val subscriptionId: FitbitSubscription,

@Column(nullable = false, unique = false)
val userId: String,

@Id
@UuidGenerator
val id: UUID = UUID.randomUUID(),
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.michibaum.fitness_service.fitbit.subscriptions

import org.springframework.stereotype.Component

@Component
class FitbitNotificationConverter(
private val fitbitSupscriptionService: FitbitSupscriptionService
) {

fun toDomain(dto: NotificationDto): FitbitNotification {
val subscription = lookupSupscription(dto.subscriptionId)

return FitbitNotification(
collectionType = convertCollectioType(dto.collectionType),
date = dto.date,
ownerId = dto.ownerId,
ownerType = dto.ownerType,
subscriptionId = subscription,
userId = subscription.userId
)
}

private fun lookupSupscription(id: String) =
fitbitSupscriptionService.findById(id) ?: throw Exception("Could not find subscription with id $id")

private fun convertCollectioType(collectionType: String): NotificationCollectionType {
return NotificationCollectionType.entries.first { it.fitbitStrings == collectionType }
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.michibaum.fitness_service.fitbit.subscriptions

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import org.hibernate.annotations.UuidGenerator
import java.util.*

@Entity
class FitbitSubscription(

@Column(nullable = false, unique = false)
val verificationCode: String,

@Column(nullable = false, unique = false)
val userId: String,

@Id
@UuidGenerator
val id: UUID = UUID.randomUUID(),
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.michibaum.fitness_service.fitbit.subscriptions

import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID

interface FitbitSubscriptionRepository: JpaRepository<FitbitSubscription, UUID> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.michibaum.fitness_service.fitbit.subscriptions

import org.springframework.stereotype.Service
import java.util.*
import kotlin.jvm.optionals.getOrNull

@Service
class FitbitSupscriptionService(
private val fitbitSubscriptionRepository: FitbitSubscriptionRepository
) {

fun findById(id: String) =
fitbitSubscriptionRepository.findById(UUID.fromString(id)).getOrNull()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.michibaum.fitness_service.fitbit.subscriptions

enum class NotificationCollectionType(val fitbitStrings: String) {
ACTIVITIES("activities"), // activities collection requires activity scope.
BODY("body"), // body collection requires weight scope.
FOODS("foods"), // foods collection requires nutrition scope.
SLEEP("sleep"), // sleep collection requires sleep scope.
USER_REVOKED_ACCESS("userRevokedAccess") // userRevokedAccess collection does not require any scopes.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.michibaum.fitness_service.fitbit.subscriptions

data class NotificationDto(
val collectionType: String,
val date: String,
val ownerId: String,
val ownerType: String,
val subscriptionId: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.michibaum.fitness_service.fitbit.subscriptions

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

@RestController
class SubscriptionController {

@PostMapping("/api/fitbit/notifications", consumes = ["application/json"], produces = ["application/json"])
fun notification(@RequestBody notifications: List<NotificationDto>): ResponseEntity<Unit> {
// https://dev.fitbit.com/build/reference/web-api/developer-guide/best-practices/#Subscriber-Security
return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}

@GetMapping("/api/fitbit/notifications", consumes = ["application/json"], produces = ["application/json"])
fun subscriber(@RequestParam("verify") verifyCode: String){

// correct verifyCode -> ResponseEntity.status(HttpStatus.NO_CONTENT).build()

// incorrect verifyCode -> ResponseEntity.status(HttpStatus.NOT_FOUND).build()

}

}

0 comments on commit f183064

Please sign in to comment.