diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index f36a57f..d1235ae 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -12,6 +12,7 @@ context.mount(new RepositoryViewerController, "/*") context.mount(new WikiController, "/*") context.mount(new IssuesController, "/*") + context.mount(new MilestonesController, "/*") context.mount(new SettingsController, "/*") context.addListener(new ServletContextListener(){ diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index 7df7c5b..8daa38e 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -73,101 +73,4 @@ redirect("/%s/%s/issues/%d".format(owner, repository, 1)) }) - 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), - getMilestoneIssueCounts(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) => { - closeMilestone(m) - 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) => { - openMilestone(m) - redirect("/%s/%s/issues/milestones".format(owner, repository)) - } - } - }) - - get("/:owner/:repository/issues/milestones/:milestoneId/delete")(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) => { - deleteMilestone(owner, repository, milestoneId) - redirect("/%s/%s/issues/milestones".format(owner, repository)) - } - } - }) } \ No newline at end of file diff --git a/src/main/scala/app/MilestonesController.scala b/src/main/scala/app/MilestonesController.scala new file mode 100644 index 0000000..d1a6837 --- /dev/null +++ b/src/main/scala/app/MilestonesController.scala @@ -0,0 +1,122 @@ +package app + +import jp.sf.amateras.scalatra.forms._ + +import service._ +import util.{WritableRepositoryAuthenticator, ReadableRepositoryAuthenticator, UsersOnlyAuthenticator} + +class MilestonesController extends MilestonesControllerBase + with MilestonesService with RepositoryService with AccountService + with ReadableRepositoryAuthenticator with WritableRepositoryAuthenticator + +trait MilestonesControllerBase extends ControllerBase { + self: MilestonesService with RepositoryService + with ReadableRepositoryAuthenticator with WritableRepositoryAuthenticator => + + case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date]) + + val milestoneForm = mapping( + "title" -> trim(label("Title", text(required, maxlength(100)))), + "description" -> trim(label("Description", optional(text()))), + "dueDate" -> trim(label("Due Date", optional(date()))) + )(MilestoneForm.apply) + + 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), + getMilestoneIssueCounts(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) => { + closeMilestone(m) + 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) => { + openMilestone(m) + redirect("/%s/%s/issues/milestones".format(owner, repository)) + } + } + }) + + get("/:owner/:repository/issues/milestones/:milestoneId/delete")(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) => { + deleteMilestone(owner, repository, milestoneId) + redirect("/%s/%s/issues/milestones".format(owner, repository)) + } + } + }) + +} diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala index 4c08981..7068987 100644 --- a/src/main/scala/service/IssuesService.scala +++ b/src/main/scala/service/IssuesService.scala @@ -51,62 +51,4 @@ }.map(_.issueId).update(id) > 0 } get - def createMilestone(owner: String, repository: String, - title: String, description: Option[String], dueDate: Option[java.util.Date]) = - Milestones.autoInc insert (owner, repository, title, description, dueDate, None) - - 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.closedDate.? } - .update ( - milestone.title, - milestone.description, - milestone.dueDate, - milestone.closedDate) - - def openMilestone(milestone: Milestone): Unit = updateMilestone(milestone.copy(closedDate = None)) - - def closeMilestone(milestone: Milestone): Unit = updateMilestone(milestone.copy(closedDate = Some(currentDate))) - - def deleteMilestone(owner: String, repository: String, milestoneId: Int): Unit = { - Query(Issues) - .filter { i => (i.userName is owner.bind) && (i.repositoryName is repository.bind) && (i.milestoneId is milestoneId.bind)} - .map { i => i.milestoneId.? } - .update(None) - - Query(Milestones) - .filter { i => (i.userName is owner.bind) && (i.repositoryName is repository.bind) && (i.milestoneId is milestoneId.bind)} - .delete - } - - 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 getMilestoneIssueCounts(owner: String, repository: String): Map[(Int, Boolean), Int] = { - import scala.slick.jdbc.GetResult - - case class IssueCount(milestoneId: Int, closed: Boolean, count: Int) - implicit val getIssueCount = GetResult(r => IssueCount(r.<<, r.<<, r.<<)) - - sql""" - select MILESTONE_ID, CLOSED, COUNT(ISSUE_ID) - from ISSUE - where USER_NAME = $owner AND REPOSITORY_NAME = $repository AND MILESTONE_ID IS NOT NULL - group by USER_NAME, REPOSITORY_NAME, MILESTONE_ID, CLOSED""" - .as[IssueCount] - .list - .map { x => (x.milestoneId, x.closed) -> x.count } - .toMap - } - - 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/scala/service/MilestonesService.scala b/src/main/scala/service/MilestonesService.scala new file mode 100644 index 0000000..7fd356a --- /dev/null +++ b/src/main/scala/service/MilestonesService.scala @@ -0,0 +1,70 @@ +package service + +import scala.slick.driver.H2Driver.simple._ +import Database.threadLocalSession +import scala.slick.jdbc.{StaticQuery => Q} +import Q.interpolation + +import model._ +import Milestones._ + +trait MilestonesService { + + def createMilestone(owner: String, repository: String, + title: String, description: Option[String], dueDate: Option[java.util.Date]) = + Milestones.autoInc insert (owner, repository, title, description, dueDate, None) + + 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.closedDate.? } + .update ( + milestone.title, + milestone.description, + milestone.dueDate, + milestone.closedDate) + + def openMilestone(milestone: Milestone): Unit = updateMilestone(milestone.copy(closedDate = None)) + + def closeMilestone(milestone: Milestone): Unit = updateMilestone(milestone.copy(closedDate = Some(currentDate))) + + def deleteMilestone(owner: String, repository: String, milestoneId: Int): Unit = { + Query(Issues) + .filter { i => (i.userName is owner.bind) && (i.repositoryName is repository.bind) && (i.milestoneId is milestoneId.bind)} + .map { i => i.milestoneId.? } + .update(None) + + Query(Milestones) + .filter { i => (i.userName is owner.bind) && (i.repositoryName is repository.bind) && (i.milestoneId is milestoneId.bind)} + .delete + } + + 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 getMilestoneIssueCounts(owner: String, repository: String): Map[(Int, Boolean), Int] = { + import scala.slick.jdbc.GetResult + + case class IssueCount(milestoneId: Int, closed: Boolean, count: Int) + implicit val getIssueCount = GetResult(r => IssueCount(r.<<, r.<<, r.<<)) + + sql""" + select MILESTONE_ID, CLOSED, COUNT(ISSUE_ID) + from ISSUE + where USER_NAME = $owner AND REPOSITORY_NAME = $repository AND MILESTONE_ID IS NOT NULL + group by USER_NAME, REPOSITORY_NAME, MILESTONE_ID, CLOSED""" + .as[IssueCount] + .list + .map { x => (x.milestoneId, x.closed) -> x.count } + .toMap + } + + 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 +}