Kaynağa Gözat

ownership & membership

athrainsky 11 ay önce
ebeveyn
işleme
ecfe95e14c

BIN
membership.jpg


+ 120 - 0
qc.txt

@@ -0,0 +1,120 @@
+
+
+
+Ownersip 
+- user yang membuat project otomatis menjadi owner dari project tsb -> spring
+- owner tidak dapat diubah 											-> put tidak merubah owner
+- rights owner: 
+- owner dapat mengubah nama, deskripsi  project 					-> /projects/id put
+- owner dapat menghapus project 									-> /projects/id delete
+- owner dapat menambah, mengubah dan menhapus platform project 		-> /platforms/id post, put, delete
+- owner dapat menambahkan dan menghapus member project 				-> /projects/member post, /projects/id/member delete
+
+Membership:
+- Member memiliki 3 macam role : 0:QC, 1: Programmer, 2:Admin
+- Tiap user bisa memiliki lebih dari 1 role dalam satu project. (contoh: user Joko bisa di daftarkan sebagai QC dan Admin)
+- Tiap Role mempunyai rights berbeda
+Rights Admin:
+- Admin dapat mengubah nama, deskripsi  project						-> /projects/id put
+- Admin dapat menambah, mengubah dan menhapus platform project		-> /platforms/id post, put, delete
+- Admin dapat menambahkan dan menghapus member project				-> /projects/member post, /projects/id/member delete
+- Admin tidak dapat menghapus member dengan role Admin				-> 
+Rights lain menyusul setelah ada table bug.
+
+
+insert  into `user`(`user_id`,`name`,`password`,`username`) values 
+(1,'test user','$2a$10$Y8LgDPJiAsbw7n5pURhGVOmi5.LWpfJaX7ZgSDbjsQXEnsCFPdhB2','user'),
+
+/projects
+get		200	ok
+		401 ok
+post	201 ok
+		400 ok
+		401 ok
+		409	ok
+		413 ok
+/projects/id
+get		200 ok
+		401 ok
+		404 ok
+put		200 ok
+		400 ok
+		401 ok
+		403 ok
+		409 ok
+		413 ok
+delete	200 ok
+		205 ok
+		401 ok
+		403	ok
+/projects/id/member
+get		200 ok
+		401 ok
+		404 ok
+post	201 ok
+		400 ok
+		401 ok
+		403	ok
+		404 ok
+		409 ok
+/projects/member/id
+get		200 ok
+		401 ok
+		404 ok
+delete	200 ok
+		401 ok
+		403	ok
+		404 ok
+/platforms
+get 	200 ok
+		401 ok
+post 	201 ok
+		400 ok
+		401 ok
+		403	ok
+		404 ok
+		409 ok
+		413 ok
+/platforms/id
+get 	200 ok
+		401 ok
+		404 ok
+put 	200 ok
+		400 ok
+		401 ok
+		403	ok
+		404 ok
+		409 ok
+		413 ok
+delete 	200 ok
+		401 ok
+		403	ok
+		404 ok
+/users
+get 	200 ok
+		401 ok
+post	201 ok
+		400 ok
+		401 ok
+		409 ok
+		413 ok
+/users/id
+get		200 ok
+		401 ok
+		404 ok
+put		200 ok
+		400 ok
+		401 ok
+		404 ok
+		409 ok
+		413 ok
+delete	200 ok
+		205 ok
+		401 ok
+		404 ok
+/users/id/password
+put		200 ok
+		400 ok
+		401 ok
+		403 ok
+		404 ok

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

