diff --git a/pom.xml b/pom.xml index d6e74685..8052de7c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,255 @@ + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.hng + hng-java-boilerplate + 0.0.1-SNAPSHOT + hng-java-boilerplate + Hng java boiler plate + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.twilio.sdk + twilio + 10.6.4 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + 1.10.0 + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-database-postgresql + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.0 + + + com.fasterxml.jackson.core + jackson-core + 2.16.1 + + + com.fasterxml.jackson.core + jackson-annotations + 2.16.1 + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + org.springframework.boot + spring-boot-starter-validation + + + org.junit.jupiter + junit-jupiter-engine + 5.10.2 + test + + + org.junit.jupiter + junit-jupiter-api + 5.10.2 + test + + + org.mockito + mockito-core + 5.12.0 + test + + + org.mapstruct + mapstruct + 1.4.2.Final + + + org.mapstruct + mapstruct-processor + 1.4.2.Final + provided + + + io.rest-assured + rest-assured + test + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.hibernate.validator + hibernate-validator + 8.0.1.Final + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-mail + + + com.restfb + restfb + 2024.9.0 + + + com.google.api-client + google-api-client + 2.6.0 + + + com.beust + jcommander + 1.82 + + + com.stripe + stripe-java + 26.7.0 + + + dev.samstevens.totp + totp + 1.7.1 + + + org.projectlombok + lombok + provided + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + ${lombok.version} + + + + + + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 diff --git a/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java b/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java index 1bc155e5..222d11df 100644 --- a/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java +++ b/src/main/java/hng_java_boilerplate/config/WebSecurityConfig.java @@ -108,6 +108,7 @@ public SecurityFilterChain httpSecurity(HttpSecurity httpSecurity) throws Except "/api/v1/payment/plans", "/api/v1/payment/webhook", "/api/v1/notification-settings", + "/api/call" "/api/v1/profile/upload-image" ).permitAll() .requestMatchers( diff --git a/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java b/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java index e2688ead..40080be7 100644 --- a/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java +++ b/src/main/java/hng_java_boilerplate/profile/service/ProfileService.java @@ -17,3 +17,5 @@ public interface ProfileService { ProfileResponse getUserProfile(String userId); ResponseEntity uploadProfileImage(MultipartFile file) throws IOException; } + + diff --git a/src/main/java/hng_java_boilerplate/twilio/CallLogs/Entity.java b/src/main/java/hng_java_boilerplate/twilio/CallLogs/Entity.java new file mode 100644 index 00000000..ac11d124 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/twilio/CallLogs/Entity.java @@ -0,0 +1,27 @@ +package hng_java_boilerplate.twilio.CallLogs; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@jakarta.persistence.Entity +@Table(name = "call_logs") +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Entity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String toNumber; + private String fromNumber; + private String callSid; + private LocalDateTime timestamp; +} diff --git a/src/main/java/hng_java_boilerplate/twilio/Controller/TwillioCallController.java b/src/main/java/hng_java_boilerplate/twilio/Controller/TwillioCallController.java new file mode 100644 index 00000000..2ec88896 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/twilio/Controller/TwillioCallController.java @@ -0,0 +1,39 @@ +package hng_java_boilerplate.twilio.Controller; + +import com.twilio.exception.TwilioException; +import hng_java_boilerplate.comment.dto.ErrorResponse; +import hng_java_boilerplate.exception.BadRequestException; +import hng_java_boilerplate.exception.CustomError; +import hng_java_boilerplate.exception.NotFoundException; +import hng_java_boilerplate.twilio.RequestAndResponse.CallRequest; +import hng_java_boilerplate.twilio.RequestAndResponse.CallResponse; +import hng_java_boilerplate.twilio.Service.TwilioCallService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class TwillioCallController { + + private final TwilioCallService twilioCallService; + + @PostMapping("/call") + public CallResponse makecall(@RequestBody CallRequest callRequest) { + if (callRequest == null || callRequest.getToNumber() == null || callRequest.getFromNumber() == null) { + throw new BadRequestException("Invalid request: Phone number is required"); + } + + CallResponse response = twilioCallService.makeCall(callRequest); + + if (response == null) { + throw new NotFoundException("Twilio service error: Service not found/available"); + } + return response; + } +} \ No newline at end of file diff --git a/src/main/java/hng_java_boilerplate/twilio/Repository/TwilioCallRepo.java b/src/main/java/hng_java_boilerplate/twilio/Repository/TwilioCallRepo.java new file mode 100644 index 00000000..bfd8fb85 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/twilio/Repository/TwilioCallRepo.java @@ -0,0 +1,10 @@ +package hng_java_boilerplate.twilio.Repository; + +import hng_java_boilerplate.twilio.CallLogs.Entity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TwilioCallRepo extends JpaRepository { + +} diff --git a/src/main/java/hng_java_boilerplate/twilio/RequestAndResponse/CallRequest.java b/src/main/java/hng_java_boilerplate/twilio/RequestAndResponse/CallRequest.java new file mode 100644 index 00000000..dcedcca1 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/twilio/RequestAndResponse/CallRequest.java @@ -0,0 +1,31 @@ +package hng_java_boilerplate.twilio.RequestAndResponse; + +public class CallRequest { + private String toNumber; + private String fromNumber; + private String messageUrl; + + public String getFromNumber() { + return fromNumber; + } + + public void setFromNumber(String fromNumber) { + this.fromNumber = fromNumber; + } + + public String getToNumber() { + return toNumber; + } + + public void setToNumber(String toNumber) { + this.toNumber = toNumber; + } + + public String getMessageUrl() { + return messageUrl; + } + + public void setMessageUrl(String messageUrl) { + this.messageUrl = messageUrl; + } +} diff --git a/src/main/java/hng_java_boilerplate/twilio/RequestAndResponse/CallResponse.java b/src/main/java/hng_java_boilerplate/twilio/RequestAndResponse/CallResponse.java new file mode 100644 index 00000000..b94914b2 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/twilio/RequestAndResponse/CallResponse.java @@ -0,0 +1,27 @@ +package hng_java_boilerplate.twilio.RequestAndResponse; + +public class CallResponse { + private String message; + private String callSid; + + public CallResponse(String message, String callSid){ + this.message = message; + this.callSid = callSid; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getCallSid() { + return callSid; + } + + public void setCallSid(String callSid) { + this.callSid = callSid; + } +} diff --git a/src/main/java/hng_java_boilerplate/twilio/Service/TwilioCallService.java b/src/main/java/hng_java_boilerplate/twilio/Service/TwilioCallService.java new file mode 100644 index 00000000..f45148a0 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/twilio/Service/TwilioCallService.java @@ -0,0 +1,59 @@ +package hng_java_boilerplate.twilio.Service; + +import com.twilio.Twilio; +import com.twilio.rest.api.v2010.account.Call; +import com.twilio.type.PhoneNumber; +import hng_java_boilerplate.twilio.CallLogs.Entity; +import hng_java_boilerplate.twilio.Repository.TwilioCallRepo; +import hng_java_boilerplate.twilio.RequestAndResponse.CallRequest; +import hng_java_boilerplate.twilio.RequestAndResponse.CallResponse; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class TwilioCallService { + + @Value("${spring.twilio.account.sid}") + private String accountSid; + + @Value("${spring.twilio.auth.token}") + private String authToken; + + @Value("${spring.twilio.from.number}") + private String fromNumber; + + private final TwilioCallRepo twilioCallRepo; + + @PostConstruct + public void init(){ + Twilio.init(accountSid,authToken); + } + + public CallResponse makeCall (CallRequest callRequest){ + + Call call = Call.creator( + new PhoneNumber(callRequest.getToNumber()), + new PhoneNumber(callRequest.getFromNumber()), + URI.create("http://demo.twilio.com/docs/voice.xml") + ).create(); + + + Entity entity = Entity.builder() + .toNumber(callRequest.getToNumber()) + .fromNumber(callRequest.getFromNumber()) + .callSid(call.getSid()) + .timestamp(LocalDateTime.now()) + .build(); + + twilioCallRepo.save(entity); + + return new CallResponse("Call Initiated Successfully", call.getSid()); + } + +}