athrainsky 11 bulan lalu
induk
melakukan
7c2fdd36af
30 mengubah file dengan 1416 tambahan dan 56 penghapusan
  1. 23 0
      qc.txt
  2. 24 0
      rights.txt
  3. 1 1
      src/main/kotlin/com/swagger/rest/RestApplication.kt
  4. 368 0
      src/main/kotlin/com/swagger/rest/controllers/BugController.kt
  5. 177 0
      src/main/kotlin/com/swagger/rest/controllers/CommentController.kt
  6. 1 1
      src/main/kotlin/com/swagger/rest/controllers/MemberController.kt
  7. 30 20
      src/main/kotlin/com/swagger/rest/controllers/PlatformController.kt
  8. 8 1
      src/main/kotlin/com/swagger/rest/controllers/ProjectController.kt
  9. 52 0
      src/main/kotlin/com/swagger/rest/models/Bug.kt
  10. 25 0
      src/main/kotlin/com/swagger/rest/models/BugInput.kt
  11. 27 0
      src/main/kotlin/com/swagger/rest/models/BugOutput.kt
  12. 30 0
      src/main/kotlin/com/swagger/rest/models/Comment.kt
  13. 15 0
      src/main/kotlin/com/swagger/rest/models/CommentInput.kt
  14. 15 0
      src/main/kotlin/com/swagger/rest/models/CommentOutput.kt
  15. 12 1
      src/main/kotlin/com/swagger/rest/models/Enum.kt
  16. 2 1
      src/main/kotlin/com/swagger/rest/models/MemberOutput.kt
  17. 5 3
      src/main/kotlin/com/swagger/rest/models/Platform.kt
  18. 1 1
      src/main/kotlin/com/swagger/rest/models/PlatformOutput.kt
  19. 1 3
      src/main/kotlin/com/swagger/rest/models/Project.kt
  20. 2 0
      src/main/kotlin/com/swagger/rest/models/ProjectMember.kt
  21. 14 0
      src/main/kotlin/com/swagger/rest/models/User.kt
  22. 1 0
      src/main/kotlin/com/swagger/rest/models/UserInput.kt
  23. 13 0
      src/main/kotlin/com/swagger/rest/repositories/BugRepository.kt
  24. 20 0
      src/main/kotlin/com/swagger/rest/repositories/CommentRepository.kt
  25. 14 2
      src/main/kotlin/com/swagger/rest/repositories/MemberRepository.kt
  26. 18 3
      src/main/kotlin/com/swagger/rest/repositories/PlatformRepository.kt
  27. 6 2
      src/main/kotlin/com/swagger/rest/repositories/ProjectRepository.kt
  28. 1 1
      src/main/kotlin/com/swagger/rest/repositories/UserRepository.kt
  29. 3 3
      src/main/resources/application.properties
  30. 507 13
      swagger3 project.yml

+ 23 - 0
qc.txt

@@ -1,4 +1,27 @@
 insert  into `user`(`user_id`,`name`,`password`,`username`) values 
 (1,'test user','$2a$10$Y8LgDPJiAsbw7n5pURhGVOmi5.LWpfJaX7ZgSDbjsQXEnsCFPdhB2','user')
 
+owner
+- owner dapat mengubah nama, deskripsi  project
+- owner dapat menghapus project
+- owner dapat menambah, mengubah dan menghapus platform project
+- owner dapat menambahkan dan menghapus member project
+- owner dapat menambah dan menghapus comment
+- owner dapat menambah, mengubah dan menghapus bug
 
+developer
+- dev dapat mengubah dev_status di bug
+- dev dapat menambah comment
+
+qc
+- qc dapat mengubah status, level, image_url di bug
+- qc dapat menambah comment
+- qc dapat menambah bug
+
+admin
+- Admin dapat mengubah nama, deskripsi  project
+- Admin dapat menambah, mengubah dan menghapus platform project
+- Admin dapat menambahkan dan menghapus member project
+- Admin tidak dapat menghapus member dengan role Admin
+- admin dapat menambah dan menghapus comment
+- admin dapat menambah, mengubah dan menghapus bug

+ 24 - 0
rights.txt

@@ -0,0 +1,24 @@
+owner
+- owner dapat mengubah nama, deskripsi  project
+- owner dapat menghapus project
+- owner dapat menambah, mengubah dan menghapus platform project
+- owner dapat menambahkan dan menghapus member project
+- owner dapat menambah dan menghapus comment
+- owner dapat menambah, mengubah dan menghapus bug
+
+developer
+- dev dapat mengubah dev_status di bug
+- dev dapat menambah comment
+
+qc
+- qc dapat mengubah status, level, image_url di bug
+- qc dapat menambah comment
+- qc dapat menambah bug
+
+admin
+- Admin dapat mengubah nama, deskripsi  project
+- Admin dapat menambah, mengubah dan menghapus platform project
+- Admin dapat menambahkan dan menghapus member project
+- Admin tidak dapat menghapus member dengan role Admin
+- admin dapat menambah dan menghapus comment
+- admin dapat menambah, mengubah dan menghapus bug

+ 1 - 1
src/main/kotlin/com/swagger/rest/RestApplication.kt

@@ -7,5 +7,5 @@ import org.springframework.boot.runApplication
 class RestApplication
 
 fun main(args: Array<String>) {
-	runApplication<RestApplication>(*args)
+    runApplication<RestApplication>(*args)
 }

+ 368 - 0
src/main/kotlin/com/swagger/rest/controllers/BugController.kt

