diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index 0167751..ce09869 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -3,21 +3,31 @@ import jp.sf.amateras.scalatra.forms._ import service._ -import util.UsersOnlyAuthenticator +import util.{WritableRepositoryAuthenticator, ReadableRepositoryAuthenticator, UsersOnlyAuthenticator} class IssuesController extends IssuesControllerBase - with IssuesService with RepositoryService with AccountService with UsersOnlyAuthenticator + with IssuesService with RepositoryService with AccountService + with UsersOnlyAuthenticator with ReadableRepositoryAuthenticator with WritableRepositoryAuthenticator trait IssuesControllerBase extends ControllerBase { - self: IssuesService with RepositoryService with UsersOnlyAuthenticator => + self: IssuesService with RepositoryService + with UsersOnlyAuthenticator with ReadableRepositoryAuthenticator with WritableRepositoryAuthenticator => case class IssueForm(title: String, content: Option[String]) + case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.sql.Date]) + val form = mapping( "title" -> trim(label("Title", text(required))), "content" -> trim(optional(text())) )(IssueForm.apply) + val milestoneForm = mapping( + "title" -> trim(label("Title", text(required))), + "description" -> trim(label("Description", optional(text()))), + "dueDate" -> trim(label("Due Date", optional(date()))) + )(MilestoneForm.apply) + get("/:owner/:repository/issues"){ val owner = params("owner") val repository = params("repository") @@ -53,4 +63,86 @@ saveIssue(owner, repository, context.loginAccount.get.userName, form.title, form.content))) }) + get("/:owner/:repository/issues/milestones")(readableRepository { + val owner = params("owner") + val repository = params("repository") + val state = params.getOrElse("state", "open") + + getRepository(owner, repository, baseUrl) match { + case None => NotFound() + case Some(r) => issues.html.milestones(state, getMilestones(owner, repository), r, isWritable(owner, repository, context.loginAccount)) + } + }) + + get("/:owner/:repository/issues/milestones/new")(writableRepository { + val owner = params("owner") + val repository = params("repository") + + getRepository(owner, repository, baseUrl) match { + case None => NotFound() + case Some(r) => issues.html.milestoneedit(None, r) + } + }) + + post("/:owner/:repository/issues/milestones/new", milestoneForm)(writableRepository { form => + val owner = params("owner") + val repository = params("repository") + + createMilestone(owner, repository, form.title, form.description, form.dueDate) + + redirect("/%s/%s/issues/milestones".format(owner, repository)) + }) + + get("/:owner/:repository/issues/milestones/:milestoneId/edit")(writableRepository { + val owner = params("owner") + val repository = params("repository") + val milestoneId = params("milestoneId").toInt + + getRepository(owner, repository, baseUrl) match { + case None => NotFound() + case Some(r) => issues.html.milestoneedit(getMilestone(owner, repository, milestoneId), r) + } + }) + + post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableRepository { form => + val owner = params("owner") + val repository = params("repository") + val milestoneId = params("milestoneId").toInt + + getMilestone(owner, repository, milestoneId) match { + case None => NotFound() + case Some(m) => { + updateMilestone(m.copy(title = form.title, description = form.description, dueDate = form.dueDate)) + redirect("/%s/%s/issues/milestones".format(owner, repository)) + } + } + }) + + get("/:owner/:repository/issues/milestones/:milestoneId/close")(writableRepository { + val owner = params("owner") + val repository = params("repository") + val milestoneId = params("milestoneId").toInt + + getMilestone(owner, repository, milestoneId) match { + case None => NotFound() + case Some(m) => { + updateMilestone(m.copy(closed = true)) + redirect("/%s/%s/issues/milestones".format(owner, repository)) + } + } + }) + + get("/:owner/:repository/issues/milestones/:milestoneId/open")(writableRepository { + val owner = params("owner") + val repository = params("repository") + val milestoneId = params("milestoneId").toInt + + getMilestone(owner, repository, milestoneId) match { + case None => NotFound() + case Some(m) => { + updateMilestone(m.copy(closed = false)) + redirect("/%s/%s/issues/milestones".format(owner, repository)) + } + } + }) } \ No newline at end of file diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala index d23481c..3e06cd9 100644 --- a/src/main/scala/service/IssuesService.scala +++ b/src/main/scala/service/IssuesService.scala @@ -50,4 +50,31 @@ }.map(_.issueId).update(id) > 0 } get + def createMilestone(owner: String, repository: String, + title: String, description: Option[String], dueDate: Option[java.sql.Date]): Unit = { + Milestones.ins insert (owner, repository, title, description, dueDate, false) + } + + def updateMilestone(milestone: Milestone): Unit = + Query(Milestones) + .filter { m => (m.userName is milestone.userName.bind) && (m.repositoryName is milestone.repositoryName.bind) && (m.milestoneId is milestone.milestoneId.bind)} + .map { m => m.title ~ m.description.? ~ m.dueDate.? ~ m.closed } + .update ( + milestone.title, + milestone.description, + milestone.dueDate, + milestone.closed) + + def getMilestone(owner: String, repository: String, milestoneId: Int): Option[Milestone] = + Query(Milestones) + .filter(m => (m.userName is owner.bind) && (m.repositoryName is repository.bind) && (m.milestoneId is milestoneId.bind)) + .sortBy(_.milestoneId desc) + .firstOption + + def getMilestones(owner: String, repository: String): List[Milestone] = + Query(Milestones) + .filter(m => (m.userName is owner.bind) && (m.repositoryName is repository.bind)) + .sortBy(_.milestoneId desc) + .list + } \ No newline at end of file diff --git a/src/main/twirl/issues/milestoneedit.scala.html b/src/main/twirl/issues/milestoneedit.scala.html new file mode 100644 index 0000000..19f86b7 --- /dev/null +++ b/src/main/twirl/issues/milestoneedit.scala.html @@ -0,0 +1,47 @@ +@(milestone: Option[model.Milestone], repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) +@import context._ +@import view.helpers +@html.main("Milestones - " + repository.owner + "/" + repository.name){ + @html.header("milestones", repository) + @issuestab("milestones", repository) +
+} + \ No newline at end of file diff --git a/src/main/twirl/issues/milestones.scala.html b/src/main/twirl/issues/milestones.scala.html new file mode 100644 index 0000000..581258f --- /dev/null +++ b/src/main/twirl/issues/milestones.scala.html @@ -0,0 +1,120 @@ +@(state: String, milestones: List[model.Milestone], repository: service.RepositoryService.RepositoryInfo, isWritable: Boolean)(implicit context: app.Context) +@import context._ +@import view.helpers +@html.main("Milestones - " + repository.owner + "/" + repository.name){ + @html.header("milestones", repository) + @issuestab("milestones", repository) +
+
+
+ @if(milestone.description.isDefined){
+
+ @milestone.title
+ + @if(milestone.dueDate.isDefined){ + @helpers.date(milestone.dueDate.get) + } else { + No due date + } +
+
+
+ 10%
+
+ @helpers.markdown(milestone.description.get, repository, false, false, false)
+
+ }
+ |
+
+ No milestones to show. + @if(isWritable){ + Create a new milestone. + } + | +