Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3149862
feat: adicionar enum UserStatus
ronaldo-ribeirof May 13, 2025
3cd6a18
feat: criei um repositório para Attendance (atendimentos)
ronaldo-ribeirof May 13, 2025
75375d9
feat: adicionei endpoints REST para atendimentos
ronaldo-ribeirof May 13, 2025
24257f3
feat: criei serviço para gerenciamento de status
ronaldo-ribeirof May 13, 2025
9721f39
feat: adicionei endpoints para alteração manual de status
ronaldo-ribeirof May 13, 2025
6e05e21
feat: adicionei campo status em CommitteeMember
ronaldo-ribeirof May 13, 2025
d4969f4
feat: adicionei campo status em Monitor
ronaldo-ribeirof May 13, 2025
771b219
feat: adicionei a busca de membros por status
ronaldo-ribeirof May 13, 2025
1f78283
feat: adicionei a busca de monitores por status
ronaldo-ribeirof May 13, 2025
1b0d839
feat: adicionei início e fim de atendimentos
ronaldo-ribeirof May 13, 2025
7b6c691
Apaguei os controllers desnecessários
ronaldo-ribeirof May 14, 2025
a809189
Ajustei comentários
ronaldo-ribeirof May 14, 2025
393628d
Merge branch 'main' into status-monitor-e-membrocomissao
ronaldo-ribeirof May 14, 2025
77a0623
Emanuel me ajudou na documentação
ronaldo-ribeirof May 14, 2025
2c6c88c
Adicionei a atualização de status
ronaldo-ribeirof May 14, 2025
d144ffb
Implementação da lógica inicial de status
ronaldo-ribeirof May 14, 2025
930c6be
Criamos um arquivo para o service do whatsapp
ronaldo-ribeirof May 17, 2025
f7b41b5
feat: Add a check logic on the "FirstContactService.kt". Its checks i…
ronaldo-ribeirof May 17, 2025
5b391bb
refact: The entity now gets the Attendant Class instead of the User C…
ronaldo-ribeirof May 17, 2025
07c32bf
Changes by victor-yghor
ronaldo-ribeirof May 17, 2025
77b5816
feat: The entity Attendant was created.
ronaldo-ribeirof May 17, 2025
c1d8718
refact: Now the Attendance entity gets the Attendant Class instead of…
ronaldo-ribeirof May 17, 2025
452d37e
Changes by victor-yghor
ronaldo-ribeirof May 17, 2025
7f48451
Changes by victor-yghor
ronaldo-ribeirof May 17, 2025
5fb13e9
Update AttendanceService.kt
ronaldo-ribeirof May 17, 2025
b793694
Delete UserStatusService.kt
ronaldo-ribeirof May 17, 2025
de3c265
Update name
ronaldo-ribeirof May 17, 2025
aaf50bf
Update FirstContactService.kt
ronaldo-ribeirof May 17, 2025
344294d
[feat] botpcd >> Add support to send mensage with whatsappService
yet1dev May 19, 2025
627f738
Merge branch 'develop' into status-monitor-e-membrocomissao
victorYghor May 20, 2025
4b46f45
put the cucumber testes to change status
victorYghor May 20, 2025
00e4210
Fix problem with the indentation of changeStatus.feature
victorYghor May 20, 2025
bca45d7
change the changeStatus cenário
victorYghor May 23, 2025
49bcd96
work: Add part of button creation in whatsapp-service
yet1dev May 27, 2025
6cdd26c
WIP start the register logic
victorYghor May 27, 2025
5379ff5
Merge branch 'whatsapp-service' into salvar-informacoes-essenciais-do…
victorYghor May 27, 2025
930581e
Fix the WhatsappService.kt
victorYghor May 27, 2025
6c10b75
Send a list of disabilities to the user
victorYghor May 27, 2025
4936f23
try to send message to the user
victorYghor May 27, 2025
2717032
Fix problem sending messages
victorYghor May 27, 2025
63528e3
Send Whatsapp message
victorYghor May 27, 2025
4764b83
Merge branch 'status-monitor-e-membrocomissao' into salvar-informacoe…
victorYghor May 30, 2025
2a968dc
Put the registerPCD.feature
victorYghor May 30, 2025
0ceb347
Refactor the registerPCD.feature for not sending buttons
victorYghor May 30, 2025
b3d9be7
Fix problem and send a message
victorYghor May 31, 2025
dbdfd92
put the mocks files
victorYghor May 31, 2025
3e46508
Write the first feature test
victorYghor Jun 1, 2025
c46285b
Harded code the text
victorYghor Jun 1, 2025
b6fc87e
Improve the steps definitions
victorYghor Jun 1, 2025
92f2839
WIP only instanciate the MessageExchangeRepository.kt in the test con…
victorYghor Jun 1, 2025
f8b1420
WIP remove the test profile
victorYghor Jun 2, 2025
60281be
WIP create the service and the FirstContactService.kt
victorYghor Jun 2, 2025
a0fe253
feat: Add support to acessibility tasks
victorYghor Jun 3, 2025
6bbeed8
feat: Add functino to create options list in whatsapp service
victorYghor Jun 3, 2025
cf39309
WIP improve the features
victorYghor Jun 3, 2025
365bfb4
WIP improve the tests
victorYghor Jun 3, 2025
550ca12
WIP Working in testes cenarios
victorYghor Jun 3, 2025
b8858df
Create the message repository
victorYghor Jun 3, 2025
3708302
try to fix tests
victorYghor Jun 4, 2025
7ca3adc
try to fix tests
victorYghor Jun 4, 2025
5683e5a
WIP testes
victorYghor Jun 4, 2025
18203be
WIP tests
victorYghor Jun 4, 2025
b3abcc9
Fix testes
victorYghor Jun 5, 2025
73d30c2
remove the tests
victorYghor Jun 5, 2025
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

