Ugrás a fő tartalomhoz

Spring tanfolyam - 3. alkalom

A mostani alkalmon a ticketing alkalmazást fogunk befejezni.


6. Fejezet - Bővítés kommenttel

Szeretnénk kommentekkel (Label) bővíteni az alkalmazásunkat, amiket jegyekhez (Ticket) írhatunk. A kommenteknek tartsuk számon a posztolóját, a tartalmát, a poszt időpontját, az esetleges frissítés időpontját, és több-több kapcsolatban álljanak a jegyekkel. Maga a komment modul legyen opcionálisan be/ki kapcsolható!

Hogy a modul be/ki kapcsolható lehessen vegyünk fel az application.properties fájlban egy változót az alábbi sor hozzáadásával (jelenleg engedélyezve van).

hu.bme.sch.kirdev.ticketingspring.load.comment=true
információ

Ez csak egy változó, önmagában nem csinál semmit, de a későbbiekben ezt fogjuk használni arra, hogy a komment modult be/ki kapcsoljuk, a következő annotációval:

@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])

Ezt az annotációt el lehet helyezni osztály szinten (pl. a CommentService-en), vagy akár metódus szinten is (pl. a TicketService-ben egy olyan metódus megvalósítására, ahol lekér egy ticketet úgy, hogy a hozzá tartozó kommenteket is visszaadja), így ha a változó értéke false, akkor az adott osztály/metódus nem lesz elérhető, mintha meg sem lenne írva a kódban. Az annotáció viszont nem használható property-ken, így pl. a TicketEntity-ben nem tudunk feltételes ˙comments` property-t létrehozni.

Hozzunk létre egy új mappát comment néven és a továbbiakban oda dolgozzunk.

CommentEntity

CommentEntity.kt

@Entity
@Table(name = "comment")
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class CommentEntity(
@Id
@GeneratedValue
@Column(nullable = false)
val id: Int = 0,

@Column(nullable = false)
var postedBy: String = "",

@Column(nullable = false)
var content: String = "",

@Column(nullable = false)
val postedAt: Date = Date(),

@Column(nullable = false)
var updatedAt: Date = Date(),

@Column(nullable = false)
var ticketId: Int = 0,

) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CommentEntity) return false
if (id != other.id) return false
return true
}

override fun hashCode(): Int = javaClass.hashCode()

override fun toString(): String {
return this::class.simpleName + "(id = $id)"
}

@PreUpdate
fun preUpdate() {
updatedAt = Date()
}
}

CommentRepository

CommentRepository.kt

@Repository
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
interface CommentRepository: CrudRepository<CommentEntity, Int> {
fun findAllByTicketId(ticketId: Int): List<CommentEntity>
}

Comment DTO-k

CommentDtos.kt

@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class CreateCommentDto(
val postedBy: String,
val content: String
)

@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class CommentDto(
val id: Int,
val postedBy: String,
val content: String,
val postedAt: Date,
val updatedAt: Date,
val ticketId: Int,
)
{
constructor(comment: CommentEntity): this(
id = comment.id,
postedBy = comment.postedBy,
content = comment.content,
postedAt = comment.postedAt,
updatedAt = comment.updatedAt,
ticketId = comment.ticketId,
)
}

@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
data class DetailedCommentDto(
val id: Int,
val postedBy: String,
val content: String,
val postedAt: Date,
val updatedAt: Date,
val ticket: TicketDto
) {
constructor(comment: CommentEntity, ticket: TicketEntity): this(
id = comment.id,
postedBy = comment.postedBy,
content = comment.content,
postedAt = comment.postedAt,
updatedAt = comment.updatedAt,
ticket = TicketDto(ticket)
)
}

CommentService

CommentService.kt

@Service
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
class CommentService(
private val commentRepository: CommentRepository,
private val ticketRepository: TicketRepository
) {

fun createComment(comment: CreateCommentDto, ticketId: Int): DetailedCommentDto {
val ticket = ticketRepository.findById(ticketId)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }
return commentRepository.save(CommentEntity(
postedBy = comment.postedBy,
content = comment.content,
ticketId = ticketId
)).let { DetailedCommentDto(it, ticket) }
}

fun getComment(id: Int): DetailedCommentDto {
return commentRepository.findById(id)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Comment not found") }
.let { comment ->
val ticket = ticketRepository.findById(comment.ticketId)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }
DetailedCommentDto(comment, ticket)
}
}

fun getAllCommentsForTicket(ticketId: Int): List<DetailedCommentDto> {
val ticket = ticketRepository.findById(ticketId)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }
return commentRepository.findAllByTicketId(ticketId).map { comment ->
DetailedCommentDto(comment, ticket)
}
}

fun updateComment(id: Int, comment: CreateCommentDto): DetailedCommentDto {
return commentRepository.findById(id).map {
it.postedBy = comment.postedBy
it.content = comment.content
commentRepository.save(it)
}.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Comment not found") }
.let { DetailedCommentDto(it, ticketRepository.findById(id)
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Ticket not found") }) }
}

fun deleteComment(id: Int) {
commentRepository.deleteById(id)
}

}

CommentController

CommentController.kt

@RestController
@RequestMapping("/")
@ConditionalOnBooleanProperty(value = ["hu.bme.sch.kirdev.ticketingspring.load.comment"])
class CommentController(
private val commentService: CommentService
) {

@PostMapping("/tickets/{ticketId}/comments")
fun createComment(@RequestBody comment: CreateCommentDto, @PathVariable ticketId: Int): ResponseEntity<DetailedCommentDto> {
val created = commentService.createComment(comment, ticketId)
return ResponseEntity.status(HttpStatus.CREATED).body(created)
}

@GetMapping("/comments/{id}")
fun getComment(@PathVariable id: Int): ResponseEntity<DetailedCommentDto> {
val comment = commentService.getComment(id)
return ResponseEntity.status(HttpStatus.OK).body(comment)
}

@GetMapping("/tickets/{ticketId}/comments")
fun getAllCommentsForTicket(@PathVariable ticketId: Int): ResponseEntity<List<DetailedCommentDto>> {
val comments = commentService.getAllCommentsForTicket(ticketId)
return ResponseEntity.status(HttpStatus.OK).body(comments)
}

@PatchMapping("/comments/{id}")
fun updateComment(@PathVariable id: Int, @RequestBody comment: CreateCommentDto): ResponseEntity<DetailedCommentDto> {
val updated = commentService.updateComment(id, comment)
return ResponseEntity.status(HttpStatus.OK).body(updated)
}

@DeleteMapping("/comments/{id}")
fun deleteComment(@PathVariable id: Int): ResponseEntity<Void> {
commentService.deleteComment(id)
return ResponseEntity.status(HttpStatus.NO_CONTENT).build()
}

}

Kommentek kipróbálása

Próbáljuk ki kommenteket, amihez most már tudjuk használni a Swaggert is!

localhost:8080/swagger-ui/index.html

Teszteljük le úgy is az alkalmazást, hogy a komment modul ki van kapcsolva!

Szorgalmi feladat

Szeretnénk egy új végpontot, ahol egy adott ticketet lekérve visszakapjuk a hozzá tartozó kommenteket is. Ehhez használjuk a @ConditionalOnBooleanProperty annotációt metódus szinten, hogy ez a végpont csak akkor legyen elérhető, ha a komment modul engedélyezve van.


Feladatot készítette: Szabó Benedek, Leírást készítette: Bácsi Miklós