diff --git a/src/main/scala/gitbucket/core/api/CreateAnIssue.scala b/src/main/scala/gitbucket/core/api/CreateAnIssue.scala new file mode 100644 index 0000000..cb54652 --- /dev/null +++ b/src/main/scala/gitbucket/core/api/CreateAnIssue.scala @@ -0,0 +1,11 @@ +package gitbucket.core.api + +/** + * https://developer.github.com/v3/issues/#create-an-issue + */ +case class CreateAnIssue( + title: String, + body: Option[String], + assignees: List[String], + milestone: Option[Int], + labels: List[String]) diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 19d5bbf..b48602f 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -21,10 +21,12 @@ with ProtectedBranchService with IssuesService with LabelsService + with MilestonesService with PullRequestService with CommitsService with CommitStatusService with RepositoryCreationService + with IssueCreationService with HandleCommentService with WebHookService with WebHookPullRequestService @@ -44,9 +46,11 @@ with ProtectedBranchService with IssuesService with LabelsService + with MilestonesService with PullRequestService with CommitStatusService with RepositoryCreationService + with IssueCreationService with HandleCommentService with OwnerAuthenticator with UsersAuthenticator @@ -297,6 +301,23 @@ }) /** + * https://developer.github.com/v3/issues/#create-an-issue + */ + post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository => + if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator? + (for{ + data <- extractFromJsonBody[CreateAnIssue] + loginAccount <- context.loginAccount + } yield { + val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _)) + val issue = createIssue(repository, data.title, data.body, data.assignees.headOption, + milestone.map(_.milestoneId), data.labels) + JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(loginAccount))) + }) getOrElse NotFound() + } else Unauthorized() + }) + + /** * https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue */ get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository => diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 4222dba..55d55b4 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -21,6 +21,7 @@ with MilestonesService with ActivityService with HandleCommentService + with IssueCreationService with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator @@ -36,6 +37,7 @@ with MilestonesService with ActivityService with HandleCommentService + with IssueCreationService with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator @@ -91,7 +93,7 @@ getAssignableUserNames(owner, name), getMilestonesWithIssueCount(owner, name), getLabels(owner, name), - isEditable(repository), + isIssueEditable(repository), isManageable(repository), repository) } getOrElse NotFound() @@ -99,7 +101,7 @@ }) get("/:owner/:repository/issues/new")(readableUsersOnly { repository => - if(isEditable(repository)){ // TODO Should this check is provided by authenticator? + if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator? defining(repository.owner, repository.name){ case (owner, name) => html.create( getAssignableUserNames(owner, name), @@ -112,46 +114,10 @@ }) post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => - if(isEditable(repository)){ // TODO Should this check is provided by authenticator? - defining(repository.owner, repository.name){ case (owner, name) => - val manageable = isManageable(repository) - val userName = context.loginAccount.get.userName - - // insert issue - val issueId = createIssue(owner, name, userName, form.title, form.content, - if (manageable) form.assignedUserName else None, - if (manageable) form.milestoneId else None) - - // insert labels - if (manageable) { - form.labelNames.map { value => - val labels = getLabels(owner, name) - value.split(",").foreach { labelName => - labels.find(_.labelName == labelName).map { label => - registerIssueLabel(owner, name, issueId, label.labelId) - } - } - } - } - - // record activity - recordCreateIssueActivity(owner, name, userName, issueId, form.title) - - getIssue(owner, name, issueId.toString).foreach { issue => - // extract references and create refer comment - createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) - - // call web hooks - callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get) - - // notifications - Notifier().toNotify(repository, issue, form.content.getOrElse("")) { - Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") - } - } - - redirect(s"/${owner}/${name}/issues/${issueId}") - } + if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator? + val issue = createIssue(repository, form.title, form.content, form.assignedUserName, + form.milestoneId, form.labelNames.toArray.flatMap(_.split(","))) + redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}") } else Unauthorized() }) @@ -398,35 +364,15 @@ countIssue(condition.copy(state = "closed"), false, owner -> repoName), condition, repository, - isEditable(repository), + isIssueEditable(repository), isManageable(repository)) } } /** - * Tests whether an logged-in user can manage issues. - */ - private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { - hasDeveloperRole(repository.owner, repository.name, context.loginAccount) - } - - /** - * Tests whether an logged-in user can post issues. - */ - private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { - repository.repository.options.issuesOption match { - case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined - case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount) - case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount) - case "DISABLE" => false - } - } - - /** * Tests whether an issue or a comment is editable by a logged-in user. */ private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = { hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName } - } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 671d9f1..e33fca1 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -424,7 +424,7 @@ if(editable) { val loginUserName = context.loginAccount.get.userName - val issueId = createIssue( + val issueId = insertIssue( owner = repository.owner, repository = repository.name, loginUser = loginUserName, diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala new file mode 100644 index 0000000..4e5b15f --- /dev/null +++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala @@ -0,0 +1,75 @@ +package gitbucket.core.service + +import gitbucket.core.controller.Context +import gitbucket.core.model.Issue +import gitbucket.core.model.Profile.profile.simple.Session +import gitbucket.core.service.RepositoryService.RepositoryInfo +import gitbucket.core.util.Notifier +import gitbucket.core.util.Implicits._ + +/** + * Created by sk on 1/5 005. + */ +trait IssueCreationService { + + self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService => + + def createIssue(repository: RepositoryInfo, title:String, body:Option[String], + assiginee: Option[String], milestoneId: Option[Int], labelNames:Seq[String])(implicit context: Context, s:Session) : Issue = { + val name = repository.name + val owner = repository.owner + val loginAccount = context.loginAccount.get + val userName = loginAccount.userName + val manageable = isManageable(repository) + + // insert issue + val issueId = insertIssue(owner, name, userName, title, body, + if (manageable) assiginee else None, + if (manageable) milestoneId else None) + val issue: Issue = getIssue(owner, name, issueId.toString).get + + // insert labels + if (manageable) { + val labels = getLabels(owner, name) + labelNames.map { labelName => + labels.find(_.labelName == labelName).map { label => + registerIssueLabel(owner, name, issueId, label.labelId) + } + } + } + + // record activity + recordCreateIssueActivity(owner, name, userName, issueId, title) + + // extract references and create refer comment + createReferComment(owner, name, issue, title + " " + body.getOrElse(""), context.loginAccount.get) + + // call web hooks + callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get) + + // notifications + Notifier().toNotify(repository, issue, body.getOrElse("")) { + Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") + } + issue + } + + /** + * Tests whether an logged-in user can manage issues. + */ + protected def isManageable(repository: RepositoryInfo)(implicit context: Context, s:Session): Boolean = { + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + } + + /** + * Tests whether an logged-in user can post issues. + */ + protected def isIssueEditable(repository: RepositoryInfo)(implicit context: Context, s:Session): Boolean = { + repository.repository.options.issuesOption match { + case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined + case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount) + case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + case "DISABLE" => false + } + } +} diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index 23c3b9d..591fb11 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -269,7 +269,7 @@ } exists), condition.mentioned.isDefined) } - def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String], + def insertIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String], assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false)(implicit s: Session) = // next id number diff --git a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala index 16fd952..673eae0 100644 --- a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala +++ b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala @@ -53,7 +53,7 @@ } def generateNewIssue(userName:String, repositoryName:String, loginUser:String="root")(implicit s:Session): Int = { - dummyService.createIssue( + dummyService.insertIssue( owner = userName, repository = repositoryName, loginUser = loginUser,