testImplementation("io.cucumber:cucumber-spring:7.22.2")
testImplementation(platform("org.junit:junit-bom:5.12.2"))
testImplementation(platform("io.cucumber:cucumber-bom:7.22.2"))
testImplementation(platform("org.assertj:assertj-bom:3.27.3"))
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/ufrpe/sbpc/botpcd/config/WhatsappApiConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import com.whatsapp.api.impl.WhatsappBusinessCloudApi
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import java.beans.BeanProperty

@Profile("!test")
@Configuration
class WhatsappApiConfig {
@Value("\${whatsapp.api.token}")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ufrpe.sbpc.botpcd.controller

import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.slf4j.Logger

@ControllerAdvice
class GlobalExceptionHandler {
val logger: Logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java)

@ExceptionHandler(RuntimeException::class)
fun runtimeExceptionHandler(runtimeException: RuntimeException): ResponseEntity<Map<String, String>> {
logger.error("Erro inesperado ao chamar a API", runtimeException)
return ResponseEntity(mapOf("message" to "Some error ocorrer calling the API"), HttpStatus.INTERNAL_SERVER_ERROR)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ package ufrpe.sbpc.botpcd.controller

import com.whatsapp.api.domain.webhook.WebHook
import com.whatsapp.api.domain.webhook.WebHookEvent
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import ufrpe.sbpc.botpcd.entity.MessageExchange
import ufrpe.sbpc.botpcd.repository.MessageExchangeRepository
import ufrpe.sbpc.botpcd.service.FirstContactService


@RestController
class WhatsappWebhookController(private val firstContactService: FirstContactService) {
class WhatsappWebhookController(private val firstContactService: FirstContactService, private val messageExchangeRepository: MessageExchangeRepository) {
@Value("\${whatsapp.verify.token}")
lateinit var VERIFY_TOKEN: String
var logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java)
val logger: Logger = LoggerFactory.getLogger(WhatsappWebhookController::class.java)

@PostMapping("/webhooks")
fun eventNotification(
Expand All @@ -27,11 +30,15 @@ class WhatsappWebhookController(private val firstContactService: FirstContactSer
val event: WebHookEvent = WebHook.constructEvent(body)
for(entry in event.entry) {
for(change in entry.changes) {
firstContactService.redirectFluxByUserType(change.value.contacts[0].waId, change)
if(change.value?.messages == null) {
return ResponseEntity.ok("We don't handle this type of message")
}
val userPhoneNumber = change.value.contacts[0].waId
firstContactService.redirectFluxByUserType(userPhoneNumber, change)
}
}
// Opcional: validar assinatura com 'signature'
return ResponseEntity.ok("Evento processado")
return ResponseEntity.ok("Event process")
}

@GetMapping("/webhooks")
Expand Down
11 changes: 7 additions & 4 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ class Attendance(
var pwd: PWD,

@ManyToOne
@JoinColumn(name = "monitor_id")
@NotNull(message = "The monitor is required")
var monitor: Monitor,
@JoinColumn(name = "attendant_id")
@NotNull(message = "The attendant ID is required")
var attendant: Attendant,

@Enumerated(EnumType.STRING)
@NotNull(message = "The attendant type is required")
var attendantType: Provider,

// Campos opcionais que serão atualizados durante o ciclo de vida do atendimento
var acceptDateTime: LocalDateTime? = null,
Expand All @@ -41,4 +45,3 @@ class Attendance(

var endDateTime: LocalDateTime? = null
)

16 changes: 16 additions & 0 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/Attendant.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ufrpe.sbpc.botpcd.entity

import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.Table
import jakarta.validation.constraints.NotEmpty

@Entity
@Table(name = "tb_attendant")
abstract class Attendant(
name: String,
phoneNumber: String,
@Enumerated(EnumType.STRING)
var status: UserStatus = UserStatus.AVAILABLE
) : User(name = name, phoneNumber = phoneNumber)
5 changes: 3 additions & 2 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/CommitteeMember.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ import jakarta.persistence.Table
@Table(name = "committee_member")
class CommitteeMember(
name: String,
phoneNumber: String
): User(name = name, phoneNumber = phoneNumber)
phoneNumber: String,
status: UserStatus
): Attendant(name = name, phoneNumber = phoneNumber, status = status)
13 changes: 10 additions & 3 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/Disability.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,23 @@ enum class Disability(val textOption: String) {
PHYSICAL_DISABILITY("Deficiência física"),
MOBILITY_IMPAIRED("Não tenho deficiência, mas tenho mobilidade reduzida");


companion object {
fun parse(text: String): Disability {
fun getByText(text: String): Disability {
val disability = Disability.entries.find { it.textOption.equals(text, ignoreCase = true) }
if(disability == null) {
throw IllegalArgumentException("Invalid disability type: $text")
}
return disability
}
fun textList(): Array<String> {
return Disability.entries.map { it.textOption }.toTypedArray()
fun getOptions(): String {
var message = "Olá, qual sua deficiência?\n"
for (disability in Disability.entries) {
message += "- Digite ${disability.ordinal + 1} para ${disability.textOption}\n"
}
message += "- Digite 7 para Não preciso de suporte."
return message
}
fun getByOrdinal(ordinal: Int) = Disability.entries.find { it.ordinal == ordinal }
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/MessageExchange.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ufrpe.sbpc.botpcd.entity

import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import org.hibernate.annotations.CreationTimestamp
import java.time.LocalDateTime

@Entity
class MessageExchange(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var fromPhoneNumber: String,
var toPhoneNumber: String,
@Column(columnDefinition = "TEXT")
var message: String
) {
@CreationTimestamp
var createAt: LocalDateTime? = null
}
5 changes: 3 additions & 2 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/Monitor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import jakarta.validation.constraints.NotEmpty
class Monitor(
name: String,
phoneNumber: String,
status: UserStatus,
@Enumerated(value = EnumType.STRING)
@NotEmpty(message = "Monitor needs to have an assistance type")
var assistanceTypes: MonitorAssistanceType,
) : User(name = name, phoneNumber = phoneNumber)
var assistanceTypes: MonitorAssistanceType
) : Attendant(name = name, phoneNumber = phoneNumber, status = status)
17 changes: 12 additions & 5 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWD.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package ufrpe.sbpc.botpcd.entity

import jakarta.persistence.CascadeType
import jakarta.persistence.ElementCollection
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.CollectionTable
import jakarta.persistence.Column
import jakarta.persistence.JoinColumn
import jakarta.persistence.OneToMany
import jakarta.persistence.Table
import jakarta.validation.constraints.NotEmpty;
import org.springframework.data.repository.NoRepositoryBean
Expand All @@ -17,8 +23,9 @@ import org.springframework.data.repository.NoRepositoryBean
class PWD(
name: String? = null,
phoneNumber: String,
phoneNumberId: String,
@NotEmpty(message = "The PWD needs to have a disability")
@Enumerated(value = EnumType.STRING)
var disability: MutableSet<Disability>
): User(name = name, phoneNumber = phoneNumber, phoneNumberId = phoneNumberId)
@ElementCollection(targetClass = Disability::class)
@CollectionTable(name = "tb_pwd_disabilities", joinColumns = [JoinColumn(name = "pwd_id")])
@Column(name = "disability")
@Enumerated(EnumType.STRING)
var disabilities: MutableSet<Disability> = mutableSetOf()
): User(name = name, phoneNumber = phoneNumber)
26 changes: 26 additions & 0 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/PWDDisability.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ufrpe.sbpc.botpcd.entity

import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.Table

@Entity
@Table(name="tb_pwd_disability")
class PWDDisability(
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
val id: Long?= null,

@Enumerated(EnumType.STRING)
val disability: Disability,

@ManyToOne
@JoinColumn(name="pwd_id")
val pwd: PWD
)
70 changes: 58 additions & 12 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/ServiceType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,69 @@ package ufrpe.sbpc.botpcd.entity
/**
*
*/
sealed class ServiceType(val provider: Provider, val disability: Set<Disability>) {
object Libras : ServiceType(provider = Provider.MONITOR, disability = mutableSetOf(Disability.DEAFNESS)) {
sealed class ServiceType(
val provider: Provider,
val disability: Set<Disability>,
val description: String
) {
object Libras : ServiceType(
provider = Provider.MONITOR,
disability = mutableSetOf(Disability.DEAFNESS),
description = "informações em Libras"
) {
val monitorAssistanceType = MonitorAssistanceType.LIBRAS_MONITOR
}
object LibrasInterpreter: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.DEAFNESS))
object Mobility: ServiceType(provider = Provider.MONITOR, disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY, Disability.BLINDED)) {
object LibrasInterpreter : ServiceType(
provider = Provider.COMMITTEE_MEMBER,
disability = mutableSetOf(Disability.DEAFNESS),
description = "atividade com interpretação em Libras"
)
object Mobility : ServiceType(
provider = Provider.MONITOR,
disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY, Disability.BLINDED),
description = "ajuda na mobilidade"
) {
val monitorAssistanceType = MonitorAssistanceType.MOBILITY_MONITOR
}
object AudioDescription: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.BLINDED))
object NeurodivergentSupport: ServiceType(provider = Provider.MONITOR, disability = mutableSetOf(Disability.NEURODIVERGENT)) {

object AudioDescription : ServiceType(
provider = Provider.COMMITTEE_MEMBER,
disability = mutableSetOf(Disability.BLINDED),
description = "programação com audiodescrição"
)

object NeurodivergentSupport : ServiceType(
provider = Provider.MONITOR,
disability = mutableSetOf(Disability.NEURODIVERGENT),
description = "suporte para pessoas neurodivergentes"
) {
val monitorAssistanceType = MonitorAssistanceType.NEURODIVERGENT_SUPPORT_MONITOR
}
object GuideInterpreter: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.DEAFBLINDNESS))
object HygieneAndNutrition: ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY))
object Car : ServiceType(provider = Provider.COMMITTEE_MEMBER, disability = mutableSetOf(Disability.PHYSICAL_DISABILITY, Disability.MOBILITY_IMPAIRED
));
fun getServicesByDisability(disability: Disability): List<ServiceType> {
return ServiceType::class.sealedSubclasses.mapNotNull{ it.objectInstance }.filter{ disability in it.disability }

object GuideInterpreter : ServiceType(
provider = Provider.COMMITTEE_MEMBER,
disability = mutableSetOf(Disability.DEAFBLINDNESS),
description = "guia-intérprete"
)

object HygieneAndNutrition : ServiceType(
provider = Provider.COMMITTEE_MEMBER,
disability = mutableSetOf(Disability.MOBILITY_IMPAIRED, Disability.PHYSICAL_DISABILITY),
description = "ajuda com alimentação e higiene"
)

object Car : ServiceType(
provider = Provider.COMMITTEE_MEMBER,
disability = mutableSetOf(Disability.PHYSICAL_DISABILITY, Disability.MOBILITY_IMPAIRED),
description = "transporte para deslocamento no evento"
)

companion object {
@JvmStatic
fun getServicesByDisability(disability: Disability): List<ServiceType> {
return ServiceType::class.sealedSubclasses
.mapNotNull { it.objectInstance }
.filter { disability in it.disability }
}
}
}
3 changes: 1 addition & 2 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ abstract class User(
var id: Long? = null,
var name: String? = null,
@NotEmpty(message = "The User needs to have a phone number")
var phoneNumber: String,
var phoneNumberId: String? = null
var phoneNumber: String
)
12 changes: 12 additions & 0 deletions src/main/kotlin/ufrpe/sbpc/botpcd/entity/UserStatus.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ufrpe.sbpc.botpcd.entity

/*
(DISPONIVEL) ele pode receber um atendimento
(OCUPADO) ele esta em atendimento, mas quando finalizar o codigo trocara para disponivel
(INDISPONIVEL) ele foi ao banheiro / esta ocupado. Nao vai receber chamado ate que altere seu status manualmente para available
*/
enum class UserStatus(val text: String) {
AVAILABLE("Disponível"),
BUSY("Ocupado"),
UNAVAILABLE("Indisponível")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ufrpe.sbpc.botpcd.repository

import org.springframework.data.jpa.repository.JpaRepository
import ufrpe.sbpc.botpcd.entity.Attendance

interface AttendanceRepository : JpaRepository<Attendance, Long>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package ufrpe.sbpc.botpcd.repository

import org.springframework.data.jpa.repository.JpaRepository
import ufrpe.sbpc.botpcd.entity.CommitteeMember
import ufrpe.sbpc.botpcd.entity.UserStatus

interface CommitteeMemberRepository: JpaRepository<CommitteeMember, Long> {
fun findByPhoneNumber(phoneNumber: String): CommitteeMember?
fun findByStatus(status: UserStatus): List<CommitteeMember>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ufrpe.sbpc.botpcd.repository

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import ufrpe.sbpc.botpcd.entity.MessageExchange

interface MessageExchangeRepository: JpaRepository<MessageExchange, Long> {
@Query(
"SELECT m FROM MessageExchange m WHERE m.toPhoneNumber = :toPhoneNumber AND m.fromPhoneNumber = :fromPhoneNumber ORDER BY m.createAt DESC LIMIT 1"
)
fun lastExchangeMessage(toPhoneNumber: String, fromPhoneNumber: String): MessageExchange?
Comment thread
victorYghor marked this conversation as resolved.
}
Loading