@@ -0,0 +1,368 @@
+package com.swagger.rest.controllers
+
+import com.swagger.rest.models.Bug
+import com.swagger.rest.models.BugInput
+import com.swagger.rest.models.BugOutput
+import com.swagger.rest.models.Enum
+import com.swagger.rest.repositories.*
+import jakarta.persistence.criteria.Predicate
+import org.springframework.data.domain.PageRequest
+import org.springframework.data.domain.Pageable
+import org.springframework.data.domain.Sort
+import org.springframework.data.jpa.domain.Specification
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.web.bind.annotation.*
+import java.text.SimpleDateFormat
+import java.util.*
+import java.util.regex.Pattern
+
+@RestController
+@RequestMapping("/api/v1")
+class BugController(
+    private val bugRepository: BugRepository,
+    private val userRepository: UserRepository,
+    private val platformRepository: PlatformRepository,
+    private val commentRepository: CommentRepository,
+    private val memberRepository: MemberRepository
+) {
+
+    private fun regex(input: String): Boolean {
+//      "(http:\\/\\/|https:\\/\\/)?(www.)?([a-zA-Z0-9]+).[a-zA-Z0-9]*.[a-z]{3}.?([a-z]+)?"
+//      "^(http:\\/\\/|https:\\/\\/)?(www.)?([a-zA-Z0-9]+).[a-zA-Z0-9]*.[a-z]{3}.?([a-z]+)?\$"
+//      "(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?"
+//      "(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})(\.[a-zA-Z0-9]{2,})?\/[a-zA-Z0-9]{2,}"
+//      "(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?"
+//      "(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?\/[a-zA-Z0-9]{2,}|((https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?"
+
+        val regex = "(http://|https://)?(www.)?([a-zA-Z0-9]+).[a-zA-Z0-9]*.[a-z]{3}.?([a-z]+)?"
+        val pattern = Pattern.compile(regex, Pattern.MULTILINE)
+        val matcher = pattern.matcher(input)
+        var ret = false
+        while (matcher.find()) {
+            ret = true
+        }
+        return ret
+    }
+
+    private fun getSortDirection(direction: String): Sort.Direction {
+        if (direction == "asc") {
+            return Sort.Direction.ASC
+        } else if (direction == "desc") {
+            return Sort.Direction.DESC
+        }
+        return Sort.Direction.ASC
+    }
+
+    @GetMapping("/bugs")
+    fun getBug(
+        @RequestParam(defaultValue = 0.toString()) page: Int,
+        @RequestParam(defaultValue = 3.toString()) limit: Int,
+        @RequestParam(defaultValue = "id, desc") sort: Array<String>
+    ): Any {
+        return try {
+            val orders: MutableList<Sort.Order> = ArrayList()
+            val column = listOf(
+                "id",
+                "created",
+                "description",
+                "qc",
+                "dev",
+                "platform",
+                "goodday_url",
+                "image_url",
+                "level",
+                "status",
+                "dev_status"
+            )
+            val sort2 = if (!sort.contains(",")) {
+                sort + ",desc"
+            } else {
+                sort
+            }
+            if (!column.contains(sort2[0])) {
+                ResponseEntity<Bug>(HttpStatus.BAD_REQUEST)
+            } else {
+                if (sort2[0].contains(",")) {
+                    // will sort more than 2 fields
+                    // sortOrder="field, direction"
+                    for (sortOrder in sort2) {
+                        val _sort = sortOrder.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                        orders.add(Sort.Order(getSortDirection(_sort[1]), _sort[0]))
+                    }
+                } else {
+                    // sort=[field, direction]
+                    orders.add(Sort.Order(getSortDirection(sort2[1]), sort2[0]))
+                }
+                val pagingSort: Pageable = PageRequest.of(page, limit, Sort.by(orders))
+                val bugs = bugRepository.findAll(pagingSort)
+                val output = bugs.map {
+                    BugOutput(
+                        id = it.id,
+                        created = SimpleDateFormat("dd-MM-yyyy").format(it.created),
+                        description = it.description.trim(),
+                        qc = it.qc!!.name,
+                        dev = it.dev!!.name,
+                        platform = it.platform!!.name,
+                        goodday_url = it.goodday_url,
+                        image_url = it.image_url,
+                        level = Enum.Level.values()[it.level],
+                        status = Enum.Status.values()[it.status],
+                        dev_status = Enum.Dev_Status.values()[it.dev_status]
+                    )
+                }
+                val ret = output.content
+                val response: MutableMap<String, Any> = HashMap()
+                response["currentPage"] = output.number
+                response["totalRecord"] = output.totalElements
+                response["totalPage"] = output.totalPages
+                response["results"] = ret
+                if (ret.isNotEmpty()) {
+                    ResponseEntity(response, HttpStatus.OK)
+                } else {
+                    arrayOf<String>()
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ResponseEntity(e, HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+    @GetMapping("/bugs/{id}")
+    fun getBugById(@PathVariable("id") id: Long): Any {
+        val bugData = bugRepository.findById(id)
+        val output = bugData.map {
+            BugOutput(
+                id = it.id,
+                created = SimpleDateFormat("dd-MM-yyyy").format(it.created),
+                description = it.description.trim(),
+                qc = it.qc!!.name,
+                dev = it.dev!!.name,
+                platform = it.platform!!.name,
+                goodday_url = it.goodday_url,
+                image_url = it.image_url,
+                level = Enum.Level.values()[it.level],
+                status = Enum.Status.values()[it.status],
+                dev_status = Enum.Dev_Status.values()[it.dev_status]
+            )
+        }
+        return if (bugData.isPresent) {
+            ResponseEntity<BugOutput>(output.get(), HttpStatus.OK)
+        } else {
+            ResponseEntity<BugOutput>(HttpStatus.NOT_FOUND)
+        }
+    }
+
+    @PostMapping("/bugs")
+    fun addBug(@RequestBody bugInput: BugInput): Any {
+        return try {
+            val specBug = Specification<Bug> { root, _, builder ->
+                val list: MutableList<Predicate> = mutableListOf()
+                list.add(builder.equal(root.get<Bug>("description"), bugInput.description.trim()))
+                builder.and(*list.toTypedArray())
+            }
+            val found = bugRepository.count(specBug)
+            val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+            val countPlat = platformRepository.findPlatform2(userId.id.toString(), bugInput.platform)
+            val countQc = userRepository.findById(bugInput.qc)
+            val countDev = userRepository.findById(bugInput.dev)
+            if (!enumValues<Enum.Level>().any {
+                    it.name == bugInput.level.uppercase()
+                } || !enumValues<Enum.Status>().any {
+                    it.name == bugInput.status.uppercase()
+                } || !enumValues<Enum.Dev_Status>().any { it.name == bugInput.dev_status.uppercase() }) {
+                ResponseEntity(HttpStatus.BAD_REQUEST)
+            } else if (!regex(bugInput.goodday_url) || !regex(bugInput.image_url)) {
+                ResponseEntity(HttpStatus.BAD_REQUEST)
+            } else if (countPlat == 0 || countQc.isEmpty || countDev.isEmpty) {
+                ResponseEntity(HttpStatus.NOT_FOUND)
+            } else if (bugInput.description.isNotBlank()) {
+                val foundPlat = platformRepository.findPlatform(userId.id.toString(), bugInput.platform)
+                val qc = userRepository.findById(bugInput.qc).get()
+                val dev = userRepository.findById(bugInput.dev).get()
+                if (bugInput.description.length > 500) {//too long
+                    ResponseEntity(HttpStatus.PAYLOAD_TOO_LARGE)
+                } else if (found > 0) {//duplicate
+                    ResponseEntity(HttpStatus.CONFLICT)
+                } else {
+                    val saveBug = Bug()
+                    saveBug.created = Date()
+                    saveBug.description = bugInput.description.trim()
+                    saveBug.qc = qc
+                    saveBug.dev = dev
+                    saveBug.platform = foundPlat
+                    saveBug.goodday_url = bugInput.goodday_url.trim()
+                    saveBug.image_url = bugInput.image_url.trim()
+                    saveBug.level = Enum.Level.valueOf(bugInput.level.uppercase()).ordinal
+                    saveBug.status = Enum.Status.valueOf(bugInput.status.uppercase()).ordinal
+                    saveBug.dev_status = Enum.Dev_Status.valueOf(bugInput.dev_status.uppercase()).ordinal
+                    val save = bugRepository.save(saveBug)
+                    if (listOf(save).isNotEmpty()) {
+                        val output = BugOutput(
+                            id = save.id,
+                            created = SimpleDateFormat("dd-MM-yyyy").format(save.created),
+                            description = save.description.trim(),
+                            qc = save.qc!!.name,
+                            dev = save.dev!!.name,
+                            platform = save.platform!!.name,
+                            goodday_url = save.goodday_url,
+                            image_url = save.image_url,
+                            level = Enum.Level.values()[save.level],
+                            status = Enum.Status.values()[save.status],
+                            dev_status = Enum.Dev_Status.values()[save.dev_status]
+                        )
+                        ResponseEntity(output, HttpStatus.CREATED)
+                    } else {
+                        ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR)
+                    }
+                }
+            } else {//invalid
+                ResponseEntity(HttpStatus.BAD_REQUEST)
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ResponseEntity(e, HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+    private fun validColumn(id: Long, bugInput: BugInput): Boolean {
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        val role = memberRepository.getRole(userId.id.toString(), bugInput.platform)
+        val roleEnum =
+            if (Enum.Member.values().any { it.ordinal == role }) Enum.Member.values()[role] else Enum.Member.UNDEFINED
+        val valid = bugRepository.validOwnerAdmin(userId.id.toString())
+        val bugData: Bug = bugRepository.findById(id).get()
+        var col = arrayOf<Number>()
+        if (bugInput.description.trim() !== bugData.description.trim()) { //col += "description,"
+            col += 1
+        }
+        if (userRepository.findById(bugInput.qc).get().name !== bugData.qc!!.name) {//col += "qc,"
+            col += 2
+        }
+        if (userRepository.findById(bugInput.dev).get().name !== bugData.dev!!.name) {//col += "dev,"
+            col += 3
+        }
+        if (bugInput.platform.trim() !== bugData.platform!!.name) {//col += "platform,"
+            col += 4
+        }
+        if (bugInput.goodday_url.trim() !== bugData.goodday_url.trim()) {//col += "goodday_url,"
+            col += 5
+        }
+        if (bugInput.image_url.trim() !== bugData.image_url.trim()) {//col += "image_url,"
+            col += 6
+        }
+        if (Enum.Level.values()[bugData.level] !== Enum.Level.valueOf(bugInput.level.uppercase())) {//col += "level,"
+            col += 6
+        }
+        if (Enum.Status.values()[bugData.status] !== Enum.Status.valueOf(bugInput.status.uppercase())) {//col += "level,"
+            col += 7
+        }
+        if (Enum.Dev_Status.values()[bugData.dev_status] !== Enum.Dev_Status.valueOf(bugInput.dev_status.uppercase())) {//col += "level,"
+            col += 8
+        }
+        var ret = false
+        if (roleEnum == Enum.Member.PROGRAMMER && col.contains(9)) {
+            ret = true
+        }
+        if (roleEnum == Enum.Member.QC && (col.contains(6) || col.contains(7) || col.contains(8))) {
+            ret = true
+        }
+        if (roleEnum == Enum.Member.ADMIN || valid > 0) {
+            ret = true
+        }
+        return ret
+    }
+
+    @PutMapping("/bugs/{id}")
+    fun updateBugById(@PathVariable("id") id: Long, @RequestBody bugInput: BugInput): ResponseEntity<out Any> {
+        val bugData = bugRepository.findById(id)
+        val specBug = Specification<Bug> { root, _, builder ->
+            val list: MutableList<Predicate> = mutableListOf()
+            list.add(builder.equal(root.get<Bug>("description"), bugInput.description.trim()))
+            builder.and(*list.toTypedArray())
+        }
+        val found = bugRepository.count(specBug)
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        val countPlat = platformRepository.findPlatform2(userId.id.toString(), bugInput.platform.trim())
+        val countQc = userRepository.findById(bugInput.qc)
+        val countDev = userRepository.findById(bugInput.dev)
+        return if (bugData.isPresent && countPlat > 0 && countQc.isPresent && countDev.isPresent) {
+            val foundPlat = platformRepository.findPlatform(userId.id.toString(), bugInput.platform.trim())
+            val qc = userRepository.findById(bugInput.qc).get()
+            val dev = userRepository.findById(bugInput.dev).get()
+            if (validColumn(id, bugInput)) {
+                if (bugInput.description.isNotBlank()) {
+                    if (bugInput.description.length > 500) {//too long
+                        ResponseEntity<Bug>(HttpStatus.PAYLOAD_TOO_LARGE)
+                    } else {
+                        if ((bugInput.description == bugData.get().description && found > 0) || (bugInput.description !== bugData.get().description && found.toInt() == 0)) {
+                            val saveBug = bugData.get()
+                            saveBug.description = bugInput.description.trim()
+                            saveBug.qc = qc
+                            saveBug.dev = dev
+                            saveBug.platform = foundPlat
+                            saveBug.goodday_url = bugInput.goodday_url.trim()
+                            saveBug.image_url = bugInput.image_url.trim()
+                            saveBug.level = Enum.Level.valueOf(bugInput.level.uppercase()).ordinal
+                            saveBug.status = Enum.Status.valueOf(bugInput.status.uppercase()).ordinal
+                            saveBug.dev_status = Enum.Dev_Status.valueOf(bugInput.dev_status.uppercase()).ordinal
+                            val save = bugRepository.save(saveBug)
+                            if (listOf(save).isNotEmpty()) {
+                                val output = BugOutput(
+                                    id = save.id,
+                                    description = save.description.trim(),
+                                    qc = save.qc!!.name,
+                                    dev = save.dev!!.name,
+                                    platform = save.platform!!.name,
+                                    goodday_url = save.goodday_url,
+                                    image_url = save.image_url,
+                                    level = Enum.Level.values()[save.level],
+                                    status = Enum.Status.values()[save.status],
+                                    dev_status = Enum.Dev_Status.values()[save.dev_status]
+                                )
+                                ResponseEntity<Any>(output, HttpStatus.OK)
+                            } else {
+                                ResponseEntity<Any>(null, HttpStatus.INTERNAL_SERVER_ERROR)
+                            }
+                        } else {//duplicate
+                            ResponseEntity<Bug>(HttpStatus.CONFLICT)
+                        }
+                    }
+                } else {//invalid
+                    ResponseEntity<Bug>(HttpStatus.BAD_REQUEST)
+                }
+            } else {
+                ResponseEntity<Bug>(HttpStatus.FORBIDDEN)
+            }
+        } else {
+            ResponseEntity<Bug>(HttpStatus.NOT_FOUND)
+        }
+    }
+
+    @DeleteMapping("/bugs/{id}")
+    fun deleteBugById(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
+        val find = bugRepository.findById(id)
+        val used = commentRepository.findByBug(id.toString())
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        val validOwnerAdmin = bugRepository.validOwnerAdmin(userId.id.toString())
+        return try {
+            if (validOwnerAdmin > 0) {
+                if (used > 0) {//child used in transaction
+                    ResponseEntity(HttpStatus.RESET_CONTENT)
+                } else if (find.isPresent) {
+                    bugRepository.deleteById(id)
+                    ResponseEntity(HttpStatus.OK)
+                } else {//bug not found
+                    ResponseEntity(HttpStatus.NOT_FOUND)
+                }
+            } else {
+                ResponseEntity(HttpStatus.FORBIDDEN)
+            }
+        } catch (e: Exception) {
+            ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+}

+ 177 - 0
src/main/kotlin/com/swagger/rest/controllers/CommentController.kt

@@ -0,0 +1,177 @@
+package com.swagger.rest.controllers
+
+import com.swagger.rest.models.Comment
+import com.swagger.rest.models.CommentInput
+import com.swagger.rest.models.CommentOutput
+import com.swagger.rest.repositories.BugRepository
+import com.swagger.rest.repositories.CommentRepository
+import com.swagger.rest.repositories.UserRepository
+import org.springframework.data.domain.PageRequest
+import org.springframework.data.domain.Pageable
+import org.springframework.data.domain.Sort
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.web.bind.annotation.*
+import java.text.SimpleDateFormat
+import java.util.*
+
+@RestController
+@RequestMapping("/api/v1")
+class CommentController(
+    private val commentRepository: CommentRepository,
+    private val bugRepository: BugRepository,
+    private val userRepository: UserRepository
+) {
+
+    private fun getSortDirection(direction: String): Sort.Direction {
+        if (direction == "asc") {
+            return Sort.Direction.ASC
+        } else if (direction == "desc") {
+            return Sort.Direction.DESC
+        }
+        return Sort.Direction.ASC
+    }
+
+    @GetMapping("/bugs/{id}/comment")
+    fun getCommentByBug(
+        @RequestParam(defaultValue = 0.toString()) page: Int,
+        @RequestParam(defaultValue = 3.toString()) limit: Int,
+        @RequestParam(defaultValue = "comment_id, desc") sort: Array<String>,
+        @PathVariable("id") id: Long
+    ): Any {
+        return try {
+            val orders: MutableList<Sort.Order> = ArrayList()
+            val column = listOf(
+                "comment_id", "bug", "creator", "created", "content"
+            )
+            val sort2 = if (!sort.contains(",")) {
+                sort + ",desc"
+            } else {
+                sort
+            }
+            val found = bugRepository.findById(id)
+            if (!column.contains(sort2[0])) {
+                ResponseEntity<CommentOutput>(HttpStatus.BAD_REQUEST)
+            } else if (found.isEmpty) {
+                ResponseEntity<CommentOutput>(HttpStatus.NOT_FOUND)
+            } else {
+                if (sort2[0].contains(",")) {
+                    // will sort more than 2 fields
+                    // sortOrder="field, direction"
+                    for (sortOrder in sort2) {
+                        val _sort = sortOrder.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+                        orders.add(Sort.Order(getSortDirection(_sort[1]), _sort[0]))
+                    }
+                } else {
+                    // sort=[field, direction]
+                    orders.add(Sort.Order(getSortDirection(sort2[1]), sort2[0]))
+                }
+                val pagingSort: Pageable = PageRequest.of(page, limit, Sort.by(orders))
+                val comments = commentRepository.findByBug2(id.toString(), pagingSort)
+                val output = comments.map {
+                    CommentOutput(
+                        id = it.id,
+                        bug = it.bug!!.description,
+                        creator = it.creator!!.username,
+                        created = SimpleDateFormat("dd-MM-yyyy").format(it.created),
+                        content = it.content
+                    )
+                }
+                val ret = output.content
+                val response: MutableMap<String, Any> = HashMap()
+                response["currentPage"] = output.number
+                response["totalRecord"] = output.totalElements
+                response["totalPage"] = output.totalPages
+                response["results"] = ret
+                if (ret.isNotEmpty()) {
+                    ResponseEntity(response, HttpStatus.OK)
+                } else {
+                    arrayOf<String>()
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ResponseEntity(e, HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+    @GetMapping("/comments/{id}")
+    fun getCommentById(@PathVariable("id") id: Long): ResponseEntity<CommentOutput> {
+        val commentData = commentRepository.findById(id)
+        val output = commentData.map {
+            CommentOutput(
+                id = it.id,
+                bug = it.bug!!.description,
+                creator = it.creator!!.username,
+                created = SimpleDateFormat("dd-MM-yyyy").format(it.created),
+                content = it.content
+            )
+        }
+        return if (commentData.isPresent) {
+            ResponseEntity<CommentOutput>(output.get(), HttpStatus.OK)
+        } else {
+            ResponseEntity<CommentOutput>(HttpStatus.NOT_FOUND)
+        }
+    }
+
+    @PostMapping("/bugs/{id}/comment")
+    fun addComment(@RequestBody commentInput: CommentInput, @PathVariable("id") id: Long): Any {
+        return try {
+            val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+            val found = bugRepository.findById(id)
+            if (commentInput.content.length > 500) {
+                ResponseEntity(HttpStatus.PAYLOAD_TOO_LARGE)
+            } else {
+                if (found.isPresent) {
+                    val saveComment = Comment()
+                    saveComment.bug = found.get()
+                    saveComment.creator = userId
+                    saveComment.created = Date()
+                    saveComment.content = commentInput.content.trim()
+                    val save = commentRepository.save(saveComment)
+                    if (listOf(save).isNotEmpty()) {
+                        val output = CommentOutput(
+                            id = save.id,
+                            bug = save.bug!!.description,
+                            creator = save.creator!!.username,
+                            created = SimpleDateFormat("dd-MM-yyyy").format(save.created),
+                            content = save.content
+                        )
+                        ResponseEntity(output, HttpStatus.CREATED)
+                    } else {
+                        ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR)
+                    }
+                } else {
+                    ResponseEntity(HttpStatus.NOT_FOUND)
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ResponseEntity(e, HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+    @DeleteMapping("/comments/{id}")
+    fun deleteCommentById(@PathVariable("id") id: Long): Any {
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        return try {
+            val find = commentRepository.findById(id)
+            val ownerAdmin = commentRepository.validOwnerAdmin(userId.id.toString(), id)
+            if (find.isPresent) {
+                if (ownerAdmin > 0) {
+                    commentRepository.deleteById(id)
+                    ResponseEntity(HttpStatus.OK)
+                } else {
+                    ResponseEntity(HttpStatus.FORBIDDEN)
+                }
+            } else {
+                ResponseEntity(HttpStatus.NOT_FOUND)
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+            ResponseEntity(e, HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+}

+ 1 - 1
src/main/kotlin/com/swagger/rest/controllers/MemberController.kt

@@ -82,7 +82,7 @@ class MemberController(
                         role = Enum.Member.values()[it.role]
                     )
                 }
-                val ret: List<MemberOutput?> = output.content
+                val ret: List<MemberOutput> = output.content
                 val response: MutableMap<String, Any> = HashMap()
                 response["currentPage"] = output.number
                 response["totalRecord"] = output.totalElements

+ 30 - 20
src/main/kotlin/com/swagger/rest/controllers/PlatformController.kt

@@ -1,15 +1,15 @@
 package com.swagger.rest.controllers
 
+import com.swagger.rest.models.Bug
 import com.swagger.rest.models.Platform
 import com.swagger.rest.models.PlatformInput
 import com.swagger.rest.models.PlatformOutput
-import com.swagger.rest.repositories.MemberRepository
-import com.swagger.rest.repositories.PlatformRepository
-import com.swagger.rest.repositories.ProjectRepository
-import com.swagger.rest.repositories.UserRepository
+import com.swagger.rest.repositories.*
+import jakarta.persistence.criteria.Predicate
 import org.springframework.data.domain.PageRequest
 import org.springframework.data.domain.Pageable
 import org.springframework.data.domain.Sort
+import org.springframework.data.jpa.domain.Specification
 import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import org.springframework.security.core.context.SecurityContextHolder
@@ -21,7 +21,8 @@ class PlatformController(
     private val platformRepository: PlatformRepository,
     private val projectRepository: ProjectRepository,
     private val memberRepository: MemberRepository,
-    private val userRepository: UserRepository
+    private val userRepository: UserRepository,
+    private val bugRepository: BugRepository
 ) {
     private fun getSortDirection(direction: String): Sort.Direction {
         if (direction == "asc") {
@@ -34,7 +35,7 @@ class PlatformController(
 
     @GetMapping("/platforms")
     fun getPlatformByProjectId(
-        @RequestParam(required = false) project: String?,
+        @RequestParam(required = false) project: String? = "",
         @RequestParam(defaultValue = 0.toString()) page: Int,
         @RequestParam(defaultValue = 3.toString()) limit: Int,
         @RequestParam(defaultValue = "platform_id, desc") sort: Array<String>
@@ -63,12 +64,15 @@ class PlatformController(
                     orders.add(Sort.Order(getSortDirection(sort2[1]), sort2[0]))
                 }
                 val pagingSort: Pageable = PageRequest.of(page, limit, Sort.by(orders as List<Sort.Order>))
-                val platforms = platformRepository.findOwnerOrMember(userId.id.toString(), pagingSort)
+                val platforms = if (project == null) {
+                    platformRepository.findOwnerOrMember(userId.id.toString(), "", pagingSort)
+                } else {
+                    platformRepository.findOwnerOrMember(userId.id.toString(), project, pagingSort)
+                }
+//                val platforms = platformRepository.findOwnerOrMember(userId.id.toString(), project, pagingSort)
                 val output = platforms.map {
                     PlatformOutput(
-                        id = it.id,
-                        name = it.name,
-                        project = it.project!!.description
+                        id = it.id, name = it.name, project = it.project!!.description
                     )
                 }
                 val ret: List<PlatformOutput?> = output.content
@@ -94,9 +98,7 @@ class PlatformController(
         val platformData = platformRepository.findById(id)
         val output = platformData.map {
             PlatformOutput(
-                id = it.id,
-                name = it.name,
-                project = it.project!!.description
+                id = it.id, name = it.name, project = it.project!!.description
             )
         }
         return if (platformData.isPresent) {
@@ -188,18 +190,25 @@ class PlatformController(
     }
 
     @DeleteMapping("/platforms/{id}")
-    fun deletePlatformById(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
+    fun deletePlatformById(@PathVariable("id") id: Long): Any {
         val count = platformRepository.findById(id).isPresent
         return try {
             if (count) {
-                val find = platformRepository.findById(id)
+                val find = platformRepository.findById(id).get()
+                val spec = Specification<Bug> { root, _, builder ->
+                    val list: MutableList<Predicate> = mutableListOf()
+                    list.add(builder.equal(root.get<Platform>("platform").get<Long>("id"), find.id))
+                    builder.and(*list.toTypedArray())
+                }
+                val used = bugRepository.count(spec)
                 val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
-                val validOwner =
-                    projectRepository.validOwner(find.get().project!!.id.toString(), userId.id.toString())
+                val validOwner = projectRepository.validOwner(find.project!!.id.toString(), userId.id.toString())
                 val validAdmin =
-                    memberRepository.validRole(find.get().project!!.id.toString(), userId.id.toString(), "2")
+                    memberRepository.validRole(find.project!!.id.toString(), userId.id.toString(), "2")
                 if (validOwner > 0 || validAdmin > 0) {
-                    if (find.isPresent) {
+                    if (used > 0) {
+                        ResponseEntity(HttpStatus.RESET_CONTENT)
+                    } else if (listOf(find).isNotEmpty()) {
                         platformRepository.deleteById(id)
                         ResponseEntity(HttpStatus.OK)
                     } else {
@@ -212,7 +221,8 @@ class PlatformController(
                 ResponseEntity(HttpStatus.NOT_FOUND)
             }
         } catch (e: Exception) {
-            ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
+            e.printStackTrace()
+            ResponseEntity(e, HttpStatus.INTERNAL_SERVER_ERROR)
         }
     }
 }

+ 8 - 1
src/main/kotlin/com/swagger/rest/controllers/ProjectController.kt

@@ -1,6 +1,7 @@
 package com.swagger.rest.controllers
 
 import com.swagger.rest.models.Project
+import com.swagger.rest.models.ProjectMember
 import com.swagger.rest.repositories.MemberRepository
 import com.swagger.rest.repositories.PlatformRepository
 import com.swagger.rest.repositories.ProjectRepository
@@ -161,11 +162,17 @@ class ProjectController(
     fun deleteProject(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
         val find = projectRepository.findById(id)
         val used = platformRepository.findByProjectId(id.toString()).size
+        val spec = Specification<ProjectMember> { root, _, builder ->
+            val list: MutableList<Predicate> = mutableListOf()
+            list.add(builder.equal(root.get<Project>("project").get<Long>("id"), id))
+            builder.and(*list.toTypedArray())
+        }
+        val usedMember = memberRepository.count(spec)
         val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
         val validOwner = projectRepository.validOwner(id.toString(), userId.id.toString())
         return try {
             if (validOwner > 0) {
-                if (used > 0) {//child used in transaction
+                if (used > 0 || usedMember > 0) {//child used in transaction
                     ResponseEntity(HttpStatus.RESET_CONTENT)
                 } else if (find.isPresent) {
                     projectRepository.deleteById(id)

+ 52 - 0
src/main/kotlin/com/swagger/rest/models/Bug.kt

@@ -0,0 +1,52 @@
+package com.swagger.rest.models
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import jakarta.persistence.*
+import org.springframework.format.annotation.DateTimeFormat
+import java.util.*
+
+@Entity
+@Table(name = "bug")
+class Bug {
+
+    @Id
+    @Column(name = "bug_id", updatable = false, nullable = false)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    var id: Long = 0
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
+    var created: Date? = null
+
+    @Column(length = 500)
+    var description: String = ""
+
+    @ManyToOne
+    @JoinColumn(name = "qc", referencedColumnName = "user_id", foreignKey = ForeignKey(name = "FK_bugQc"))
+    var qc: User? = null
+
+    @ManyToOne
+    @JoinColumn(name = "dev", referencedColumnName = "user_id", foreignKey = ForeignKey(name = "FK_bugDev"))
+    var dev: User? = null
+
+    @ManyToOne
+    @JoinColumn(name = "platform", referencedColumnName = "platform_id", foreignKey = ForeignKey(name = "FK_bugPla"))
+    var platform: Platform? = null
+
+    var goodday_url: String = ""
+
+    var image_url: String = ""
+
+    @Column(columnDefinition = "TINYINT")
+    var level: Int = 0
+
+    @Column(columnDefinition = "TINYINT")
+    var status: Int = 0
+
+    @Column(columnDefinition = "TINYINT")
+    var dev_status: Int = 0
+
+    @OneToMany(mappedBy = "bug", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var bug: List<Comment> = mutableListOf()
+
+}

+ 25 - 0
src/main/kotlin/com/swagger/rest/models/BugInput.kt

@@ -0,0 +1,25 @@
+package com.swagger.rest.models
+
+class BugInput {
+
+    var id: Long = 0
+
+    var description: String = ""
+
+    var qc: Long = 0
+
+    var dev: Long = 0
+
+    var platform: String = ""
+
+    var goodday_url: String = ""
+
+    var image_url: String = ""
+
+    var level: String = ""
+
+    var status: String = ""
+
+    var dev_status: String = ""
+
+}

+ 27 - 0
src/main/kotlin/com/swagger/rest/models/BugOutput.kt

@@ -0,0 +1,27 @@
+package com.swagger.rest.models
+
+class BugOutput (
+
+    var id: Long = 0,
+
+    var created: String = "",
+
+    var description: String = "",
+
+    var qc: String = "",
+
+    var dev: String = "",
+
+    var platform: String = "",
+
+    var goodday_url: String = "",
+
+    var image_url: String = "",
+
+    var level: Enum.Level? = null,
+
+    var status: Enum.Status? = null,
+
+    var dev_status: Enum.Dev_Status? = null
+
+)

+ 30 - 0
src/main/kotlin/com/swagger/rest/models/Comment.kt

@@ -0,0 +1,30 @@
+package com.swagger.rest.models
+
+import jakarta.persistence.*
+import org.springframework.format.annotation.DateTimeFormat
+import java.util.*
+
+@Entity
+@Table(name = "comment")
+class Comment {
+
+    @Id
+    @Column(name = "comment_id", updatable = false, nullable = false)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    var id: Long = 0
+
+    @ManyToOne
+    @JoinColumn(name = "bug", referencedColumnName = "bug_id", foreignKey = ForeignKey(name = "FK_commBug"))
+    var bug: Bug? = null
+
+    @ManyToOne
+    @JoinColumn(name = "creator", referencedColumnName = "user_id", foreignKey = ForeignKey(name = "FK_commUser"))
+    var creator: User? = null
+
+    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
+    var created: Date? = null
+
+    @Column(length = 500)
+    var content: String = ""
+
+}

+ 15 - 0
src/main/kotlin/com/swagger/rest/models/CommentInput.kt

@@ -0,0 +1,15 @@
+package com.swagger.rest.models
+
+import jakarta.persistence.Column
+import java.util.*
+
+class CommentInput {
+
+    var id: Long = 0
+
+    var bug: String = ""
+
+    @Column(length = 500)
+    var content: String = ""
+
+}

+ 15 - 0
src/main/kotlin/com/swagger/rest/models/CommentOutput.kt

@@ -0,0 +1,15 @@
+package com.swagger.rest.models
+
+class CommentOutput(
+
+    var id: Long = 0,
+
+    var bug: String = "",
+
+    var creator: String = "",
+
+    var created: String = "",
+
+    var content: String = ""
+
+)

+ 12 - 1
src/main/kotlin/com/swagger/rest/models/Enum.kt

@@ -2,8 +2,19 @@ package com.swagger.rest.models
 
 class Enum {
 
-    enum class Member{
+    enum class Member {
         QC, PROGRAMMER, ADMIN, UNDEFINED
     }
 
+    enum class Level {
+        NOTE, MINOR, MAJOR, UNDEFINED
+    }
+
+    enum class Status {
+        ONPROGRESS, DONE, HOLD, CANCEL, UNDEFINED
+    }
+
+    enum class Dev_Status {
+        NOTSTART, ONPROGRESS, DONE, UNDEFINED
+    }
 }

+ 2 - 1
src/main/kotlin/com/swagger/rest/models/MemberOutput.kt

@@ -1,6 +1,6 @@
 package com.swagger.rest.models
 
-class MemberOutput (
+class MemberOutput(
 
     var id: Long? = 0,
 
@@ -9,4 +9,5 @@ class MemberOutput (
     var user: String? = "",
 
     var role: Enum.Member? = null
+
 )

+ 5 - 3
src/main/kotlin/com/swagger/rest/models/Platform.kt

@@ -1,10 +1,12 @@
 package com.swagger.rest.models
 
+import com.fasterxml.jackson.annotation.JsonIgnore
 import jakarta.persistence.*
 
 @Entity
 @Table(name = "platform")
 class Platform {
+
     @Id
     @Column(name = "platform_id", updatable = false, nullable = false)
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -17,8 +19,8 @@ class Platform {
     @JoinColumn(name = "project_id", referencedColumnName = "project_id", foreignKey = ForeignKey(name = "FK_plaPro"))
     var project: Project? = null
 
-    override fun toString(): String {
-        return "Platform [id=$id, name=$name, project_Id=$project]"
-    }
+    @OneToMany(mappedBy = "platform", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var platform: List<Bug> = mutableListOf()
 
 }

+ 1 - 1
src/main/kotlin/com/swagger/rest/models/PlatformOutput.kt

@@ -1,6 +1,6 @@
 package com.swagger.rest.models
 
-class PlatformOutput (
+class PlatformOutput(
 
     var id: Long? = 0,
 

+ 1 - 3
src/main/kotlin/com/swagger/rest/models/Project.kt

@@ -7,6 +7,7 @@ import jakarta.persistence.*
 @Entity
 @Table(name = "project")
 class Project {
+
     @Id
     @Column(name = "project_id", updatable = false, nullable = false)
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -30,7 +31,4 @@ class Project {
     @JoinColumn(name = "owner", referencedColumnName = "user_id", foreignKey = ForeignKey(name = "FK_proUser"))
     var owner: User? = null
 
-    override fun toString(): String {
-        return "Project [id=$id, name=$name, desc=$description]"
-    }
 }

+ 2 - 0
src/main/kotlin/com/swagger/rest/models/ProjectMember.kt

@@ -5,6 +5,7 @@ import jakarta.persistence.*
 @Entity
 @Table(name = "project_member")
 class ProjectMember {
+
     @Id
     @Column(name = "member_id", updatable = false, nullable = false)
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -20,4 +21,5 @@ class ProjectMember {
 
     @Column(columnDefinition = "TINYINT")
     var role: Int = 0
+
 }

+ 14 - 0
src/main/kotlin/com/swagger/rest/models/User.kt

@@ -8,6 +8,7 @@ import jakarta.persistence.*
 @Entity
 @Table(name = "user")
 class User {
+
     @Id
     @Column(name = "user_id", updatable = false, nullable = false)
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -30,4 +31,17 @@ class User {
     @OneToMany(mappedBy = "owner", cascade = [CascadeType.ALL])
     @JsonIgnore
     var owner: List<Project> = mutableListOf()
+
+    @OneToMany(mappedBy = "qc", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var qc: List<Bug> = mutableListOf()
+
+    @OneToMany(mappedBy = "dev", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var dev: List<Bug> = mutableListOf()
+
+    @OneToMany(mappedBy = "creator", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var creator: List<Comment> = mutableListOf()
+
 }

+ 1 - 0
src/main/kotlin/com/swagger/rest/models/UserInput.kt

@@ -11,4 +11,5 @@ class UserInput {
     var password: String? = ""
 
     var name: String? = ""
+
 }

+ 13 - 0
src/main/kotlin/com/swagger/rest/repositories/BugRepository.kt

@@ -0,0 +1,13 @@
+package com.swagger.rest.repositories
+
+import com.swagger.rest.models.Bug
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor
+import org.springframework.data.jpa.repository.Query
+
+interface BugRepository : JpaRepository<Bug, Long>, JpaSpecificationExecutor<Bug> {
+
+    @Query("SELECT COUNT(0) FROM bug WHERE platform IN (SELECT platform FROM platform WHERE project_id IN (SELECT project_id FROM project WHERE OWNER=?1 UNION SELECT project_id FROM project_member WHERE role=2 AND user_id=?1))", nativeQuery = true)
+    fun validOwnerAdmin(owner: String?): Int
+
+}

+ 20 - 0
src/main/kotlin/com/swagger/rest/repositories/CommentRepository.kt

@@ -0,0 +1,20 @@
+package com.swagger.rest.repositories
+
+import com.swagger.rest.models.Comment
+import org.springframework.data.domain.Page
+import org.springframework.data.domain.Pageable
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor
+import org.springframework.data.jpa.repository.Query
+
+interface CommentRepository : JpaRepository<Comment, Long>, JpaSpecificationExecutor<Comment> {
+
+    @Query("SELECT COUNT(0) FROM comment WHERE bug=?1", nativeQuery = true)
+    fun findByBug(bug: String): Int
+
+    @Query("SELECT * FROM comment WHERE bug=?1", nativeQuery = true)
+    fun findByBug2(bug: String, pageable: Pageable): Page<Comment>
+
+    @Query("SELECT COUNT(0) FROM COMMENT WHERE comment_id=?2 AND bug IN (SELECT bug_id FROM bug WHERE platform IN (SELECT platform FROM platform WHERE project_id IN (SELECT project_id FROM project WHERE OWNER=?1 UNION SELECT project_id FROM project_member WHERE role=2 AND user_id=?1)))", nativeQuery = true)
+    fun validOwnerAdmin(user: String, id: Long): Int
+}

+ 14 - 2
src/main/kotlin/com/swagger/rest/repositories/MemberRepository.kt

@@ -1,5 +1,7 @@
 package com.swagger.rest.repositories
 
+import com.swagger.rest.models.Enum
+import com.swagger.rest.models.Platform
 import com.swagger.rest.models.ProjectMember
 import org.springframework.data.jpa.repository.JpaRepository
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor
@@ -13,12 +15,22 @@ interface MemberRepository : JpaRepository<ProjectMember, Long>, JpaSpecificatio
     @Query("SELECT f.* FROM project_member f WHERE user_id=?1", nativeQuery = true)
     fun findByUser(user: String?): List<ProjectMember>
 
-    @Query("SELECT COUNT(0) FROM project_member WHERE project_id like %?1% AND user_id=?2 AND role=?3", nativeQuery = true)
+    @Query(
+        "SELECT COUNT(0) FROM project_member WHERE project_id like %?1% AND user_id=?2 AND role=?3",
+        nativeQuery = true
+    )
     fun validRole(project: String?, user: String?, role: String?): Int
 
-    @Query("SELECT COUNT(0) FROM project_member WHERE project_id=?1 AND user_id=?2 AND role<>2 AND role=?3", nativeQuery = true)
+    @Query(
+        "SELECT COUNT(0) FROM project_member WHERE project_id=?1 AND user_id=?2 AND role<>2 AND role=?3",
+        nativeQuery = true
+    )
     fun targetRole(project: String?, user: String?, role: String?): Int
 
     @Query("SELECT COUNT(0) FROM project_member WHERE project_id=?1 AND user_id=?2", nativeQuery = true)
     fun validMember(project: String?, user: String?): Int
+
+    @Query("SELECT IFNULL((SELECT role FROM project_member WHERE user_id=?1 AND project_id IN (SELECT project_id FROM platform WHERE NAME=?2)), 4) AS role", nativeQuery = true)
+    fun getRole(user: String, platform: String): Int
+
 }

+ 18 - 3
src/main/kotlin/com/swagger/rest/repositories/PlatformRepository.kt

@@ -1,7 +1,6 @@
 package com.swagger.rest.repositories
 
 import com.swagger.rest.models.Platform
-import com.swagger.rest.models.Project
 import org.springframework.data.domain.Page
 import org.springframework.data.domain.Pageable
 import org.springframework.data.jpa.repository.JpaRepository
@@ -15,6 +14,22 @@ interface PlatformRepository : JpaRepository<Platform, Long>, JpaSpecificationEx
     @Query("SELECT f.* FROM platform f WHERE project_id=?1", nativeQuery = true)
     fun findByProjectId(project: String?): List<Platform>
 
-    @Query("SELECT * FROM platform WHERE project_id IN (SELECT project_id FROM project WHERE OWNER=?1) OR project_id IN (SELECT project_id FROM project_member WHERE user_id=?1)", nativeQuery = true)
-    fun findOwnerOrMember(user: String, pageable: Pageable): Page<Platform>
+    @Query(
+        "SELECT * FROM platform WHERE project_id like %?2% AND (project_id IN (SELECT project_id FROM project WHERE OWNER=?1) OR project_id IN (SELECT project_id FROM project_member WHERE user_id=?1))",
+        nativeQuery = true
+    )
+    fun findOwnerOrMember(user: String, project: String?, pageable: Pageable): Page<Platform>
+
+    @Query(
+        "SELECT * FROM platform WHERE name=?2 AND project_id IN (SELECT project_id FROM project WHERE owner=?1 UNION SELECT project_id FROM project_member WHERE user_id=?1)",
+        nativeQuery = true
+    )
+    fun findPlatform(user: String, platform: String): Platform
+
+    @Query(
+        "SELECT COUNT(0) FROM platform WHERE name=?2 AND project_id IN (SELECT project_id FROM project WHERE owner=?1 UNION SELECT project_id FROM project_member WHERE user_id=?1)",
+        nativeQuery = true
+    )
+    fun findPlatform2(user: String, platform: String): Int
+
 }

+ 6 - 2
src/main/kotlin/com/swagger/rest/repositories/ProjectRepository.kt

@@ -13,8 +13,11 @@ import org.springframework.security.access.method.P
 
 interface ProjectRepository : JpaRepository<Project, Long>, JpaSpecificationExecutor<Project> {
 
-    @Query("SELECT * FROM project f WHERE OWNER=?1 OR (project_id IN (SELECT project_id FROM project_member WHERE user_id=?1))", nativeQuery = true)
-    fun findOwnerOrMember(user: String, pageable: Pageable):Page<Project>
+    @Query(
+        "SELECT * FROM project f WHERE OWNER=?1 OR (project_id IN (SELECT project_id FROM project_member WHERE user_id=?1))",
+        nativeQuery = true
+    )
+    fun findOwnerOrMember(user: String, pageable: Pageable): Page<Project>
 
     fun findByName(name: String?): Project
 
@@ -23,4 +26,5 @@ interface ProjectRepository : JpaRepository<Project, Long>, JpaSpecificationExec
 
     @Query("SELECT COUNT(0) FROM project WHERE project_id=?1 AND owner=?2", nativeQuery = true)
     fun validOwner(project: String?, owner: String?): Int
+
 }

+ 1 - 1
src/main/kotlin/com/swagger/rest/repositories/UserRepository.kt

@@ -4,7 +4,7 @@ import com.swagger.rest.models.User
 import org.springframework.data.jpa.repository.JpaRepository
 import org.springframework.data.jpa.repository.Query
 
-interface UserRepository:JpaRepository<User, Long> {
+interface UserRepository : JpaRepository<User, Long> {
     @Query("SELECT u FROM User u WHERE u.username = :username")
     fun getUserByUsername(username: String): User
 

+ 3 - 3
src/main/resources/application.properties

@@ -1,4 +1,4 @@
 spring.jpa.hibernate.ddl-auto=update
-spring.datasource.url= jdbc:mysql://localhost:3306/swagger
-spring.datasource.username= root
-spring.datasource.password= root
+spring.datasource.url=jdbc:mysql://localhost:3306/swagger
+spring.datasource.username=root
+spring.datasource.password=root

+ 507 - 13
swagger3 project.yml

@@ -9,7 +9,8 @@ info:
     - Auth 
     - Membership & Ownership
     - Rights & Paging
-  version: 5.0.0
+    - Bug
+  version: 6.0.0
 servers:
 - url: http://localhost:8080/api/v1
 tags:
@@ -19,6 +20,10 @@ tags:
   description: everything platform
 - name: users
   description: everything user
+- name: bugs
+  description: everything bug
+- name: comments
+  description: everything comment
 paths:
   /projects:
     get:
@@ -143,11 +148,7 @@ paths:
         - $ref: '#/components/parameters/roleQuery'
         - $ref: '#/components/parameters/page'
         - $ref: '#/components/parameters/limit'
-        - name: sort
-          in: query
-          description: sort direction (default 'member_id, desc')
-          schema:
-            type: string
+        - $ref: '#/components/parameters/sortQuery'
       responses:
         200:
           $ref: '#/components/responses/getArrayMember'
@@ -236,6 +237,8 @@ paths:
       responses:
         200:
           $ref: '#/components/responses/getArrayPlatform'
+        400:
+          $ref: '#/components/responses/400'
         401:
           $ref: '#/components/responses/UnauthorizedError'
       security: 
@@ -321,6 +324,8 @@ paths:
       responses:
         200:
           description: successful operation
+        205:
+          description: Unable to delete. Data is used.
         401:
           $ref: '#/components/responses/UnauthorizedError'
         403:
@@ -482,6 +487,226 @@ paths:
           $ref: '#/components/responses/404'
       security: 
         - testAuth: []
+  /bugs:
+    get:
+      tags: 
+      - bugs
+      summary: find all bugs
+      description: return all bugs
+      operationId: getBug
+      parameters: 
+        - $ref: '#/components/parameters/page'
+        - $ref: '#/components/parameters/limit'
+        - $ref: '#/components/parameters/sortQuery'
+      responses:
+        200:
+          $ref: '#/components/responses/getArrayBug'
+        400:
+          $ref: '#/components/responses/400'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+      security: 
+      - testAuth: []
+    post:
+      tags:
+      - bugs
+      summary: add bug
+      description: add new bug
+      operationId: addBug
+      requestBody:
+        $ref: '#/components/requestBodies/Bug'
+      responses:
+        201:
+          $ref: '#/components/responses/successAddBug'
+        400:
+          $ref: '#/components/responses/400'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        404:
+          $ref: '#/components/responses/404'
+        409:
+          $ref: '#/components/responses/409'
+        413:
+          $ref: '#/components/responses/413'
+      security: 
+      - testAuth: []
+  /bugs/{bugId}:
+    get:
+      tags: 
+      - bugs
+      summary: find bug by ID
+      description: return bug
+      operationId: getBugById
+      parameters: 
+        - $ref: '#/components/parameters/bugPath'
+      responses:
+        200:
+          $ref: '#/components/responses/getSingleBug'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+      - testAuth: []
+    put:
+      tags: 
+      - bugs
+      summary: update existing bug
+      description: update bug
+      operationId: updateBugById
+      parameters: 
+        - $ref: '#/components/parameters/bugPath'
+      requestBody:
+        description: Bug object
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                description:
+                  example: bug browser
+                qc:
+                  example: 23
+                dev:
+                  example: 24
+                platform:
+                  example: web
+                goodday_url:
+                  example: goodday.com
+                image_url:
+                  example: image.com
+                level:
+                  example: major
+                status:
+                  example: onprogress
+                dev_status:
+                  example: notstart
+      responses:
+        200:
+          $ref: '#/components/responses/getSingleBug'
+        400:
+          $ref: '#/components/responses/400'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
+        404:
+          $ref: '#/components/responses/404'
+        409:
+          $ref: '#/components/responses/409'
+        413:
+          $ref: '#/components/responses/413'
+      security: 
+      - testAuth: []
+    delete:
+      tags: 
+      - bugs
+      summary: deletes a bug by ID
+      description: delete a bug by ID
+      operationId: deleteBugById
+      parameters: 
+        - $ref: '#/components/parameters/bugPath'
+      responses:
+        200:
+          description: successful operation
+        205:
+          description: Unable to delete. Data is used.
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+      - testAuth: []
+  /bugs/{bugId}/comment:
+    get:
+      tags: 
+      - bugs
+      summary: find all comments by bug ID
+      description: return all comments by bug ID
+      operationId: getCommentByBug
+      parameters: 
+        - $ref: '#/components/parameters/bugPath'
+        - $ref: '#/components/parameters/page'
+        - $ref: '#/components/parameters/limit'
+        - name: sort
+          in: query
+          description: sort direction (default 'comment_id, desc')
+          schema:
+            type: string
+      responses:
+        200:
+          $ref: '#/components/responses/getArrayComment'
+        400:
+          $ref: '#/components/responses/400'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+      - testAuth: []
+    post:
+      tags: 
+      - bugs
+      summary: add comment
+      description: add new comment
+      operationId: addComment
+      parameters: 
+        - $ref: '#/components/parameters/bugPath'
+      requestBody:
+        $ref: '#/components/requestBodies/Comment'
+      responses:
+        201:
+          $ref: '#/components/responses/successAddComment'
+        400:
+          $ref: '#/components/responses/400'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        404:
+          $ref: '#/components/responses/404'
+        413:
+          $ref: '#/components/responses/413'
+      security: 
+      - testAuth: []
+  /comments/{commentId}:
+    get:
+      tags: 
+      - comments
+      summary: find comment by ID
+      description: return comment
+      operationId: getCommentById
+      parameters: 
+        - $ref: '#/components/parameters/commentPath'
+      responses:
+        200:
+          $ref: '#/components/responses/getSingleComment'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+      - testAuth: []
+    delete:
+      tags: 
+      - comments
+      summary: deletes a comment by ID
+      description: delete a comment by ID
+      operationId: deleteCommentById
+      parameters: 
+        - $ref: '#/components/parameters/commentPath'
+      responses:
+        200:
+          description: successful operation
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+      - testAuth: []
 components:
   schemas:
     Project:
@@ -548,7 +773,7 @@ components:
           type: string
           maximum: 255
           example: abidzar
-    project_member:
+    Project_member:
       type: object
       properties:
         id:
@@ -561,13 +786,82 @@ components:
         role:
           type: number
           enum: [0,1,2]
-          description: role = 0.QC, 1. Programmer, 2.Admin
+          description: role = 0.QC, 1.Programmer, 2.Admin
       required: 
         - id
         - project
         - user
         - role
-    PaginatedResult:
+    Bug:
+      type: object
+      properties:
+        id:
+          type: integer
+          uniqueItems: true
+        created:
+          type: string
+          format: date-time
+        description:
+          type: string
+          maximum: 500
+        qc:
+          $ref: '#/components/schemas/User'
+        dev:
+          $ref: '#/components/schemas/User'
+        platform:
+          $ref: '#/components/schemas/Platform'
+        goodday_url:
+          type: string
+          format: url
+        image_url:
+          type: string
+          format: url
+        level:
+          type: number
+          enum: [0,1,2]
+          description: level = 0.Note, 1.Minor, 2.Major
+        status:
+          type: number
+          enum: [0,1,2,3]
+          description: status = 0.onProgress, 1.done, 2.hold, 3.cancel
+        dev_status:
+          type: number
+          enum: [0,1,2]
+          description: dev_status = 0.notStart, 1.onProgress, 2.done
+      required: 
+        - id
+        - created
+        - description
+        - qc
+        - dev
+        - platform
+        - goodday_url
+        - level
+        - status
+        - dev_status
+    Comment:
+      type: object
+      properties:
+        id:
+          type: integer
+          uniqueItems: true
+        bug:
+          $ref: '#/components/schemas/Bug'
+        creator:
+          $ref: '#/components/schemas/User'
+        created:
+          type: string
+          format: date-time
+        content:
+          type: string
+          maximum: 500
+      required: 
+        - id
+        - bug
+        - creator
+        - created
+        - content
+    paginatedResult:
       type: object
       properties:
         totalPage: { type: number, example: 2 }
@@ -635,7 +929,171 @@ components:
       description: sort direction (default 'id, desc')
       schema:
         type: string
+    bugPath:
+      name: bugId
+      in: path
+      description: Bug ID
+      required: true
+      schema:
+        type: integer
+    commentPath:
+      name: commentId
+      in: path
+      description: Comment ID
+      required: true
+      schema:
+        type: integer
   responses:
+    getSingleComment:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                example: 8
+              bug:
+                example: error mobile
+              creator:
+                example: irma
+              created:
+                example: 22-11-2023
+              content:
+                example: qc error mobile done
+    successAddComment:
+      description: record succesfully added
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                example: 15
+              bug:
+                example: error web
+              creator:
+                example: arif
+              created:
+                example: 4-06-2023
+              content:
+                example: error web mulai dikerjakan
+    getArrayComment:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            type: object
+            allOf:
+              - $ref: '#/components/schemas/paginatedResult'
+              - type: object
+                properties:
+                  results:
+                    type: array
+                    items: 
+                      properties:
+                        id:
+                          example: 3
+                        bug:
+                          example: bug web
+                        creator:
+                          example: dama
+                        created:
+                          example: 15-08-2023
+                        content:
+                          example: web sudah done
+    getSingleBug:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            properties:
+              id: 
+                example: 1
+              created:
+                example: 01-02-2023
+              description:
+                example: error button
+              qc:
+                example: irma
+              dev:
+                example: dama
+              platform:
+                example: mobile
+              goodday_url:
+                example: goodday.com
+              image_url:
+                example: image.com
+              level:
+                example: minor
+              status:
+                example: onprogress
+              dev_status:
+                example: done
+    successAddBug:
+      description: record succesfully added
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                example: 2
+              created:
+                example: 23-03-2023
+              description:
+                example: bug browser
+              qc:
+                example: mita
+              dev:
+                example: agus
+              platform:
+                example: mobile
+              goodday_url:
+                example: goodday.com
+              image_url:
+                example: image.com
+              level:
+                example: minor
+              status:
+                example: onprogress
+              dev_status:
+                example: notstart
+    getArrayBug:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            type: object
+            allOf:
+              - $ref: '#/components/schemas/paginatedResult'
+              - type: object
+                properties:
+                  results:
+                    type: array
+                    items:
+                      properties:
+                        id: 
+                          type: integer
+                          example: 1
+                        created:
+                          type: string
+                          example: 01-01-2023
+                        description:
+                          example: error button
+                        qc:
+                          example: irma
+                        dev:
+                          example: dama
+                        platform:
+                          example: mobile
+                        goodday_url:
+                          example: goodday.com
+                        image_url:
+                          example: image.com
+                        level:
+                          example: minor
+                        status:
+                          example: onprogress
+                        dev_status:
+                          example: done
     getArrayProject:
       description: successful operation
       content:
@@ -643,7 +1101,7 @@ components:
           schema:
             type: object
             allOf:
-              - $ref: '#/components/schemas/PaginatedResult'
+              - $ref: '#/components/schemas/paginatedResult'
               - type: object
                 properties:
                   results:
@@ -745,7 +1203,7 @@ components:
           schema:
             type: object
             allOf:
-              - $ref: '#/components/schemas/PaginatedResult'
+              - $ref: '#/components/schemas/paginatedResult'
               - type: object
                 properties:
                   results:
@@ -795,7 +1253,7 @@ components:
           schema:
             type: object
             allOf:
-              - $ref: '#/components/schemas/PaginatedResult'
+              - $ref: '#/components/schemas/paginatedResult'
               - type: object
                 properties:
                   results:
@@ -862,7 +1320,7 @@ components:
           schema:
             type: object
             allOf:
-              - $ref: '#/components/schemas/PaginatedResult'
+              - $ref: '#/components/schemas/paginatedResult'
               - type: object
                 properties:
                   results:
@@ -949,6 +1407,42 @@ components:
               name:
                 type: string
                 example: abidzar
+    Bug:
+      description: Bug object
+      required: true
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              description:
+                example: bug button
+              qc:
+                example: 33
+              dev: 
+                example: 34
+              platform:
+                example: web
+              goodday_url:
+                example: goodday.com
+              image_url:
+                example: image.com
+              level:
+                example: note
+              status:
+                example: onprogress
+              dev_status:
+                example: notstart
+    Comment:
+      description: Comment object
+      required: true
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              content:
+                example: error web mulai dikerjakan
   securitySchemes:
     testAuth:
       type: http