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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.OpenAPIDefinition
import io.swagger.v3.oas.annotations.info.Info
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling

@OpenAPIDefinition(
info = Info(
Expand All @@ -13,6 +14,7 @@ import org.springframework.boot.runApplication
)
)
@SpringBootApplication
@EnableScheduling
class UrlShortenerApplication

fun main(args: Array<String>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ class CustomUserDetailsService(private val userRepository: UserRepository) : Use
.authorities(emptyList())
.build()
}

fun getEmailByUserId(userId: String): String {
val user = userRepository.findById(userId)
.orElseThrow { UsernameNotFoundException("User with ID='$userId' is not found") }
return user.email
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.zufar.urlshortener.shorten.service

import org.springframework.mail.javamail.JavaMailSender
import org.springframework.mail.javamail.MimeMessageHelper
import org.springframework.stereotype.Service

@Service
class EmailNotificationService(
private val mailSender: JavaMailSender
) {
fun sendExpirationNotification(email: String, shortUrl: String, expirationDate: String) {
val message = mailSender.createMimeMessage()
val helper = MimeMessageHelper(message, true)

helper.setTo(email)
helper.setSubject("Your Short URL is Expiring Soon")
helper.setText(
"""
Hello,

This is a reminder that your short URL ($shortUrl) will expire on $expirationDate.
Please consider extending its validity if necessary.

Best regards,
URL Shortener Team
""".trimIndent(),
false
)

mailSender.send(message)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.zufar.urlshortener.shorten.service

import com.zufar.urlshortener.auth.service.CustomUserDetailsService
import com.zufar.urlshortener.shorten.repository.UrlRepository
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.LocalDateTime

@Component
class UrlExpirationNotificationScheduler(
private val urlRepository: UrlRepository,
private val emailNotificationService: EmailNotificationService,
private val userDetailsService: CustomUserDetailsService
) {
private val log = LoggerFactory.getLogger(UrlExpirationNotificationScheduler::class.java)

@Scheduled(cron = "0 0 0 * * *")
fun notifyExpiringUrls() {
log.info("Starting expiration notification task")
val thresholdDate = LocalDateTime.now().plusDays(7)

val expiringUrls = urlRepository.findAll().filter { it.expirationDate.isBefore(thresholdDate) }

if (expiringUrls.isEmpty()) {
log.info("No URLs found nearing expiration")
return
}

expiringUrls.forEach { url ->
val email = userDetailsService.getEmailByUserId(url.userId ?: "")
try {
emailNotificationService.sendExpirationNotification(
email = email,
shortUrl = url.shortUrl,
expirationDate = url.expirationDate.toString()
)
log.info("Sent expiration notification for URL: ${url.shortUrl} to user: $email")
} catch (e: Exception) {
log.error("Failed to send notification for URL: ${url.shortUrl} to user: $email", e)
}
}
}
}
70 changes: 39 additions & 31 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,37 +1,45 @@

# Application Properties
spring.application.name=URL-Shortener
app.base-url=${SERVER_BASE_URL}
server.port=8080
# Application Properties
spring.application.name=URL-Shortener
app.base-url=${SERVER_BASE_URL}
server.port=8080

# Springdoc Configuration
springdoc.swagger-ui.path=/api/v1/swagger-ui
springdoc.api-docs.path=/api/v1/api-docs
springdoc.api-docs.enabled=true
springdoc.show-actuator=true
springdoc.packages-to-scan=com.zufar.urlshortener
# Springdoc Configuration
springdoc.swagger-ui.path=/api/v1/swagger-ui
springdoc.api-docs.path=/api/v1/api-docs
springdoc.api-docs.enabled=true
springdoc.show-actuator=true
springdoc.packages-to-scan=com.zufar.urlshortener

# JWT Configuration
jwt.secret=${JWT_SECRET}
jwt.accessTokenExpiration=3600000
jwt.refreshTokenExpiration=604800000
# JWT Configuration
jwt.secret=${JWT_SECRET}
jwt.accessTokenExpiration=3600000
jwt.refreshTokenExpiration=604800000

# MongoDB Configuration
spring.data.mongodb.uri=${MONGODB_URI}
spring.data.mongodb.database=${MONGODB_DATABASE}
# MongoDB Configuration
spring.data.mongodb.uri=${MONGODB_URI}
spring.data.mongodb.database=${MONGODB_DATABASE}

# Actuator
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/actuator
management.endpoint.health.show-details=always
# Actuator
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/actuator
management.endpoint.health.show-details=always

# Logging
logging.level.root=WARN
logging.level.org.mongodb.driver=WARN
logging.level.org.springframework.data.mongodb=WARN
logging.level.org.springframework.web=WARN
logging.level.com.zufar.urlshortener=INFO
logging.file.name=logs/application.log
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30
logging.pattern.console="%d{yyyy-MM-dd HH:mm:ss.SSS} - correlationId=%X{correlationId} [%thread] %-5level %logger{36} - %msg%n"
# Logging
logging.level.root=WARN
logging.level.org.mongodb.driver=WARN
logging.level.org.springframework.data.mongodb=WARN
logging.level.org.springframework.web=WARN
logging.level.com.zufar.urlshortener=INFO
logging.file.name=logs/application.log
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30
logging.pattern.console="%d{yyyy-MM-dd HH:mm:ss.SSS} - correlationId=%X{correlationId} [%thread] %-5level %logger{36} - %msg%n"

# Mail
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${MAIL_USERNAME}
spring.mail.password=${MAIL_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true