@@ -0,0 +1,109 @@
+package com.swagger.rest.controllers
+
+import com.swagger.rest.models.MemberInput
+import com.swagger.rest.models.ProjectMember
+import com.swagger.rest.repositories.MemberRepository
+import com.swagger.rest.repositories.ProjectRepository
+import com.swagger.rest.repositories.UserRepository
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.web.bind.annotation.*
+
+@RestController
+@RequestMapping("/api/v1")
+class MemberController(
+    private val memberRepository: MemberRepository,
+    private val projectRepository: ProjectRepository,
+    private val userRepository: UserRepository
+) {
+
+    @GetMapping("/projects/{id}/member")
+    fun getMemberByProject(@PathVariable("id") id: Long): Any {
+        val memberData = memberRepository.findByProject(id.toString())
+        return if (memberData.isNotEmpty()) {
+            ResponseEntity<List<ProjectMember>>(memberData, HttpStatus.OK)
+        } else {
+            ResponseEntity<List<ProjectMember>>(HttpStatus.NOT_FOUND)
+        }
+    }
+
+    @GetMapping("/projects/member/{id}")
+    fun getMemberProject(@PathVariable("id") id: Long): Any {
+        val memberData = memberRepository.findById(id)
+        return if (memberData.isPresent) {
+            ResponseEntity<ProjectMember>(memberData.get(), HttpStatus.OK)
+        } else {
+            ResponseEntity<ProjectMember>(HttpStatus.NOT_FOUND)
+        }
+    }
+
+    @DeleteMapping("/projects/member/{id}")
+    fun deleteProjectMember(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        return try {
+            val find = memberRepository.findById(id)
+            if (find.isPresent) {
+                val validOwner =
+                    projectRepository.validOwner(find.get().project_id!!.id.toString(), userId.id.toString())
+                val validAdmin =
+                    memberRepository.validRole(find.get().project_id!!.id.toString(), userId.id.toString(), "2")
+                val targetNotAdmin = memberRepository.targetRole(
+                    find.get().project_id!!.id.toString(),
+                    find.get().user_id!!.id.toString(),
+                    find.get().role.toString()
+                )
+                if (validOwner > 0 || (validAdmin > 0 && targetNotAdmin > 0)) {
+                    memberRepository.deleteById(id)
+                    ResponseEntity(HttpStatus.OK)
+                } else {
+                    ResponseEntity(HttpStatus.FORBIDDEN)
+                }
+            } else {
+                ResponseEntity(HttpStatus.NOT_FOUND)
+            }
+        } catch (e: Exception) {
+            ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+
+    @PostMapping("/projects/{id}/member")
+    fun addProjectMember(
+        @PathVariable("id") id: Long, @RequestBody member: MemberInput
+    ): Any {
+        return try {
+            val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+            val found = projectRepository.findById(id)
+            val user = userRepository.findById(member.user_id!!.toLong())
+            val validOwner = projectRepository.validOwner(id.toString(), userId.id.toString())
+            val validAdmin = memberRepository.validRole(id.toString(), userId.id.toString(), "2")
+            if (validOwner > 0 || validAdmin > 0) {
+                if (enumValues<Member>().any { it.name == member.role!!.uppercase() }) {
+                    val role = Member.valueOf(member.role!!.uppercase()).ordinal
+                    val dup = memberRepository.findDup(id.toString(), role.toString(), member.user_id.toString()).size
+                    if (found.isEmpty || user.isEmpty) {
+                        ResponseEntity<ProjectMember>(HttpStatus.NOT_FOUND)
+                    } else if (dup > 0) {//duplicate
+                        ResponseEntity<ProjectMember>(HttpStatus.CONFLICT)
+                    } else {
+                        val saveMember = ProjectMember()
+                        saveMember.project_id = found.get()
+                        saveMember.user_id = user.get()
+                        saveMember.role = role
+                        ResponseEntity<ProjectMember>(memberRepository.save(saveMember), HttpStatus.CREATED)
+                    }
+                } else {// invalid
+                    ResponseEntity<ProjectMember>(HttpStatus.BAD_REQUEST)
+                }
+            } else {
+                ResponseEntity<ProjectMember>(HttpStatus.FORBIDDEN)
+            }
+        } catch (e: Exception) {
+            ResponseEntity<ProjectMember>(null, HttpStatus.INTERNAL_SERVER_ERROR)
+        }
+    }
+}
+
+private enum class Member {
+    QC, PROGRAMMER, ADMIN
+}

+ 82 - 46
src/main/kotlin/com/swagger/rest/controllers/PlatformController.kt

@@ -2,10 +2,13 @@ package com.swagger.rest.controllers
 
 import com.swagger.rest.models.Platform
 import com.swagger.rest.models.PlatformInput
+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 org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
+import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.web.bind.annotation.*
 import java.util.*
 
@@ -13,18 +16,19 @@ import java.util.*
 @RequestMapping("/api/v1")
 class PlatformController(
     private val platformRepository: PlatformRepository,
-    private val projectRepository: ProjectRepository
+    private val projectRepository: ProjectRepository,
+    private val memberRepository: MemberRepository,
+    private val userRepository: UserRepository
 ) {
 
     @GetMapping("/platforms")
     fun getPlatformByProjectId(@RequestParam(required = false) project: String?): Any? {
         return try {
-            val platforms: List<Platform> =
-                if (project == null) {
-                    platformRepository.findAll()
-                } else {
-                    platformRepository.findByProject(project)
-                }
+            val platforms: List<Platform> = if (project == null) {
+                platformRepository.findAll()
+            } else {
+                platformRepository.findByProject(project)
+            }
             if (platforms.isNotEmpty()) {
                 ResponseEntity<List<Platform>?>(platforms, HttpStatus.OK)
             } else {
@@ -49,62 +53,94 @@ class PlatformController(
     fun addPlatformByProjectId(
         @RequestBody platform: PlatformInput
     ): ResponseEntity<Platform> {
-        return try {
-            val found = platformRepository.findByName(platform.name, platform.project_id.toString()).size
-            val foundProject = projectRepository.findById(platform.project_id!!.toLong()).isPresent
-            if (platform.name!!.isNotBlank()) {
-                if (!foundProject) {//project not found
-                    ResponseEntity<Platform>(HttpStatus.NOT_FOUND)
-                } else if (platform.name!!.length > 100) {//too long
-                    ResponseEntity<Platform>(HttpStatus.PAYLOAD_TOO_LARGE)
-                } else if (found > 0) {//duplicate
-                    ResponseEntity<Platform>(HttpStatus.CONFLICT)
-                } else {
-                    val newPlatform = Platform()
-                    newPlatform.name = platform.name!!.trim()
-                    newPlatform.project_id = projectRepository.findById(platform.project_id!!).get()
-                    ResponseEntity<Platform>(platformRepository.save(newPlatform), HttpStatus.CREATED)
+        val proj = projectRepository.findByNameContaining(platform.project_name).size
+        return if (proj == 0 || platform.project_name!!.isBlank()) {
+            ResponseEntity<Platform>(HttpStatus.NOT_FOUND)
+        } else if (platform.project_name!!.isNotEmpty()) {
+            val foundProject = projectRepository.findByName(platform.project_name)
+            val found = platformRepository.findByName(platform.name, foundProject.id.toString()).size
+            val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+            val validOwner = projectRepository.validOwner(foundProject.id.toString(), userId.id.toString())
+            val validAdmin = memberRepository.validRole(foundProject.id.toString(), userId.id.toString(), "2")
+            if (validOwner > 0 || validAdmin > 0) {
+                if (platform.name!!.isNotBlank()) {
+                    if (platform.name!!.length > 100) {//too long
+                        ResponseEntity<Platform>(HttpStatus.PAYLOAD_TOO_LARGE)
+                    } else if (found > 0) {//duplicate
+                        ResponseEntity<Platform>(HttpStatus.CONFLICT)
+                    } else {
+                        val newPlatform = Platform()
+                        newPlatform.name = platform.name!!.trim()
+                        newPlatform.project_id = foundProject
+                        ResponseEntity<Platform>(platformRepository.save(newPlatform), HttpStatus.CREATED)
+                    }
+                } else {//invalid name
+                    ResponseEntity<Platform>(HttpStatus.BAD_REQUEST)
                 }
-            } else {//invalid name
-                ResponseEntity<Platform>(HttpStatus.BAD_REQUEST)
+            } else {
+                ResponseEntity<Platform>(HttpStatus.FORBIDDEN)
             }
-        } catch (e: Exception) {
+        } else {
             ResponseEntity<Platform>(null, HttpStatus.INTERNAL_SERVER_ERROR)
         }
     }
 
     @PutMapping("/platforms/{id}")
     fun updatePlatformById(@PathVariable("id") id: Long, @RequestBody input: PlatformInput): Any {
-        val targetProject = projectRepository.findById(input.project_id!!.toLong())//target project
-        val platformExist = platformRepository.findById(id)//exist data
-        return if (input.name!!.isNotBlank()) {
-            if (input.name!!.length > 100) {//too long
-                ResponseEntity<Platform>(HttpStatus.PAYLOAD_TOO_LARGE)
-            } else if (targetProject.isEmpty || platformExist.isEmpty) {//target project not found
-                ResponseEntity<Platform>(HttpStatus.NOT_FOUND)
-            } else {
-                val check = platformRepository.findByName(input.name, input.project_id.toString())
-                if (check.isEmpty() || (input.name == platformExist.get().name && input.project_id.toString() == platformExist.get().project_id!!.id.toString())) {//tidak ada yg sama
-                    val savePlatform = platformExist.get()
-                    savePlatform.project_id = targetProject.get()
-                    savePlatform.name = input.name!!.trim()
-                    ResponseEntity<Platform>(platformRepository.save(savePlatform), HttpStatus.OK)
+        val targetProj = projectRepository.findByNameContaining(input.project_name).size
+        return if (targetProj > 0) {
+            val targetProject = projectRepository.findByName(input.project_name)//target project
+            val platformExist = platformRepository.findById(id)//exist data
+            val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+            val validOwner = projectRepository.validOwner(targetProject.id.toString(), userId.id.toString())
+            val validAdmin = memberRepository.validRole(targetProject.id.toString(), userId.id.toString(), "2")
+            if (validOwner > 0 || validAdmin > 0) {
+                if (input.name!!.isNotBlank()) {
+                    if (input.name!!.length > 100) {//too long
+                        ResponseEntity<Platform>(HttpStatus.PAYLOAD_TOO_LARGE)
+                    } else if (targetProject.name.isEmpty() || platformExist.isEmpty) {//target project not found
+                        ResponseEntity<Platform>(HttpStatus.NOT_FOUND)
+                    } else {
+                        val check = platformRepository.findByName(input.name, targetProject.id.toString())
+                        if (check.isEmpty() || (input.name == platformExist.get().name && targetProject.id.toString() == platformExist.get().project_id!!.id.toString())) {//tidak ada yg sama
+                            val savePlatform = platformExist.get()
+                            savePlatform.project_id = targetProject
+                            savePlatform.name = input.name!!.trim()
+                            ResponseEntity<Platform>(platformRepository.save(savePlatform), HttpStatus.OK)
+                        } else {
+                            ResponseEntity<Platform>(HttpStatus.CONFLICT)
+                        }
+                    }
                 } else {
-                    ResponseEntity<Platform>(HttpStatus.CONFLICT)
+                    ResponseEntity<Platform>(HttpStatus.BAD_REQUEST)
                 }
+            } else {//invalid name
+                ResponseEntity<Platform>(HttpStatus.FORBIDDEN)
             }
-        } else {//invalid name
-            ResponseEntity<Platform>(HttpStatus.BAD_REQUEST)
+        } else {
+            ResponseEntity<Platform>(HttpStatus.NOT_FOUND)
         }
     }
 
     @DeleteMapping("/platforms/{id}")
     fun deletePlatformById(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
-        val find = platformRepository.findById(id)
+        val count = platformRepository.findById(id).isPresent
         return try {
-            if (find.isPresent) {
-                platformRepository.deleteById(id)
-                ResponseEntity(HttpStatus.OK)
+            if (count) {
+                val find = platformRepository.findById(id)
+                val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+                val validOwner = projectRepository.validOwner(find.get().project_id!!.id.toString(), userId.id.toString())
+                val validAdmin = memberRepository.validRole(find.get().project_id!!.id.toString(), userId.id.toString(), "2")
+                if (validOwner > 0 || validAdmin > 0) {
+                    if (find.isPresent) {
+                        platformRepository.deleteById(id)
+                        ResponseEntity(HttpStatus.OK)
+                    } else {
+                        ResponseEntity(HttpStatus.NOT_FOUND)
+                    }
+                } else {
+                    ResponseEntity(HttpStatus.FORBIDDEN)
+                }
             } else {
                 ResponseEntity(HttpStatus.NOT_FOUND)
             }

+ 45 - 23
src/main/kotlin/com/swagger/rest/controllers/ProjectController.kt

@@ -1,10 +1,13 @@
 package com.swagger.rest.controllers
 
 import com.swagger.rest.models.Project
+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 org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
+import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.web.bind.annotation.*
 import java.util.*
 
@@ -12,7 +15,9 @@ import java.util.*
 @RequestMapping("/api/v1")
 class ProjectController(
     private val projectRepository: ProjectRepository,
-    private val platformRepository: PlatformRepository
+    private val platformRepository: PlatformRepository,
+    private val userRepository: UserRepository,
+    private val memberRepository: MemberRepository
 ) {
 
     @GetMapping("/projects")
@@ -50,7 +55,10 @@ class ProjectController(
                 } else if (found > 0) {//duplicate
                     ResponseEntity<Project>(HttpStatus.CONFLICT)
                 } else {
+                    val username: String = SecurityContextHolder.getContext().authentication.name
+                    val userLogin = userRepository.getUserByUsername(username)
                     project.name = project.name.trim()
+                    project.owner = userLogin
                     val saveProject: Project = projectRepository.save(project)
                     ResponseEntity<Project>(saveProject, HttpStatus.CREATED)
                 }
@@ -66,23 +74,30 @@ class ProjectController(
     fun updateProjectById(@PathVariable("id") id: Long, @RequestBody project: Project): ResponseEntity<out Any?> {
         val projectData = projectRepository.findById(id)
         val found = projectRepository.findByNameContaining(project.name).size
-        return if (project.name.isNotBlank()) {
-            if (project.name.length > 100 || project.description!!.length > 255) {//too long
-                ResponseEntity<Project>(HttpStatus.PAYLOAD_TOO_LARGE)
-            } else if (projectData.isPresent) {
-                if ((project.name == projectData.get().name && found > 0) || (project.name !== projectData.get().name && found == 0)) {
-                    val saveProject = projectData.get()
-                    saveProject.name = project.name.trim()
-                    saveProject.description = project.description
-                    ResponseEntity<Any?>(projectRepository.save(saveProject), HttpStatus.OK)
-                } else {//duplicate
-                    ResponseEntity<Project>(HttpStatus.CONFLICT)
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        val validOwner = projectRepository.validOwner(id.toString(), userId.id.toString())
+        val validAdmin = memberRepository.validRole(id.toString(), userId.id.toString(), "2")
+        return if (validOwner > 0 || validAdmin > 0) {
+            if (project.name.isNotBlank()) {
+                if (project.name.length > 100 || project.description!!.length > 255) {//too long
+                    ResponseEntity<Project>(HttpStatus.PAYLOAD_TOO_LARGE)
+                } else if (projectData.isPresent) {
+                    if ((project.name == projectData.get().name && found > 0) || (project.name !== projectData.get().name && found == 0)) {
+                        val saveProject = projectData.get()
+                        saveProject.name = project.name.trim()
+                        saveProject.description = project.description
+                        ResponseEntity<Any?>(projectRepository.save(saveProject), HttpStatus.OK)
+                    } else {//duplicate
+                        ResponseEntity<Project>(HttpStatus.CONFLICT)
+                    }
+                } else {//target invalid
+                    ResponseEntity<Project?>(HttpStatus.NOT_FOUND)
                 }
-            } else {//target invalid
-                ResponseEntity<Project?>(HttpStatus.NOT_FOUND)
+            } else {//name invalid
+                ResponseEntity<Project?>(HttpStatus.BAD_REQUEST)
             }
-        } else {//name invalid
-            ResponseEntity<Project?>(HttpStatus.BAD_REQUEST)
+        } else {
+            ResponseEntity<Project>(HttpStatus.FORBIDDEN)
         }
     }
 
@@ -90,17 +105,24 @@ class ProjectController(
     fun deleteProject(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
         val find = projectRepository.findById(id)
         val used = platformRepository.findByProject(id.toString()).size
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        val validOwner = projectRepository.validOwner(id.toString(), userId.id.toString())
         return try {
-            if (used > 0) {//child used in transaction
-                ResponseEntity(HttpStatus.RESET_CONTENT)
-            } else if (find.isPresent) {
-                projectRepository.deleteById(id)
-                ResponseEntity(HttpStatus.OK)
-            } else {//project not found
-                ResponseEntity(HttpStatus.NOT_FOUND)
+            if (validOwner > 0) {
+                if (used > 0) {//child used in transaction
+                    ResponseEntity(HttpStatus.RESET_CONTENT)
+                } else if (find.isPresent) {
+                    projectRepository.deleteById(id)
+                    ResponseEntity(HttpStatus.OK)
+                } else {//project not found
+                    ResponseEntity(HttpStatus.NOT_FOUND)
+                }
+            } else {
+                ResponseEntity(HttpStatus.FORBIDDEN)
             }
         } catch (e: Exception) {
             ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR)
         }
     }
+
 }

+ 13 - 2
src/main/kotlin/com/swagger/rest/controllers/UserController.kt

@@ -2,9 +2,11 @@ package com.swagger.rest.controllers
 
 import com.swagger.rest.models.User
 import com.swagger.rest.models.UserInput
+import com.swagger.rest.repositories.MemberRepository
 import com.swagger.rest.repositories.UserRepository
 import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
+import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.security.crypto.password.PasswordEncoder
 import org.springframework.web.bind.annotation.*
 
@@ -12,7 +14,8 @@ import org.springframework.web.bind.annotation.*
 @RequestMapping("/api/v1")
 class UserController(
     private val userRepository: UserRepository,
-    private val passwordEncoder: PasswordEncoder
+    private val passwordEncoder: PasswordEncoder,
+    private val memberRepository: MemberRepository
 ) {
 
     @GetMapping("/users")
@@ -94,8 +97,16 @@ class UserController(
     @DeleteMapping("/users/{id}")
     fun deleteUserById(@PathVariable("id") id: Long): ResponseEntity<HttpStatus> {
         val find = userRepository.findById(id)
+        val used = memberRepository.findByUser(id.toString()).size
+        val userId = userRepository.getUserByUsername(SecurityContextHolder.getContext().authentication.name)
+        val validAdmin = memberRepository.validRole("", userId.id.toString(), "2")
+        val targetRole = memberRepository.validRole("", id.toString(), "2")
         return try {
-            if (find.isPresent) {
+            if (validAdmin > 0 && targetRole > 0) {
+                ResponseEntity(HttpStatus.FORBIDDEN)
+            } else if (used > 0) {
+                ResponseEntity(HttpStatus.RESET_CONTENT)
+            } else if (find.isPresent) {
                 userRepository.deleteById(id)
                 ResponseEntity(HttpStatus.OK)
             } else {

+ 8 - 0
src/main/kotlin/com/swagger/rest/models/MemberInput.kt

@@ -0,0 +1,8 @@
+package com.swagger.rest.models
+
+class MemberInput {
+
+    var user_id: Long? = 0
+
+    var role: String? = ""
+}

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

@@ -4,6 +4,6 @@ class PlatformInput {
 
     var name: String? = ""
 
-    var project_id: Long? = 0
+    var project_name: String? = ""
 
 }

+ 8 - 0
src/main/kotlin/com/swagger/rest/models/Project.kt

@@ -22,6 +22,14 @@ class Project {
     @JsonIgnore
     var project_id: List<Platform> = mutableListOf()
 
+    @OneToMany(mappedBy = "project_id", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var member_id: List<ProjectMember> = mutableListOf()
+
+    @ManyToOne
+    @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]"
     }

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

@@ -0,0 +1,23 @@
+package com.swagger.rest.models
+
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "project_member")
+class ProjectMember {
+    @Id
+    @Column(name = "member_id", updatable = false, nullable = false)
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    var id: Long = 0
+
+    @ManyToOne
+    @JoinColumn(name = "project_id", referencedColumnName = "project_id", foreignKey = ForeignKey(name = "FK_memPro"))
+    var project_id: Project? = null
+
+    @ManyToOne
+    @JoinColumn(name = "user_id", referencedColumnName = "user_id", foreignKey = ForeignKey(name = "FK_memUser"))
+    var user_id: User? = null
+
+    @Column(columnDefinition = "TINYINT")
+    var role: Int = 0
+}

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

@@ -1,5 +1,6 @@
 package com.swagger.rest.models
 
+import com.fasterxml.jackson.annotation.JsonIgnore
 import com.fasterxml.jackson.annotation.JsonProperty
 import jakarta.persistence.*
 
@@ -21,4 +22,12 @@ class User {
 
     @Column(name = "name", length = 255)
     var name: String = ""
+
+    @OneToMany(mappedBy = "user_id", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var user_id: List<ProjectMember> = mutableListOf()
+
+    @OneToMany(mappedBy = "owner", cascade = [CascadeType.ALL])
+    @JsonIgnore
+    var owner: List<Project> = mutableListOf()
 }

+ 24 - 0
src/main/kotlin/com/swagger/rest/repositories/MemberRepository.kt

@@ -0,0 +1,24 @@
+package com.swagger.rest.repositories
+
+import com.swagger.rest.models.ProjectMember
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+
+interface MemberRepository : JpaRepository<ProjectMember, Long> {
+    @Query("SELECT f.* FROM project_member f WHERE project_id=?1", nativeQuery = true)
+    fun findByProject(project: String?): List<ProjectMember>
+
+    @Query("SELECT f.* FROM project_member f WHERE project_id=?1 and role=?2 and user_id=?3", nativeQuery = true)
+    fun findDup(project: String?, role: String?, user: String?): List<ProjectMember>
+
+    @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)
+    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)
+    fun targetRole(project: String?, user: String?, role: String?): Int
+
+
+}

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

@@ -11,5 +11,4 @@ interface PlatformRepository : JpaRepository<Platform, Long> {
     @Query("SELECT f.* FROM platform f WHERE project_id=?1", nativeQuery = true)
     fun findByProject(project: String?): List<Platform>
 
-    fun findByNameContaining(name: String?): List<Platform>
 }

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

@@ -2,8 +2,14 @@ package com.swagger.rest.repositories
 
 import com.swagger.rest.models.Project
 import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
 
 
 interface ProjectRepository : JpaRepository<Project, Long> {
     fun findByNameContaining(name: String?): List<Project>
+
+    fun findByName(name: String?): Project
+
+    @Query("SELECT COUNT(0) FROM project WHERE project_id=?1 AND owner=?2", nativeQuery = true)
+    fun validOwner(project: String?, owner: String?): Int
 }

+ 524 - 177
swagger3 project.yml

@@ -1,8 +1,14 @@
 openapi: 3.0.1
 info:
   title: Project API
-  description: Latihan Project API
-  version: 3.0.0
+  description: |
+    Latihan Project API 
+    
+    - CRUD Project 
+    - Platform Relationship 
+    - Auth 
+    - Membership & Ownership
+  version: 4.0.0
 servers:
 - url: http://localhost:8080/api/v1
 tags:
@@ -22,11 +28,11 @@ paths:
       operationId: getProject
       responses:
         200:
-          $ref: '#/components/responses/200AP'
+          $ref: '#/components/responses/getArrayProject'
         401:
           $ref: '#/components/responses/UnauthorizedError'
       security: 
-      - basicAuth: []
+        - testAuth: []
     post:
       tags:
       - projects
@@ -37,7 +43,7 @@ paths:
         $ref: '#/components/requestBodies/Project'
       responses:
         201:
-          $ref: '#/components/responses/201'
+          $ref: '#/components/responses/successAddProject'
         400:
           $ref: '#/components/responses/400'
         401:
@@ -47,7 +53,7 @@ paths:
         413:
           $ref: '#/components/responses/413'
       security: 
-      - basicAuth: []
+        - testAuth: []
   /projects/{projectId}:
     get:
       tags:
@@ -59,13 +65,13 @@ paths:
         - $ref: '#/components/parameters/projectPath'
       responses:
         200:
-          $ref: '#/components/responses/200SP'
+          $ref: '#/components/responses/getSingleProject'
         401:
           $ref: '#/components/responses/UnauthorizedError'
         404:
           $ref: '#/components/responses/404'
       security: 
-      - basicAuth: []
+        - testAuth: []
     put:
       tags:
       - projects
@@ -78,19 +84,19 @@ paths:
         $ref: '#/components/requestBodies/Project'
       responses:
         200:
-          $ref: '#/components/responses/200AP'
+          $ref: '#/components/responses/getSingleProject'
         400:
           $ref: '#/components/responses/400'
         401:
           $ref: '#/components/responses/UnauthorizedError'
-        404:
-          $ref: '#/components/responses/404'
+        403:
+          $ref: '#/components/responses/403'
         409:
           $ref: '#/components/responses/409'
         413:
           $ref: '#/components/responses/413'
       security: 
-      - basicAuth: []
+        - testAuth: []
     delete:
       tags:
       - projects
@@ -106,10 +112,90 @@ paths:
           description: Unable to delete. Data is used.
         401:
           $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
+      security: 
+        - testAuth: []
+  /projects/{projectId}/member:
+    get:
+      tags: 
+      - projects
+      summary: find all member by project
+      description: return all member by project
+      operationId: getMemberByProject
+      parameters: 
+        - $ref: '#/components/parameters/projectPath'
+      responses:
+        200:
+          $ref: '#/components/responses/getArrayMember'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
         404:
           $ref: '#/components/responses/404'
+      security:
+      - testAuth: []
+    post:
+      tags:
+      - projects
+      summary: add project member
+      description: add project member
+      operationId: addProjectMember
+      parameters: 
+        - $ref: '#/components/parameters/projectPath'
+      requestBody:
+        $ref: '#/components/requestBodies/ProjectMember'
+      responses:
+        201:
+          $ref: '#/components/responses/successAddMember'
+        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'
       security: 
-      - basicAuth: []
+        - testAuth: []
+  /projects/member/{memberId}:
+    get:
+      tags: 
+      - projects
+      summary: find member by ID
+      description: return member by ID
+      operationId: getMemberProject
+      parameters: 
+        - $ref: '#/components/parameters/memberPath'
+      responses:
+        200:
+          $ref: '#/components/responses/getSingleMember'
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+      - testAuth: []
+    delete:
+      tags: 
+      - projects
+      summary: delete project member
+      description: delete project member 
+      operationId: deleteProjectMember
+      parameters: 
+        - $ref: '#/components/parameters/memberPath'
+      responses:
+        200:
+          description: successful operation
+        401:
+          $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
+        404:
+          $ref: '#/components/responses/404'
+      security: 
+        - testAuth: []
   /platforms:
     get:
       tags: 
@@ -121,17 +207,11 @@ paths:
         - $ref: '#/components/parameters/projectQuery'
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                type: array
-                items: 
-                  $ref: '#/components/schemas/Platform'
+          $ref: '#/components/responses/getArrayPlatform'
         401:
           $ref: '#/components/responses/UnauthorizedError'
       security: 
-      - basicAuth: []
+        - testAuth: []
     post:
       tags: 
       - platforms
@@ -139,30 +219,16 @@ paths:
       description: add new platform for a project
       operationId: addPlatformByProjectId
       requestBody:
-        description: Platform object
-        required: true
-        content:
-          application/json:
-            schema:
-              type: object
-              properties:
-                name:
-                  type: string
-                  example: web
-                project_id:
-                  type: integer
-                  example: 1
+        $ref: '#/components/requestBodies/Platform'
       responses:
         201:
-          description: record successfully added
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Platform'
+          $ref: '#/components/responses/successAddPlatform'
         400:
           $ref: '#/components/responses/400'
         401:
           $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
         404:
           $ref: '#/components/responses/404'
         409:
@@ -170,7 +236,7 @@ paths:
         413:
           $ref: '#/components/responses/413'
       security: 
-      - basicAuth: []
+        - testAuth: []
   /platforms/{platformId}:
     get:
       tags: 
@@ -182,17 +248,13 @@ paths:
         - $ref: '#/components/parameters/platformPath'
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Platform'
+          $ref: '#/components/responses/getSinglePlatform'
         401:
           $ref: '#/components/responses/UnauthorizedError'
         404:
           $ref: '#/components/responses/404'
       security: 
-      - basicAuth: []
+        - testAuth: []
     put:
       tags: 
       - platforms
@@ -202,30 +264,16 @@ paths:
       parameters: 
         - $ref: '#/components/parameters/platformPath'
       requestBody:
-        description: Platform object
-        required: true
-        content:
-          application/json:
-            schema:
-              type: object
-              properties:
-                name:
-                  type: string
-                  example: web
-                project_id:
-                  type: integer
-                  example: 1
+        $ref: '#/components/requestBodies/Platform'
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/Platform'
+          $ref: '#/components/responses/getSinglePlatform'
         400:
           $ref: '#/components/responses/400'
         401:
           $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
         404:
           $ref: '#/components/responses/404'
         409:
@@ -233,7 +281,7 @@ paths:
         413:
           $ref: '#/components/responses/413'
       security: 
-      - basicAuth: []
+        - testAuth: []
     delete:
       tags: 
       - platforms
@@ -247,10 +295,12 @@ paths:
           description: successful operation
         401:
           $ref: '#/components/responses/UnauthorizedError'
+        403:
+          $ref: '#/components/responses/403'
         404:
           $ref: '#/components/responses/404'
       security: 
-        - basicAuth: []
+        - testAuth: []
   /users:
     get:
       tags: 
@@ -260,26 +310,11 @@ paths:
       operationId: getUser
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                type: array
-                items: 
-                  properties:
-                    id:
-                      type: integer
-                      example: 1
-                    username:
-                      type: string
-                      example: abi
-                    name:
-                      type: string
-                      example: abidzar
+          $ref: '#/components/responses/getArrayUser'
         401:
           $ref: '#/components/responses/UnauthorizedError'
       security: 
-      - basicAuth: [] 
+        - testAuth: []
     post:
       tags: 
       - users
@@ -287,38 +322,10 @@ paths:
       description: add new user
       operationId: AddUser
       requestBody:
-        description: User object
-        required: true
-        content:
-          application/json:
-            schema:
-              type: object
-              properties:
-                username:
-                  type: string
-                  example: abi
-                password:
-                  type: string
-                  example: abi123
-                name:
-                  type: string
-                  example: abidzar
+        $ref: '#/components/requestBodies/User'
       responses:
         201:
-          description: record successfully added
-          content:
-            application/json:
-              schema:
-                  properties:
-                    id:
-                      type: integer
-                      example: 1
-                    username:
-                      type: string
-                      example: abi
-                    name:
-                      type: string
-                      example: abidzar
+          $ref: '#/components/responses/successAddUser'
         400:
           $ref: '#/components/responses/400'
         401:
@@ -328,7 +335,7 @@ paths:
         413:  
           $ref: '#/components/responses/413'
       security: 
-        - basicAuth: []
+        - testAuth: []
   /users/{userId}:
     get:
       tags: 
@@ -340,26 +347,13 @@ paths:
         - $ref: '#/components/parameters/UserPath'
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                  properties:
-                    id:
-                      type: integer
-                      example: 1
-                    username:
-                      type: string
-                      example: abi
-                    name:
-                      type: string
-                      example: abidzar
+          $ref: '#/components/responses/getSingleUser'
         401:
           $ref: '#/components/responses/UnauthorizedError'
         404:
           $ref: '#/components/responses/404'
       security: 
-      - basicAuth: []
+        - testAuth: []
     put:
       tags: 
       - users
@@ -384,20 +378,7 @@ paths:
                   example: abidzar
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                  properties:
-                    id:
-                      type: integer
-                      example: 1
-                    username:
-                      type: string
-                      example: abi
-                    name:
-                      type: string
-                      example: abidzar
+          $ref: '#/components/responses/getSingleUser'
         400:
           $ref: '#/components/responses/400'
         401:
@@ -409,7 +390,7 @@ paths:
         413:
           $ref: '#/components/responses/413'
       security: 
-      - basicAuth: []
+        - testAuth: []
     delete:
       tags: 
       - users
@@ -421,12 +402,14 @@ paths:
       responses:
         200:
           description: successful operation
+        205:
+          description: Unable to delete. Data is used.
         401:
           $ref: '#/components/responses/UnauthorizedError'
         404:
           $ref: '#/components/responses/404'
       security: 
-        - basicAuth: []
+        - testAuth: []
   /users/{userId}/password:
     put:
       tags: 
@@ -452,20 +435,7 @@ paths:
                   example: abi456
       responses:
         200:
-          description: successful operation
-          content:
-            application/json:
-              schema:
-                  properties:
-                    id:
-                      type: integer
-                      example: 1
-                    username:
-                      type: string
-                      example: abi
-                    name:
-                      type: string
-                      example: abidzar
+          $ref: '#/components/responses/getSingleUser'
         400:
           $ref: '#/components/responses/400'
         401:
@@ -475,13 +445,14 @@ paths:
         404:
           $ref: '#/components/responses/404'
       security: 
-      - basicAuth: []
+        - testAuth: []
 components:
   schemas:
     Project:
       required:
         - id
         - name
+        - owner
       type: object
       properties:
         id:
@@ -497,6 +468,8 @@ components:
           type: string
           maximum: 255
           example: PropInspector
+        owner:
+          $ref: '#/components/schemas/User'
     Platform:
       type: object
       required: 
@@ -539,6 +512,25 @@ components:
           type: string
           maximum: 255
           example: abidzar
+    project_member:
+      type: object
+      properties:
+        id:
+          type: integer
+          uniqueItems: true
+        project_id:
+          $ref: '#/components/schemas/Project'
+        user_id:
+          $ref: '#/components/schemas/User'
+        role:
+          type: number
+          format: int32
+          description: role = 0.QC, 1. Programmer, 2.Admin
+      required: 
+        - id
+        - project_id
+        - user_id
+        - role
   parameters:
     projectPath:
       name: projectId
@@ -567,35 +559,358 @@ components:
       description: Project ID
       schema:
         type: integer
+    memberPath:
+      name: memberId
+      in: path
+      description: Project Member ID
+      required: true
+      schema:
+        type: integer
   responses:
-    200SP:
+    getArrayProject:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            type: array
+            items:
+              properties:
+                id: 
+                  type: integer
+                  example: 1
+                name:
+                  type: string
+                  example: TM
+                description:
+                  type: string
+                  example: TelMesengger
+                owner:
+                  properties:
+                    id:
+                      type: integer
+                      example: 1
+                    username:
+                      type: string
+                      example: abi
+                    name:
+                      type: string
+                      example: abidzar
+    getSingleProject:
       description: successful operation
       content:
         application/json:
           schema:
-            $ref: '#/components/schemas/Project'
-    200AP:
+            properties:
+              id: 
+                type: integer
+                example: 1
+              name:
+                type: string
+                example: TM
+              description:
+                type: string
+                example: TelMesengger
+              owner:
+                properties:
+                  id:
+                    type: integer
+                    example: 1
+                  username:
+                    type: string
+                    example: abi
+                  name:
+                    type: string
+                    example: abidzar
+    successAddProject:
+      description: record successfully added
+      content:
+        application/json:
+          schema:
+              properties:
+                id: 
+                  type: integer
+                  example: 1
+                name:
+                  type: string
+                  example: TM
+                description:
+                  type: string
+                  example: TelMesengger
+                owner:
+                  properties:
+                    id:
+                      type: integer
+                      example: 1
+                    username:
+                      type: string
+                      example: abi
+                    name:
+                      type: string
+                      example: abidzar
+    successAddMember:
+      description: record successfully added
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                type: integer
+                example: 1
+              project_id:
+                properties:
+                  id:
+                    example: 1
+                  name:
+                    example: TM
+                  description:
+                    example: TelMessenger
+                  owner:
+                    properties:
+                      id:
+                        example: 1
+                      username:
+                        example: abi
+                      name:
+                        example: abidzar
+              user_id:
+                properties:
+                  id:
+                    example: 1
+                  username:
+                    example: abi
+                  name:
+                    example: abidzar
+              role:
+                type: integer
+                example: 0
+                description: QC      
+    getArrayPlatform:
       description: successful operation
       content:
         application/json:
           schema:
             type: array
-            items:
-              $ref: '#/components/schemas/Project'
-    201:
-      description: Record Successfully added
+            items: 
+              properties:
+                id:
+                  type: integer
+                  example: 1
+                name:
+                  type: string
+                  example: mobile
+                project_id:
+                  properties:
+                    id:
+                      type: integer
+                      example: 1
+                    name:
+                      type: string
+                      example: TM
+                    description:
+                      type: string
+                      example: TelMessenger
+                    owner:
+                      properties:
+                        id:
+                          type: integer
+                          example: 1
+                        username:
+                          type: string
+                          example: abi
+                        name:
+                          type: string
+                          example: abidzar            
+    successAddPlatform:
+      description: record successfully added
       content:
         application/json:
           schema:
-            $ref: '#/components/schemas/Project'
+            properties:
+              id:
+                type: integer
+                example: 1
+              name:
+                type: string
+                example: mobile
+              project_id:
+                properties:
+                  id:
+                    type: integer
+                    example: 1
+                  name:
+                    type: string
+                    example: TM
+                  description:
+                    type: string
+                    example: TelMessenger
+                  owner:
+                    properties:
+                      id:
+                        type: integer
+                        example: 1
+                      username:
+                        type: string
+                        example: abi
+                      name:
+                        type: string
+                        example: abidzar
+    getSinglePlatform:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                type: integer
+                example: 1
+              name:
+                type: string
+                example: mobile
+              project_id:
+                properties:
+                  id:
+                    type: integer
+                    example: 1
+                  name:
+                    type: string
+                    example: TM
+                  description:
+                    type: string
+                    example: TelMessenger
+                  owner:
+                    properties:
+                      id:
+                        type: integer
+                        example: 1
+                      username:
+                        type: string
+                        example: abi
+                      name:
+                        type: string
+                        example: abidzar
+    getArrayUser:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            type: array
+            items: 
+              properties:
+                id:
+                  type: integer
+                  example: 1
+                username:
+                  type: string
+                  example: abi
+                name:
+                  type: string
+                  example: abidzar
+    successAddUser:
+      description: record successfully added
+      content:
+        application/json:
+          schema:
+              properties:
+                id:
+                  type: integer
+                  example: 1
+                username:
+                  type: string
+                  example: abi
+                name:
+                  type: string
+                  example: abidzar
+    getSingleUser:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+              properties:
+                id:
+                  type: integer
+                  example: 1
+                username:
+                  type: string
+                  example: abi
+                name:
+                  type: string
+                  example: abidzar
+    getSingleMember:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            properties:
+              id:
+                example: 1
+              project_id:
+                properties:
+                  id:
+                    example: 1
+                  name:
+                    example: TM
+                  description:
+                    example: TelMessenger
+                  owner:
+                    properties:
+                      id:
+                        example: 1
+                      username:
+                        example: abi
+                      name:
+                        example: abidzar
+              user_id:
+                properties:
+                  id:
+                    example: 1
+                  username:
+                    example: abi
+                  name:
+                    example: abidzar
+              role:
+                example: 0
+    getArrayMember:
+      description: successful operation
+      content:
+        application/json:
+          schema:
+            type: array
+            items:
+              properties:
+                id:
+                  example: 1
+                project_id:
+                  properties:
+                    id:
+                      example: 1
+                    name:
+                      example: TM
+                    description:
+                      example: TelMessenger
+                    owner:
+                      properties:
+                        id:
+                          example: 1
+                        username:
+                          example: abi
+                        name:
+                          example: abidzar
+                user_id:
+                  properties:
+                    id:
+                      example: 1
+                    username:
+                      example: abi
+                    name:
+                      example: abidzar
+                role:
+                  example: 0
     400:
       description: invalid data
+    403:
+      description: do not have rights
     UnauthorizedError:
       description: Authentication information is missing or invalid
-      headers:
-        WWW_Authenticate:
-          schema:
-            type: string
     404:
       description: not found
     409:
@@ -617,6 +932,20 @@ components:
                 description:
                   type: string
                   example: TelMesengger
+    ProjectMember:
+      description: project member object
+      required: true
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              user_id:
+                type: integer
+                example: 1
+              role:
+                type: string
+                example: qc
     Platform:
       description: Platform object
       required: true
@@ -627,11 +956,29 @@ components:
             properties:
               name:
                 type: string
-                example: mobile
-              project_id:
-                $ref: '#/components/schemas/Project'
+                example: web
+              project_name:
+                type: string
+                example: TM
+    User:
+      description: User object
+      required: true
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              username:
+                type: string
+                example: abi
+              password:
+                type: string
+                example: abi123
+              name:
+                type: string
+                example: abidzar
   securitySchemes:
-    basicAuth:
+    testAuth:
       type: http
       scheme: basic
-      description: Use `user` / `password` as the test credentials
+      description: use `user`/`password` to login