diff --git a/src/main/resources/update/1_0.sql b/src/main/resources/update/1_0.sql
index 418c3e3..5067ada 100644
--- a/src/main/resources/update/1_0.sql
+++ b/src/main/resources/update/1_0.sql
@@ -1,135 +1,135 @@
-CREATE TABLE ACCOUNT(
- USER_NAME VARCHAR(100) NOT NULL,
- MAIL_ADDRESS VARCHAR(100) NOT NULL,
- PASSWORD VARCHAR(40) NOT NULL,
- ADMINISTRATOR BOOLEAN NOT NULL,
- URL VARCHAR(200),
- REGISTERED_DATE TIMESTAMP NOT NULL,
- UPDATED_DATE TIMESTAMP NOT NULL,
- LAST_LOGIN_DATE TIMESTAMP
-);
-
-CREATE TABLE REPOSITORY(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- PRIVATE BOOLEAN NOT NULL,
- DESCRIPTION TEXT,
- DEFAULT_BRANCH VARCHAR(100),
- REGISTERED_DATE TIMESTAMP NOT NULL,
- UPDATED_DATE TIMESTAMP NOT NULL,
- LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
-);
-
-CREATE TABLE COLLABORATOR(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- COLLABORATOR_NAME VARCHAR(100) NOT NULL
-);
-
-CREATE TABLE ISSUE(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- ISSUE_ID INT NOT NULL,
- OPENED_USER_NAME VARCHAR(100) NOT NULL,
- MILESTONE_ID INT,
- ASSIGNED_USER_NAME VARCHAR(100),
- TITLE TEXT NOT NULL,
- CONTENT TEXT,
- CLOSED BOOLEAN NOT NULL,
- REGISTERED_DATE TIMESTAMP NOT NULL,
- UPDATED_DATE TIMESTAMP NOT NULL
-);
-
-CREATE TABLE ISSUE_ID(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- ISSUE_ID INT NOT NULL
-);
-
-CREATE TABLE ISSUE_COMMENT(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- ISSUE_ID INT NOT NULL,
- COMMENT_ID INT AUTO_INCREMENT,
- ACTION VARCHAR(10),
- COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
- CONTENT TEXT NOT NULL,
- REGISTERED_DATE TIMESTAMP NOT NULL,
- UPDATED_DATE TIMESTAMP NOT NULL
-);
-
-CREATE TABLE LABEL(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- LABEL_ID INT AUTO_INCREMENT,
- LABEL_NAME VARCHAR(100) NOT NULL,
- COLOR CHAR(6) NOT NULL
-);
-
-CREATE TABLE ISSUE_LABEL(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- ISSUE_ID INT NOT NULL,
- LABEL_ID INT NOT NULL
-);
-
-CREATE TABLE MILESTONE(
- USER_NAME VARCHAR(100) NOT NULL,
- REPOSITORY_NAME VARCHAR(100) NOT NULL,
- MILESTONE_ID INT AUTO_INCREMENT,
- TITLE VARCHAR(100) NOT NULL,
- DESCRIPTION TEXT,
- DUE_DATE TIMESTAMP,
- CLOSED_DATE TIMESTAMP
-);
-
-ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
-ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
-
-ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
-ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
-
-ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
-ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
-ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
-
-ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
-ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
-ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
-ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
-
-ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
-ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
-
-ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
-ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
-ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
-
-ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
-ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
-
-ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
-ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
-
-ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
-ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
-
-INSERT INTO ACCOUNT (
- USER_NAME,
- MAIL_ADDRESS,
- PASSWORD,
- ADMINISTRATOR,
- URL,
- REGISTERED_DATE,
- UPDATED_DATE,
- LAST_LOGIN_DATE
-) VALUES (
- 'root',
- 'root@localhost',
- 'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
- true,
- 'https://github.com/takezoe/gitbucket',
- SYSDATE,
- SYSDATE,
- NULL
-);
+CREATE TABLE ACCOUNT(
+ USER_NAME VARCHAR(100) NOT NULL,
+ MAIL_ADDRESS VARCHAR(100) NOT NULL,
+ PASSWORD VARCHAR(40) NOT NULL,
+ ADMINISTRATOR BOOLEAN NOT NULL,
+ URL VARCHAR(200),
+ REGISTERED_DATE TIMESTAMP NOT NULL,
+ UPDATED_DATE TIMESTAMP NOT NULL,
+ LAST_LOGIN_DATE TIMESTAMP
+);
+
+CREATE TABLE REPOSITORY(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ PRIVATE BOOLEAN NOT NULL,
+ DESCRIPTION TEXT,
+ DEFAULT_BRANCH VARCHAR(100),
+ REGISTERED_DATE TIMESTAMP NOT NULL,
+ UPDATED_DATE TIMESTAMP NOT NULL,
+ LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
+);
+
+CREATE TABLE COLLABORATOR(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ COLLABORATOR_NAME VARCHAR(100) NOT NULL
+);
+
+CREATE TABLE ISSUE(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ ISSUE_ID INT NOT NULL,
+ OPENED_USER_NAME VARCHAR(100) NOT NULL,
+ MILESTONE_ID INT,
+ ASSIGNED_USER_NAME VARCHAR(100),
+ TITLE TEXT NOT NULL,
+ CONTENT TEXT,
+ CLOSED BOOLEAN NOT NULL,
+ REGISTERED_DATE TIMESTAMP NOT NULL,
+ UPDATED_DATE TIMESTAMP NOT NULL
+);
+
+CREATE TABLE ISSUE_ID(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ ISSUE_ID INT NOT NULL
+);
+
+CREATE TABLE ISSUE_COMMENT(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ ISSUE_ID INT NOT NULL,
+ COMMENT_ID INT AUTO_INCREMENT,
+ ACTION VARCHAR(10),
+ COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
+ CONTENT TEXT NOT NULL,
+ REGISTERED_DATE TIMESTAMP NOT NULL,
+ UPDATED_DATE TIMESTAMP NOT NULL
+);
+
+CREATE TABLE LABEL(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ LABEL_ID INT AUTO_INCREMENT,
+ LABEL_NAME VARCHAR(100) NOT NULL,
+ COLOR CHAR(6) NOT NULL
+);
+
+CREATE TABLE ISSUE_LABEL(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ ISSUE_ID INT NOT NULL,
+ LABEL_ID INT NOT NULL
+);
+
+CREATE TABLE MILESTONE(
+ USER_NAME VARCHAR(100) NOT NULL,
+ REPOSITORY_NAME VARCHAR(100) NOT NULL,
+ MILESTONE_ID INT AUTO_INCREMENT,
+ TITLE VARCHAR(100) NOT NULL,
+ DESCRIPTION TEXT,
+ DUE_DATE TIMESTAMP,
+ CLOSED_DATE TIMESTAMP
+);
+
+ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
+ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
+
+ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
+ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
+
+ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
+ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
+ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
+
+ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
+ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
+ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
+ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
+
+ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
+ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
+
+ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
+ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
+ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
+
+ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
+ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
+
+ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
+ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
+
+ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
+ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
+
+INSERT INTO ACCOUNT (
+ USER_NAME,
+ MAIL_ADDRESS,
+ PASSWORD,
+ ADMINISTRATOR,
+ URL,
+ REGISTERED_DATE,
+ UPDATED_DATE,
+ LAST_LOGIN_DATE
+) VALUES (
+ 'root',
+ 'root@localhost',
+ 'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
+ true,
+ 'https://github.com/takezoe/gitbucket',
+ SYSDATE,
+ SYSDATE,
+ NULL
+);
diff --git a/src/main/scala/app/DashboardController.scala b/src/main/scala/app/DashboardController.scala
index bf5e11e..bd788c9 100644
--- a/src/main/scala/app/DashboardController.scala
+++ b/src/main/scala/app/DashboardController.scala
@@ -1,109 +1,109 @@
-package app
-
-import service._
-import util.{UsersAuthenticator, Keys}
-import util.Implicits._
-
-class DashboardController extends DashboardControllerBase
- with IssuesService with PullRequestService with RepositoryService with AccountService
- with UsersAuthenticator
-
-trait DashboardControllerBase extends ControllerBase {
- self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator =>
-
- get("/dashboard/issues/repos")(usersOnly {
- searchIssues("all")
- })
-
- get("/dashboard/issues/assigned")(usersOnly {
- searchIssues("assigned")
- })
-
- get("/dashboard/issues/created_by")(usersOnly {
- searchIssues("created_by")
- })
-
- get("/dashboard/pulls")(usersOnly {
- searchPullRequests("created_by", None)
- })
-
- get("/dashboard/pulls/owned")(usersOnly {
- searchPullRequests("created_by", None)
- })
-
- get("/dashboard/pulls/public")(usersOnly {
- searchPullRequests("not_created_by", None)
- })
-
- get("/dashboard/pulls/for/:owner/:repository")(usersOnly {
- searchPullRequests("all", Some(params("owner") + "/" + params("repository")))
- })
-
- private def searchIssues(filter: String) = {
- import IssuesService._
-
- // condition
- val condition = session.putAndGet(Keys.Session.DashboardIssues,
- if(request.hasQueryString) IssueSearchCondition(request)
- else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
- )
-
- val userName = context.loginAccount.get.userName
- val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name)
- val filterUser = Map(filter -> userName)
- val page = IssueSearchCondition.page(request)
- //
- dashboard.html.issues(
- issues.html.listparts(
- searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*),
- page,
- countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
- countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
- condition),
- countIssue(condition, Map.empty, false, repositories: _*),
- countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
- countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
- countIssueGroupByRepository(condition, filterUser, false, repositories: _*),
- condition,
- filter)
-
- }
-
- private def searchPullRequests(filter: String, repository: Option[String]) = {
- import IssuesService._
- import PullRequestService._
-
- // condition
- val condition = session.putAndGet(Keys.Session.DashboardPulls, {
- if(request.hasQueryString) IssueSearchCondition(request)
- else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
- }.copy(repo = repository))
-
- val userName = context.loginAccount.get.userName
- val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name)
- val filterUser = Map(filter -> userName)
- val page = IssueSearchCondition.page(request)
-
- val counts = countIssueGroupByRepository(
- IssueSearchCondition().copy(state = condition.state), Map.empty, true, repositories: _*)
-
- dashboard.html.pulls(
- pulls.html.listparts(
- searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, repositories: _*),
- page,
- countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*),
- countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*),
- condition,
- None,
- false),
- getPullRequestCountGroupByUser(condition.state == "closed", None, None),
- getRepositoryNamesOfUser(userName).map { RepoName =>
- (userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0))
- }.sortBy(_._3).reverse,
- condition,
- filter)
-
- }
-
-
-}
+package app
+
+import service._
+import util.{UsersAuthenticator, Keys}
+import util.Implicits._
+
+class DashboardController extends DashboardControllerBase
+ with IssuesService with PullRequestService with RepositoryService with AccountService
+ with UsersAuthenticator
+
+trait DashboardControllerBase extends ControllerBase {
+ self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator =>
+
+ get("/dashboard/issues/repos")(usersOnly {
+ searchIssues("all")
+ })
+
+ get("/dashboard/issues/assigned")(usersOnly {
+ searchIssues("assigned")
+ })
+
+ get("/dashboard/issues/created_by")(usersOnly {
+ searchIssues("created_by")
+ })
+
+ get("/dashboard/pulls")(usersOnly {
+ searchPullRequests("created_by", None)
+ })
+
+ get("/dashboard/pulls/owned")(usersOnly {
+ searchPullRequests("created_by", None)
+ })
+
+ get("/dashboard/pulls/public")(usersOnly {
+ searchPullRequests("not_created_by", None)
+ })
+
+ get("/dashboard/pulls/for/:owner/:repository")(usersOnly {
+ searchPullRequests("all", Some(params("owner") + "/" + params("repository")))
+ })
+
+ private def searchIssues(filter: String) = {
+ import IssuesService._
+
+ // condition
+ val condition = session.putAndGet(Keys.Session.DashboardIssues,
+ if(request.hasQueryString) IssueSearchCondition(request)
+ else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
+ )
+
+ val userName = context.loginAccount.get.userName
+ val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name)
+ val filterUser = Map(filter -> userName)
+ val page = IssueSearchCondition.page(request)
+ //
+ dashboard.html.issues(
+ issues.html.listparts(
+ searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*),
+ page,
+ countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
+ countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
+ condition),
+ countIssue(condition, Map.empty, false, repositories: _*),
+ countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
+ countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
+ countIssueGroupByRepository(condition, filterUser, false, repositories: _*),
+ condition,
+ filter)
+
+ }
+
+ private def searchPullRequests(filter: String, repository: Option[String]) = {
+ import IssuesService._
+ import PullRequestService._
+
+ // condition
+ val condition = session.putAndGet(Keys.Session.DashboardPulls, {
+ if(request.hasQueryString) IssueSearchCondition(request)
+ else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
+ }.copy(repo = repository))
+
+ val userName = context.loginAccount.get.userName
+ val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name)
+ val filterUser = Map(filter -> userName)
+ val page = IssueSearchCondition.page(request)
+
+ val counts = countIssueGroupByRepository(
+ IssueSearchCondition().copy(state = condition.state), Map.empty, true, repositories: _*)
+
+ dashboard.html.pulls(
+ pulls.html.listparts(
+ searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, repositories: _*),
+ page,
+ countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*),
+ countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*),
+ condition,
+ None,
+ false),
+ getPullRequestCountGroupByUser(condition.state == "closed", None, None),
+ getRepositoryNamesOfUser(userName).map { RepoName =>
+ (userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0))
+ }.sortBy(_._3).reverse,
+ condition,
+ filter)
+
+ }
+
+
+}
diff --git a/src/main/scala/app/IndexController.scala b/src/main/scala/app/IndexController.scala
index 8866795..f5eb2d8 100644
--- a/src/main/scala/app/IndexController.scala
+++ b/src/main/scala/app/IndexController.scala
@@ -1,85 +1,85 @@
-package app
-
-import util._
-import service._
-import jp.sf.amateras.scalatra.forms._
-
-class IndexController extends IndexControllerBase
- with RepositoryService with ActivityService with AccountService with UsersAuthenticator
-
-trait IndexControllerBase extends ControllerBase {
- self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
-
- case class SignInForm(userName: String, password: String)
-
- val form = mapping(
- "userName" -> trim(label("Username", text(required))),
- "password" -> trim(label("Password", text(required)))
- )(SignInForm.apply)
-
- get("/"){
- val loginAccount = context.loginAccount
-
- html.index(getRecentActivities(),
- getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
- loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
- )
- }
-
- get("/signin"){
- val redirect = params.get("redirect")
- if(redirect.isDefined && redirect.get.startsWith("/")){
- flash += Keys.Flash.Redirect -> redirect.get
- }
- html.signin()
- }
-
- post("/signin", form){ form =>
- authenticate(context.settings, form.userName, form.password) match {
- case Some(account) => signin(account)
- case None => redirect("/signin")
- }
- }
-
- get("/signout"){
- session.invalidate
- redirect("/")
- }
-
- get("/activities.atom"){
- contentType = "application/atom+xml; type=feed"
- helper.xml.feed(getRecentActivities())
- }
-
- /**
- * Set account information into HttpSession and redirect.
- */
- private def signin(account: model.Account) = {
- session.setAttribute(Keys.Session.LoginAccount, account)
- updateLastLoginDate(account.userName)
-
- flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
- if(redirectUrl.stripSuffix("/") == request.getContextPath){
- redirect("/")
- } else {
- redirect(redirectUrl)
- }
- }.getOrElse {
- redirect("/")
- }
- }
-
- /**
- * JSON API for collaborator completion.
- *
- * TODO Move to other controller?
- */
- get("/_user/proposals")(usersOnly {
- contentType = formats("json")
- org.json4s.jackson.Serialization.write(
- Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
- )
- })
-
-
-}
+package app
+
+import util._
+import service._
+import jp.sf.amateras.scalatra.forms._
+
+class IndexController extends IndexControllerBase
+ with RepositoryService with ActivityService with AccountService with UsersAuthenticator
+
+trait IndexControllerBase extends ControllerBase {
+ self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
+
+ case class SignInForm(userName: String, password: String)
+
+ val form = mapping(
+ "userName" -> trim(label("Username", text(required))),
+ "password" -> trim(label("Password", text(required)))
+ )(SignInForm.apply)
+
+ get("/"){
+ val loginAccount = context.loginAccount
+
+ html.index(getRecentActivities(),
+ getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
+ loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
+ )
+ }
+
+ get("/signin"){
+ val redirect = params.get("redirect")
+ if(redirect.isDefined && redirect.get.startsWith("/")){
+ flash += Keys.Flash.Redirect -> redirect.get
+ }
+ html.signin()
+ }
+
+ post("/signin", form){ form =>
+ authenticate(context.settings, form.userName, form.password) match {
+ case Some(account) => signin(account)
+ case None => redirect("/signin")
+ }
+ }
+
+ get("/signout"){
+ session.invalidate
+ redirect("/")
+ }
+
+ get("/activities.atom"){
+ contentType = "application/atom+xml; type=feed"
+ helper.xml.feed(getRecentActivities())
+ }
+
+ /**
+ * Set account information into HttpSession and redirect.
+ */
+ private def signin(account: model.Account) = {
+ session.setAttribute(Keys.Session.LoginAccount, account)
+ updateLastLoginDate(account.userName)
+
+ flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
+ if(redirectUrl.stripSuffix("/") == request.getContextPath){
+ redirect("/")
+ } else {
+ redirect(redirectUrl)
+ }
+ }.getOrElse {
+ redirect("/")
+ }
+ }
+
+ /**
+ * JSON API for collaborator completion.
+ *
+ * TODO Move to other controller?
+ */
+ get("/_user/proposals")(usersOnly {
+ contentType = formats("json")
+ org.json4s.jackson.Serialization.write(
+ Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
+ )
+ })
+
+
+}
diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala
index 661c1e0..4f28466 100644
--- a/src/main/scala/app/IssuesController.scala
+++ b/src/main/scala/app/IssuesController.scala
@@ -1,403 +1,403 @@
-package app
-
-import jp.sf.amateras.scalatra.forms._
-
-import service._
-import IssuesService._
-import util._
-import util.Implicits._
-import util.ControlUtil._
-import org.scalatra.Ok
-import model.Issue
-
-class IssuesController extends IssuesControllerBase
- with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
-
-trait IssuesControllerBase extends ControllerBase {
- self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
-
- case class IssueCreateForm(title: String, content: Option[String],
- assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
- case class IssueEditForm(title: String, content: Option[String])
- case class CommentForm(issueId: Int, content: String)
- case class IssueStateForm(issueId: Int, content: Option[String])
-
- val issueCreateForm = mapping(
- "title" -> trim(label("Title", text(required))),
- "content" -> trim(optional(text())),
- "assignedUserName" -> trim(optional(text())),
- "milestoneId" -> trim(optional(number())),
- "labelNames" -> trim(optional(text()))
- )(IssueCreateForm.apply)
-
- val issueEditForm = mapping(
- "title" -> trim(label("Title", text(required))),
- "content" -> trim(optional(text()))
- )(IssueEditForm.apply)
-
- val commentForm = mapping(
- "issueId" -> label("Issue Id", number()),
- "content" -> trim(label("Comment", text(required)))
- )(CommentForm.apply)
-
- val issueStateForm = mapping(
- "issueId" -> label("Issue Id", number()),
- "content" -> trim(optional(text()))
- )(IssueStateForm.apply)
-
- get("/:owner/:repository/issues")(referrersOnly {
- searchIssues("all", _)
- })
-
- get("/:owner/:repository/issues/assigned/:userName")(referrersOnly {
- searchIssues("assigned", _)
- })
-
- get("/:owner/:repository/issues/created_by/:userName")(referrersOnly {
- searchIssues("created_by", _)
- })
-
- get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
- defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
- getIssue(owner, name, issueId) map {
- issues.html.issue(
- _,
- getComments(owner, name, issueId.toInt),
- getIssueLabels(owner, name, issueId.toInt),
- (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
- getMilestonesWithIssueCount(owner, name),
- getLabels(owner, name),
- hasWritePermission(owner, name, context.loginAccount),
- repository)
- } getOrElse NotFound
- }
- })
-
- get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- issues.html.create(
- (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
- getMilestones(owner, name),
- getLabels(owner, name),
- hasWritePermission(owner, name, context.loginAccount),
- repository)
- }
- })
-
- post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- val writable = hasWritePermission(owner, name, context.loginAccount)
- val userName = context.loginAccount.get.userName
-
- // insert issue
- val issueId = createIssue(owner, name, userName, form.title, form.content,
- if(writable) form.assignedUserName else None,
- if(writable) form.milestoneId else None)
-
- // insert labels
- if(writable){
- 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)
-
- // extract references and create refer comment
- getIssue(owner, name, issueId.toString).foreach { issue =>
- createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
- }
-
- // notifications
- Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
- Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
- }
-
- redirect(s"/${owner}/${name}/issues/${issueId}")
- }
- })
-
- ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- getIssue(owner, name, params("id")).map { issue =>
- if(isEditable(owner, name, issue.openedUserName)){
- // update issue
- updateIssue(owner, name, issue.issueId, form.title, form.content)
- // extract references and create refer comment
- createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
-
- redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
- } else Unauthorized
- } getOrElse NotFound
- }
- })
-
- post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
- handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
- redirect(s"/${repository.owner}/${repository.name}/${
- if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
- } getOrElse NotFound
- })
-
- post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
- handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
- redirect(s"/${repository.owner}/${repository.name}/${
- if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
- } getOrElse NotFound
- })
-
- ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- getComment(owner, name, params("id")).map { comment =>
- if(isEditable(owner, name, comment.commentedUserName)){
- updateComment(comment.commentId, form.content)
- redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
- } else Unauthorized
- } getOrElse NotFound
- }
- })
-
- ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- getComment(owner, name, params("id")).map { comment =>
- if(isEditable(owner, name, comment.commentedUserName)){
- Ok(deleteComment(comment.commentId))
- } else Unauthorized
- } getOrElse NotFound
- }
- })
-
- ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
- getIssue(repository.owner, repository.name, params("id")) map { x =>
- if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
- params.get("dataType") collect {
- case t if t == "html" => issues.html.editissue(
- x.title, x.content, x.issueId, x.userName, x.repositoryName)
- } getOrElse {
- contentType = formats("json")
- org.json4s.jackson.Serialization.write(
- Map("title" -> x.title,
- "content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
- repository, false, true)
- ))
- }
- } else Unauthorized
- } getOrElse NotFound
- })
-
- ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
- getComment(repository.owner, repository.name, params("id")) map { x =>
- if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
- params.get("dataType") collect {
- case t if t == "html" => issues.html.editcomment(
- x.content, x.commentId, x.userName, x.repositoryName)
- } getOrElse {
- contentType = formats("json")
- org.json4s.jackson.Serialization.write(
- Map("content" -> view.Markdown.toHtml(x.content,
- repository, false, true)
- ))
- }
- } else Unauthorized
- } getOrElse NotFound
- })
-
- ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
- defining(params("id").toInt){ issueId =>
- registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
- issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
- }
- })
-
- ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
- defining(params("id").toInt){ issueId =>
- deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
- issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
- }
- })
-
- ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
- updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
- Ok("updated")
- })
-
- ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
- updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
- milestoneId("milestoneId").map { milestoneId =>
- getMilestonesWithIssueCount(repository.owner, repository.name)
- .find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
- issues.milestones.html.progress(openCount + closeCount, closeCount, false)
- } getOrElse NotFound
- } getOrElse Ok()
- })
-
- post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
- defining(params.get("value")){ action =>
- executeBatch(repository) {
- handleComment(_, None, repository)( _ => action)
- }
- }
- })
-
- post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
- params("value").toIntOpt.map{ labelId =>
- executeBatch(repository) { issueId =>
- getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
- registerIssueLabel(repository.owner, repository.name, issueId, labelId)
- }
- }
- } getOrElse NotFound
- })
-
- post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
- defining(assignedUserName("value")){ value =>
- executeBatch(repository) {
- updateAssignedUserName(repository.owner, repository.name, _, value)
- }
- }
- })
-
- post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
- defining(milestoneId("value")){ value =>
- executeBatch(repository) {
- updateMilestoneId(repository.owner, repository.name, _, value)
- }
- }
- })
-
- get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
- (Directory.getAttachedDir(repository.owner, repository.name) match {
- case dir if(dir.exists && dir.isDirectory) =>
- dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
- contentType = FileUtil.getMimeType(file.getName)
- file
- }
- case _ => None
- }) getOrElse NotFound
- })
-
- val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
- val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
-
- private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
- hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
-
- private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
- params("checked").split(',') map(_.toInt) foreach execute
- redirect(s"/${repository.owner}/${repository.name}/issues")
- }
-
- private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
- StringUtil.extractIssueId(message).foreach { issueId =>
- if(getIssue(owner, repository, issueId).isDefined){
- createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
- fromIssue.issueId + ":" + fromIssue.title, "refer")
- }
- }
- }
-
- /**
- * @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
- */
- private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
- (getAction: model.Issue => Option[String] =
- p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
-
- defining(repository.owner, repository.name){ case (owner, name) =>
- val userName = context.loginAccount.get.userName
-
- getIssue(owner, name, issueId.toString) map { issue =>
- val (action, recordActivity) =
- getAction(issue)
- .collect {
- case "close" => true -> (Some("close") ->
- Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
- case "reopen" => false -> (Some("reopen") ->
- Some(recordReopenIssueActivity _))
- }
- .map { case (closed, t) =>
- updateClosed(owner, name, issueId, closed)
- t
- }
- .getOrElse(None -> None)
-
- val commentId = content
- .map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
- .getOrElse ( action.get.capitalize -> action.get )
- match {
- case (content, action) => createComment(owner, name, userName, issueId, content, action)
- }
-
- // record activity
- content foreach {
- (if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
- (owner, name, userName, issueId, _)
- }
- recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
-
- // extract references and create refer comment
- content.map { content =>
- createReferComment(owner, name, issue, content)
- }
-
- // notifications
- Notifier() match {
- case f =>
- content foreach {
- f.toNotify(repository, issueId, _){
- Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
- if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
- }
- }
- action foreach {
- f.toNotify(repository, issueId, _){
- Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
- }
- }
- }
-
- issue -> commentId
- }
- }
- }
-
- private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
- defining(repository.owner, repository.name){ case (owner, repoName) =>
- val filterUser = Map(filter -> params.getOrElse("userName", ""))
- val page = IssueSearchCondition.page(request)
- val sessionKey = Keys.Session.Issues(owner, repoName)
-
- // retrieve search condition
- val condition = session.putAndGet(sessionKey,
- if(request.hasQueryString) IssueSearchCondition(request)
- else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
- )
-
- issues.html.list(
- searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
- page,
- (getCollaborators(owner, repoName) :+ owner).sorted,
- getMilestones(owner, repoName),
- getLabels(owner, repoName),
- countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
- countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
- countIssue(condition, Map.empty, false, owner -> repoName),
- context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
- context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
- countIssueGroupByLabels(owner, repoName, condition, filterUser),
- condition,
- filter,
- repository,
- hasWritePermission(owner, repoName, context.loginAccount))
- }
- }
-
-}
+package app
+
+import jp.sf.amateras.scalatra.forms._
+
+import service._
+import IssuesService._
+import util._
+import util.Implicits._
+import util.ControlUtil._
+import org.scalatra.Ok
+import model.Issue
+
+class IssuesController extends IssuesControllerBase
+ with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
+
+trait IssuesControllerBase extends ControllerBase {
+ self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
+
+ case class IssueCreateForm(title: String, content: Option[String],
+ assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
+ case class IssueEditForm(title: String, content: Option[String])
+ case class CommentForm(issueId: Int, content: String)
+ case class IssueStateForm(issueId: Int, content: Option[String])
+
+ val issueCreateForm = mapping(
+ "title" -> trim(label("Title", text(required))),
+ "content" -> trim(optional(text())),
+ "assignedUserName" -> trim(optional(text())),
+ "milestoneId" -> trim(optional(number())),
+ "labelNames" -> trim(optional(text()))
+ )(IssueCreateForm.apply)
+
+ val issueEditForm = mapping(
+ "title" -> trim(label("Title", text(required))),
+ "content" -> trim(optional(text()))
+ )(IssueEditForm.apply)
+
+ val commentForm = mapping(
+ "issueId" -> label("Issue Id", number()),
+ "content" -> trim(label("Comment", text(required)))
+ )(CommentForm.apply)
+
+ val issueStateForm = mapping(
+ "issueId" -> label("Issue Id", number()),
+ "content" -> trim(optional(text()))
+ )(IssueStateForm.apply)
+
+ get("/:owner/:repository/issues")(referrersOnly {
+ searchIssues("all", _)
+ })
+
+ get("/:owner/:repository/issues/assigned/:userName")(referrersOnly {
+ searchIssues("assigned", _)
+ })
+
+ get("/:owner/:repository/issues/created_by/:userName")(referrersOnly {
+ searchIssues("created_by", _)
+ })
+
+ get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
+ defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
+ getIssue(owner, name, issueId) map {
+ issues.html.issue(
+ _,
+ getComments(owner, name, issueId.toInt),
+ getIssueLabels(owner, name, issueId.toInt),
+ (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
+ getMilestonesWithIssueCount(owner, name),
+ getLabels(owner, name),
+ hasWritePermission(owner, name, context.loginAccount),
+ repository)
+ } getOrElse NotFound
+ }
+ })
+
+ get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ issues.html.create(
+ (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
+ getMilestones(owner, name),
+ getLabels(owner, name),
+ hasWritePermission(owner, name, context.loginAccount),
+ repository)
+ }
+ })
+
+ post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ val writable = hasWritePermission(owner, name, context.loginAccount)
+ val userName = context.loginAccount.get.userName
+
+ // insert issue
+ val issueId = createIssue(owner, name, userName, form.title, form.content,
+ if(writable) form.assignedUserName else None,
+ if(writable) form.milestoneId else None)
+
+ // insert labels
+ if(writable){
+ 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)
+
+ // extract references and create refer comment
+ getIssue(owner, name, issueId.toString).foreach { issue =>
+ createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
+ }
+
+ // notifications
+ Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
+ Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
+ }
+
+ redirect(s"/${owner}/${name}/issues/${issueId}")
+ }
+ })
+
+ ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ getIssue(owner, name, params("id")).map { issue =>
+ if(isEditable(owner, name, issue.openedUserName)){
+ // update issue
+ updateIssue(owner, name, issue.issueId, form.title, form.content)
+ // extract references and create refer comment
+ createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
+
+ redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
+ } else Unauthorized
+ } getOrElse NotFound
+ }
+ })
+
+ post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
+ handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
+ redirect(s"/${repository.owner}/${repository.name}/${
+ if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
+ } getOrElse NotFound
+ })
+
+ post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
+ handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
+ redirect(s"/${repository.owner}/${repository.name}/${
+ if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
+ } getOrElse NotFound
+ })
+
+ ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ getComment(owner, name, params("id")).map { comment =>
+ if(isEditable(owner, name, comment.commentedUserName)){
+ updateComment(comment.commentId, form.content)
+ redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
+ } else Unauthorized
+ } getOrElse NotFound
+ }
+ })
+
+ ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ getComment(owner, name, params("id")).map { comment =>
+ if(isEditable(owner, name, comment.commentedUserName)){
+ Ok(deleteComment(comment.commentId))
+ } else Unauthorized
+ } getOrElse NotFound
+ }
+ })
+
+ ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
+ getIssue(repository.owner, repository.name, params("id")) map { x =>
+ if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
+ params.get("dataType") collect {
+ case t if t == "html" => issues.html.editissue(
+ x.title, x.content, x.issueId, x.userName, x.repositoryName)
+ } getOrElse {
+ contentType = formats("json")
+ org.json4s.jackson.Serialization.write(
+ Map("title" -> x.title,
+ "content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
+ repository, false, true)
+ ))
+ }
+ } else Unauthorized
+ } getOrElse NotFound
+ })
+
+ ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
+ getComment(repository.owner, repository.name, params("id")) map { x =>
+ if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
+ params.get("dataType") collect {
+ case t if t == "html" => issues.html.editcomment(
+ x.content, x.commentId, x.userName, x.repositoryName)
+ } getOrElse {
+ contentType = formats("json")
+ org.json4s.jackson.Serialization.write(
+ Map("content" -> view.Markdown.toHtml(x.content,
+ repository, false, true)
+ ))
+ }
+ } else Unauthorized
+ } getOrElse NotFound
+ })
+
+ ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
+ defining(params("id").toInt){ issueId =>
+ registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
+ issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
+ }
+ })
+
+ ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
+ defining(params("id").toInt){ issueId =>
+ deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
+ issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
+ }
+ })
+
+ ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
+ updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
+ Ok("updated")
+ })
+
+ ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
+ updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
+ milestoneId("milestoneId").map { milestoneId =>
+ getMilestonesWithIssueCount(repository.owner, repository.name)
+ .find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
+ issues.milestones.html.progress(openCount + closeCount, closeCount, false)
+ } getOrElse NotFound
+ } getOrElse Ok()
+ })
+
+ post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
+ defining(params.get("value")){ action =>
+ executeBatch(repository) {
+ handleComment(_, None, repository)( _ => action)
+ }
+ }
+ })
+
+ post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
+ params("value").toIntOpt.map{ labelId =>
+ executeBatch(repository) { issueId =>
+ getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
+ registerIssueLabel(repository.owner, repository.name, issueId, labelId)
+ }
+ }
+ } getOrElse NotFound
+ })
+
+ post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
+ defining(assignedUserName("value")){ value =>
+ executeBatch(repository) {
+ updateAssignedUserName(repository.owner, repository.name, _, value)
+ }
+ }
+ })
+
+ post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
+ defining(milestoneId("value")){ value =>
+ executeBatch(repository) {
+ updateMilestoneId(repository.owner, repository.name, _, value)
+ }
+ }
+ })
+
+ get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
+ (Directory.getAttachedDir(repository.owner, repository.name) match {
+ case dir if(dir.exists && dir.isDirectory) =>
+ dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
+ contentType = FileUtil.getMimeType(file.getName)
+ file
+ }
+ case _ => None
+ }) getOrElse NotFound
+ })
+
+ val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
+ val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
+
+ private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
+ hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
+
+ private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
+ params("checked").split(',') map(_.toInt) foreach execute
+ redirect(s"/${repository.owner}/${repository.name}/issues")
+ }
+
+ private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
+ StringUtil.extractIssueId(message).foreach { issueId =>
+ if(getIssue(owner, repository, issueId).isDefined){
+ createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
+ fromIssue.issueId + ":" + fromIssue.title, "refer")
+ }
+ }
+ }
+
+ /**
+ * @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
+ */
+ private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
+ (getAction: model.Issue => Option[String] =
+ p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
+
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ val userName = context.loginAccount.get.userName
+
+ getIssue(owner, name, issueId.toString) map { issue =>
+ val (action, recordActivity) =
+ getAction(issue)
+ .collect {
+ case "close" => true -> (Some("close") ->
+ Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
+ case "reopen" => false -> (Some("reopen") ->
+ Some(recordReopenIssueActivity _))
+ }
+ .map { case (closed, t) =>
+ updateClosed(owner, name, issueId, closed)
+ t
+ }
+ .getOrElse(None -> None)
+
+ val commentId = content
+ .map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
+ .getOrElse ( action.get.capitalize -> action.get )
+ match {
+ case (content, action) => createComment(owner, name, userName, issueId, content, action)
+ }
+
+ // record activity
+ content foreach {
+ (if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
+ (owner, name, userName, issueId, _)
+ }
+ recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
+
+ // extract references and create refer comment
+ content.map { content =>
+ createReferComment(owner, name, issue, content)
+ }
+
+ // notifications
+ Notifier() match {
+ case f =>
+ content foreach {
+ f.toNotify(repository, issueId, _){
+ Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
+ if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
+ }
+ }
+ action foreach {
+ f.toNotify(repository, issueId, _){
+ Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
+ }
+ }
+ }
+
+ issue -> commentId
+ }
+ }
+ }
+
+ private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
+ defining(repository.owner, repository.name){ case (owner, repoName) =>
+ val filterUser = Map(filter -> params.getOrElse("userName", ""))
+ val page = IssueSearchCondition.page(request)
+ val sessionKey = Keys.Session.Issues(owner, repoName)
+
+ // retrieve search condition
+ val condition = session.putAndGet(sessionKey,
+ if(request.hasQueryString) IssueSearchCondition(request)
+ else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
+ )
+
+ issues.html.list(
+ searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
+ page,
+ (getCollaborators(owner, repoName) :+ owner).sorted,
+ getMilestones(owner, repoName),
+ getLabels(owner, repoName),
+ countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
+ countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
+ countIssue(condition, Map.empty, false, owner -> repoName),
+ context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
+ context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
+ countIssueGroupByLabels(owner, repoName, condition, filterUser),
+ condition,
+ filter,
+ repository,
+ hasWritePermission(owner, repoName, context.loginAccount))
+ }
+ }
+
+}
diff --git a/src/main/scala/app/RepositorySettingsController.scala b/src/main/scala/app/RepositorySettingsController.scala
index ed30d93..103e0ea 100644
--- a/src/main/scala/app/RepositorySettingsController.scala
+++ b/src/main/scala/app/RepositorySettingsController.scala
@@ -1,269 +1,269 @@
-package app
-
-import service._
-import util.Directory._
-import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
-import jp.sf.amateras.scalatra.forms._
-import org.apache.commons.io.FileUtils
-import org.scalatra.i18n.Messages
-import service.WebHookService.WebHookPayload
-import util.JGitUtil.CommitInfo
-import util.ControlUtil._
-import org.eclipse.jgit.api.Git
-
-class RepositorySettingsController extends RepositorySettingsControllerBase
- with RepositoryService with AccountService with WebHookService
- with OwnerAuthenticator with UsersAuthenticator
-
-trait RepositorySettingsControllerBase extends ControllerBase {
- self: RepositoryService with AccountService with WebHookService
- with OwnerAuthenticator with UsersAuthenticator =>
-
- // for repository options
- case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
-
- val optionsForm = mapping(
- "repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
- "description" -> trim(label("Description" , optional(text()))),
- "defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
- "isPrivate" -> trim(label("Repository Type", boolean()))
- )(OptionsForm.apply)
-
- // for collaborator addition
- case class CollaboratorForm(userName: String)
-
- val collaboratorForm = mapping(
- "userName" -> trim(label("Username", text(required, collaborator)))
- )(CollaboratorForm.apply)
-
- // for web hook url addition
- case class WebHookForm(url: String)
-
- val webHookForm = mapping(
- "url" -> trim(label("url", text(required, webHook)))
- )(WebHookForm.apply)
-
- // for transfer ownership
- case class TransferOwnerShipForm(newOwner: String)
-
- val transferForm = mapping(
- "newOwner" -> trim(label("New owner", text(required, transferUser)))
- )(TransferOwnerShipForm.apply)
-
- /**
- * Redirect to the Options page.
- */
- get("/:owner/:repository/settings")(ownerOnly { repository =>
- redirect(s"/${repository.owner}/${repository.name}/settings/options")
- })
-
- /**
- * Display the Options page.
- */
- get("/:owner/:repository/settings/options")(ownerOnly {
- settings.html.options(_, flash.get("info"))
- })
-
- /**
- * Save the repository options.
- */
- post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
- saveRepositoryOptions(
- repository.owner,
- repository.name,
- form.description,
- if(repository.branchList.isEmpty) "master" else form.defaultBranch,
- repository.repository.parentUserName.map { _ =>
- repository.repository.isPrivate
- } getOrElse form.isPrivate
- )
- // Change repository name
- if(repository.name != form.repositoryName){
- // Update database
- renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
- // Move git repository
- defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
- FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
- }
- // Move wiki repository
- defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
- FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
- }
- }
- flash += "info" -> "Repository settings has been updated."
- redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
- })
-
- /**
- * Display the Collaborators page.
- */
- get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
- settings.html.collaborators(
- getCollaborators(repository.owner, repository.name),
- getAccountByUserName(repository.owner).get.isGroupAccount,
- repository)
- })
-
- /**
- * Add the collaborator.
- */
- post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
- if(!getAccountByUserName(repository.owner).get.isGroupAccount){
- addCollaborator(repository.owner, repository.name, form.userName)
- }
- redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
- })
-
- /**
- * Add the collaborator.
- */
- get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
- if(!getAccountByUserName(repository.owner).get.isGroupAccount){
- removeCollaborator(repository.owner, repository.name, params("name"))
- }
- redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
- })
-
- /**
- * Display the web hook page.
- */
- get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
- settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
- })
-
- /**
- * Add the web hook URL.
- */
- post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
- addWebHookURL(repository.owner, repository.name, form.url)
- redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
- })
-
- /**
- * Delete the web hook URL.
- */
- get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
- deleteWebHookURL(repository.owner, repository.name, params("url"))
- redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
- })
-
- /**
- * Send the test request to registered web hook URLs.
- */
- get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
- using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
- import scala.collection.JavaConverters._
- val commits = git.log
- .add(git.getRepository.resolve(repository.repository.defaultBranch))
- .setMaxCount(3)
- .call.iterator.asScala.map(new CommitInfo(_))
-
- getWebHookURLs(repository.owner, repository.name) match {
- case webHookURLs if(webHookURLs.nonEmpty) =>
- for(ownerAccount <- getAccountByUserName(repository.owner)){
- callWebHook(repository.owner, repository.name, webHookURLs,
- WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
- }
- case _ =>
- }
-
- flash += "info" -> "Test payload deployed!"
- }
- redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
- })
-
- /**
- * Display the danger zone.
- */
- get("/:owner/:repository/settings/danger")(ownerOnly {
- settings.html.danger(_)
- })
-
- /**
- * Transfer repository ownership.
- */
- post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
- // Change repository owner
- if(repository.owner != form.newOwner){
- LockUtil.lock(s"${repository.owner}/${repository.name}"){
- // Update database
- renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
- // Move git repository
- defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
- FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
- }
- // Move wiki repository
- defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
- FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
- }
- }
- }
- redirect(s"/${form.newOwner}/${repository.name}")
- })
-
- /**
- * Delete the repository.
- */
- post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
- LockUtil.lock(s"${repository.owner}/${repository.name}"){
- deleteRepository(repository.owner, repository.name)
-
- FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
- FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
- FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
- }
- redirect(s"/${repository.owner}")
- })
-
- /**
- * Provides duplication check for web hook url.
- */
- private def webHook: Constraint = new Constraint(){
- override def validate(name: String, value: String, messages: Messages): Option[String] =
- getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
- }
-
- /**
- * Provides Constraint to validate the collaborator name.
- */
- private def collaborator: Constraint = new Constraint(){
- override def validate(name: String, value: String, messages: Messages): Option[String] =
- getAccountByUserName(value) match {
- case None => Some("User does not exist.")
- case Some(x) if(x.isGroupAccount)
- => Some("User does not exist.")
- case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
- => Some("User can access this repository already.")
- case _ => None
- }
- }
-
- /**
- * Duplicate check for the rename repository name.
- */
- private def renameRepositoryName: Constraint = new Constraint(){
- override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
- params.get("repository").filter(_ != value).flatMap { _ =>
- params.get("owner").flatMap { userName =>
- getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
- }
- }
- }
-
- /**
- * Provides Constraint to validate the repository transfer user.
- */
- private def transferUser: Constraint = new Constraint(){
- override def validate(name: String, value: String, messages: Messages): Option[String] =
- getAccountByUserName(value) match {
- case None => Some("User does not exist.")
- case Some(x) => if(x.userName == params("owner")){
- Some("This is current repository owner.")
- } else {
- params.get("repository").flatMap { repositoryName =>
- getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
- }
- }
- }
- }
+package app
+
+import service._
+import util.Directory._
+import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
+import jp.sf.amateras.scalatra.forms._
+import org.apache.commons.io.FileUtils
+import org.scalatra.i18n.Messages
+import service.WebHookService.WebHookPayload
+import util.JGitUtil.CommitInfo
+import util.ControlUtil._
+import org.eclipse.jgit.api.Git
+
+class RepositorySettingsController extends RepositorySettingsControllerBase
+ with RepositoryService with AccountService with WebHookService
+ with OwnerAuthenticator with UsersAuthenticator
+
+trait RepositorySettingsControllerBase extends ControllerBase {
+ self: RepositoryService with AccountService with WebHookService
+ with OwnerAuthenticator with UsersAuthenticator =>
+
+ // for repository options
+ case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
+
+ val optionsForm = mapping(
+ "repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
+ "description" -> trim(label("Description" , optional(text()))),
+ "defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
+ "isPrivate" -> trim(label("Repository Type", boolean()))
+ )(OptionsForm.apply)
+
+ // for collaborator addition
+ case class CollaboratorForm(userName: String)
+
+ val collaboratorForm = mapping(
+ "userName" -> trim(label("Username", text(required, collaborator)))
+ )(CollaboratorForm.apply)
+
+ // for web hook url addition
+ case class WebHookForm(url: String)
+
+ val webHookForm = mapping(
+ "url" -> trim(label("url", text(required, webHook)))
+ )(WebHookForm.apply)
+
+ // for transfer ownership
+ case class TransferOwnerShipForm(newOwner: String)
+
+ val transferForm = mapping(
+ "newOwner" -> trim(label("New owner", text(required, transferUser)))
+ )(TransferOwnerShipForm.apply)
+
+ /**
+ * Redirect to the Options page.
+ */
+ get("/:owner/:repository/settings")(ownerOnly { repository =>
+ redirect(s"/${repository.owner}/${repository.name}/settings/options")
+ })
+
+ /**
+ * Display the Options page.
+ */
+ get("/:owner/:repository/settings/options")(ownerOnly {
+ settings.html.options(_, flash.get("info"))
+ })
+
+ /**
+ * Save the repository options.
+ */
+ post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
+ saveRepositoryOptions(
+ repository.owner,
+ repository.name,
+ form.description,
+ if(repository.branchList.isEmpty) "master" else form.defaultBranch,
+ repository.repository.parentUserName.map { _ =>
+ repository.repository.isPrivate
+ } getOrElse form.isPrivate
+ )
+ // Change repository name
+ if(repository.name != form.repositoryName){
+ // Update database
+ renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
+ // Move git repository
+ defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
+ FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
+ }
+ // Move wiki repository
+ defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
+ FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
+ }
+ }
+ flash += "info" -> "Repository settings has been updated."
+ redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
+ })
+
+ /**
+ * Display the Collaborators page.
+ */
+ get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
+ settings.html.collaborators(
+ getCollaborators(repository.owner, repository.name),
+ getAccountByUserName(repository.owner).get.isGroupAccount,
+ repository)
+ })
+
+ /**
+ * Add the collaborator.
+ */
+ post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
+ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
+ addCollaborator(repository.owner, repository.name, form.userName)
+ }
+ redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
+ })
+
+ /**
+ * Add the collaborator.
+ */
+ get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
+ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
+ removeCollaborator(repository.owner, repository.name, params("name"))
+ }
+ redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
+ })
+
+ /**
+ * Display the web hook page.
+ */
+ get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
+ settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
+ })
+
+ /**
+ * Add the web hook URL.
+ */
+ post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
+ addWebHookURL(repository.owner, repository.name, form.url)
+ redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
+ })
+
+ /**
+ * Delete the web hook URL.
+ */
+ get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
+ deleteWebHookURL(repository.owner, repository.name, params("url"))
+ redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
+ })
+
+ /**
+ * Send the test request to registered web hook URLs.
+ */
+ get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
+ using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
+ import scala.collection.JavaConverters._
+ val commits = git.log
+ .add(git.getRepository.resolve(repository.repository.defaultBranch))
+ .setMaxCount(3)
+ .call.iterator.asScala.map(new CommitInfo(_))
+
+ getWebHookURLs(repository.owner, repository.name) match {
+ case webHookURLs if(webHookURLs.nonEmpty) =>
+ for(ownerAccount <- getAccountByUserName(repository.owner)){
+ callWebHook(repository.owner, repository.name, webHookURLs,
+ WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
+ }
+ case _ =>
+ }
+
+ flash += "info" -> "Test payload deployed!"
+ }
+ redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
+ })
+
+ /**
+ * Display the danger zone.
+ */
+ get("/:owner/:repository/settings/danger")(ownerOnly {
+ settings.html.danger(_)
+ })
+
+ /**
+ * Transfer repository ownership.
+ */
+ post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
+ // Change repository owner
+ if(repository.owner != form.newOwner){
+ LockUtil.lock(s"${repository.owner}/${repository.name}"){
+ // Update database
+ renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
+ // Move git repository
+ defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
+ FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
+ }
+ // Move wiki repository
+ defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
+ FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
+ }
+ }
+ }
+ redirect(s"/${form.newOwner}/${repository.name}")
+ })
+
+ /**
+ * Delete the repository.
+ */
+ post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
+ LockUtil.lock(s"${repository.owner}/${repository.name}"){
+ deleteRepository(repository.owner, repository.name)
+
+ FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
+ FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
+ FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
+ }
+ redirect(s"/${repository.owner}")
+ })
+
+ /**
+ * Provides duplication check for web hook url.
+ */
+ private def webHook: Constraint = new Constraint(){
+ override def validate(name: String, value: String, messages: Messages): Option[String] =
+ getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
+ }
+
+ /**
+ * Provides Constraint to validate the collaborator name.
+ */
+ private def collaborator: Constraint = new Constraint(){
+ override def validate(name: String, value: String, messages: Messages): Option[String] =
+ getAccountByUserName(value) match {
+ case None => Some("User does not exist.")
+ case Some(x) if(x.isGroupAccount)
+ => Some("User does not exist.")
+ case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
+ => Some("User can access this repository already.")
+ case _ => None
+ }
+ }
+
+ /**
+ * Duplicate check for the rename repository name.
+ */
+ private def renameRepositoryName: Constraint = new Constraint(){
+ override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
+ params.get("repository").filter(_ != value).flatMap { _ =>
+ params.get("owner").flatMap { userName =>
+ getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
+ }
+ }
+ }
+
+ /**
+ * Provides Constraint to validate the repository transfer user.
+ */
+ private def transferUser: Constraint = new Constraint(){
+ override def validate(name: String, value: String, messages: Messages): Option[String] =
+ getAccountByUserName(value) match {
+ case None => Some("User does not exist.")
+ case Some(x) => if(x.userName == params("owner")){
+ Some("This is current repository owner.")
+ } else {
+ params.get("repository").flatMap { repositoryName =>
+ getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/scala/model/Account.scala b/src/main/scala/model/Account.scala
index 8c3ff27..93d0f68 100644
--- a/src/main/scala/model/Account.scala
+++ b/src/main/scala/model/Account.scala
@@ -1,34 +1,34 @@
-package model
-
-import scala.slick.driver.H2Driver.simple._
-
-object Accounts extends Table[Account]("ACCOUNT") {
- def userName = column[String]("USER_NAME", O PrimaryKey)
- def fullName = column[String]("FULL_NAME")
- def mailAddress = column[String]("MAIL_ADDRESS")
- def password = column[String]("PASSWORD")
- def isAdmin = column[Boolean]("ADMINISTRATOR")
- def url = column[String]("URL")
- def registeredDate = column[java.util.Date]("REGISTERED_DATE")
- def updatedDate = column[java.util.Date]("UPDATED_DATE")
- def lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
- def image = column[String]("IMAGE")
- def groupAccount = column[Boolean]("GROUP_ACCOUNT")
- def removed = column[Boolean]("REMOVED")
- def * = userName ~ fullName ~ mailAddress ~ password ~ isAdmin ~ url.? ~ registeredDate ~ updatedDate ~ lastLoginDate.? ~ image.? ~ groupAccount ~ removed <> (Account, Account.unapply _)
-}
-
-case class Account(
- userName: String,
- fullName: String,
- mailAddress: String,
- password: String,
- isAdmin: Boolean,
- url: Option[String],
- registeredDate: java.util.Date,
- updatedDate: java.util.Date,
- lastLoginDate: Option[java.util.Date],
- image: Option[String],
- isGroupAccount: Boolean,
- isRemoved: Boolean
-)
+package model
+
+import scala.slick.driver.H2Driver.simple._
+
+object Accounts extends Table[Account]("ACCOUNT") {
+ def userName = column[String]("USER_NAME", O PrimaryKey)
+ def fullName = column[String]("FULL_NAME")
+ def mailAddress = column[String]("MAIL_ADDRESS")
+ def password = column[String]("PASSWORD")
+ def isAdmin = column[Boolean]("ADMINISTRATOR")
+ def url = column[String]("URL")
+ def registeredDate = column[java.util.Date]("REGISTERED_DATE")
+ def updatedDate = column[java.util.Date]("UPDATED_DATE")
+ def lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
+ def image = column[String]("IMAGE")
+ def groupAccount = column[Boolean]("GROUP_ACCOUNT")
+ def removed = column[Boolean]("REMOVED")
+ def * = userName ~ fullName ~ mailAddress ~ password ~ isAdmin ~ url.? ~ registeredDate ~ updatedDate ~ lastLoginDate.? ~ image.? ~ groupAccount ~ removed <> (Account, Account.unapply _)
+}
+
+case class Account(
+ userName: String,
+ fullName: String,
+ mailAddress: String,
+ password: String,
+ isAdmin: Boolean,
+ url: Option[String],
+ registeredDate: java.util.Date,
+ updatedDate: java.util.Date,
+ lastLoginDate: Option[java.util.Date],
+ image: Option[String],
+ isGroupAccount: Boolean,
+ isRemoved: Boolean
+)
diff --git a/src/main/scala/model/BasicTemplate.scala b/src/main/scala/model/BasicTemplate.scala
index 2d92e64..3327f06 100644
--- a/src/main/scala/model/BasicTemplate.scala
+++ b/src/main/scala/model/BasicTemplate.scala
@@ -1,44 +1,44 @@
-package model
-
-import scala.slick.driver.H2Driver.simple._
-
-protected[model] trait BasicTemplate { self: Table[_] =>
- def userName = column[String]("USER_NAME")
- def repositoryName = column[String]("REPOSITORY_NAME")
-
- def byRepository(owner: String, repository: String) =
- (userName is owner.bind) && (repositoryName is repository.bind)
-
- def byRepository(userName: Column[String], repositoryName: Column[String]) =
- (this.userName is userName) && (this.repositoryName is repositoryName)
-}
-
-protected[model] trait IssueTemplate extends BasicTemplate { self: Table[_] =>
- def issueId = column[Int]("ISSUE_ID")
-
- def byIssue(owner: String, repository: String, issueId: Int) =
- byRepository(owner, repository) && (this.issueId is issueId.bind)
-
- def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
- byRepository(userName, repositoryName) && (this.issueId is issueId)
-}
-
-protected[model] trait LabelTemplate extends BasicTemplate { self: Table[_] =>
- def labelId = column[Int]("LABEL_ID")
-
- def byLabel(owner: String, repository: String, labelId: Int) =
- byRepository(owner, repository) && (this.labelId is labelId.bind)
-
- def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
- byRepository(userName, repositoryName) && (this.labelId is labelId)
-}
-
-protected[model] trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
- def milestoneId = column[Int]("MILESTONE_ID")
-
- def byMilestone(owner: String, repository: String, milestoneId: Int) =
- byRepository(owner, repository) && (this.milestoneId is milestoneId.bind)
-
- def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
- byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
+package model
+
+import scala.slick.driver.H2Driver.simple._
+
+protected[model] trait BasicTemplate { self: Table[_] =>
+ def userName = column[String]("USER_NAME")
+ def repositoryName = column[String]("REPOSITORY_NAME")
+
+ def byRepository(owner: String, repository: String) =
+ (userName is owner.bind) && (repositoryName is repository.bind)
+
+ def byRepository(userName: Column[String], repositoryName: Column[String]) =
+ (this.userName is userName) && (this.repositoryName is repositoryName)
+}
+
+protected[model] trait IssueTemplate extends BasicTemplate { self: Table[_] =>
+ def issueId = column[Int]("ISSUE_ID")
+
+ def byIssue(owner: String, repository: String, issueId: Int) =
+ byRepository(owner, repository) && (this.issueId is issueId.bind)
+
+ def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
+ byRepository(userName, repositoryName) && (this.issueId is issueId)
+}
+
+protected[model] trait LabelTemplate extends BasicTemplate { self: Table[_] =>
+ def labelId = column[Int]("LABEL_ID")
+
+ def byLabel(owner: String, repository: String, labelId: Int) =
+ byRepository(owner, repository) && (this.labelId is labelId.bind)
+
+ def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
+ byRepository(userName, repositoryName) && (this.labelId is labelId)
+}
+
+protected[model] trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
+ def milestoneId = column[Int]("MILESTONE_ID")
+
+ def byMilestone(owner: String, repository: String, milestoneId: Int) =
+ byRepository(owner, repository) && (this.milestoneId is milestoneId.bind)
+
+ def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
+ byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
}
\ No newline at end of file
diff --git a/src/main/scala/model/Issue.scala b/src/main/scala/model/Issue.scala
index d5ce8a3..c44407e 100644
--- a/src/main/scala/model/Issue.scala
+++ b/src/main/scala/model/Issue.scala
@@ -1,41 +1,41 @@
-package model
-
-import scala.slick.driver.H2Driver.simple._
-
-object IssueId extends Table[(String, String, Int)]("ISSUE_ID") with IssueTemplate {
- def * = userName ~ repositoryName ~ issueId
- def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
-}
-
-object IssueOutline extends Table[(String, String, Int, Int)]("ISSUE_OUTLINE_VIEW") with IssueTemplate {
- def commentCount = column[Int]("COMMENT_COUNT")
- def * = userName ~ repositoryName ~ issueId ~ commentCount
-}
-
-object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate {
- def openedUserName = column[String]("OPENED_USER_NAME")
- def assignedUserName = column[String]("ASSIGNED_USER_NAME")
- def title = column[String]("TITLE")
- def content = column[String]("CONTENT")
- def closed = column[Boolean]("CLOSED")
- def registeredDate = column[java.util.Date]("REGISTERED_DATE")
- def updatedDate = column[java.util.Date]("UPDATED_DATE")
- def pullRequest = column[Boolean]("PULL_REQUEST")
- def * = userName ~ repositoryName ~ issueId ~ openedUserName ~ milestoneId.? ~ assignedUserName.? ~ title ~ content.? ~ closed ~ registeredDate ~ updatedDate ~ pullRequest <> (Issue, Issue.unapply _)
-
- def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
-}
-
-case class Issue(
- userName: String,
- repositoryName: String,
- issueId: Int,
- openedUserName: String,
- milestoneId: Option[Int],
- assignedUserName: Option[String],
- title: String,
- content: Option[String],
- closed: Boolean,
- registeredDate: java.util.Date,
- updatedDate: java.util.Date,
+package model
+
+import scala.slick.driver.H2Driver.simple._
+
+object IssueId extends Table[(String, String, Int)]("ISSUE_ID") with IssueTemplate {
+ def * = userName ~ repositoryName ~ issueId
+ def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
+}
+
+object IssueOutline extends Table[(String, String, Int, Int)]("ISSUE_OUTLINE_VIEW") with IssueTemplate {
+ def commentCount = column[Int]("COMMENT_COUNT")
+ def * = userName ~ repositoryName ~ issueId ~ commentCount
+}
+
+object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate {
+ def openedUserName = column[String]("OPENED_USER_NAME")
+ def assignedUserName = column[String]("ASSIGNED_USER_NAME")
+ def title = column[String]("TITLE")
+ def content = column[String]("CONTENT")
+ def closed = column[Boolean]("CLOSED")
+ def registeredDate = column[java.util.Date]("REGISTERED_DATE")
+ def updatedDate = column[java.util.Date]("UPDATED_DATE")
+ def pullRequest = column[Boolean]("PULL_REQUEST")
+ def * = userName ~ repositoryName ~ issueId ~ openedUserName ~ milestoneId.? ~ assignedUserName.? ~ title ~ content.? ~ closed ~ registeredDate ~ updatedDate ~ pullRequest <> (Issue, Issue.unapply _)
+
+ def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
+}
+
+case class Issue(
+ userName: String,
+ repositoryName: String,
+ issueId: Int,
+ openedUserName: String,
+ milestoneId: Option[Int],
+ assignedUserName: Option[String],
+ title: String,
+ content: Option[String],
+ closed: Boolean,
+ registeredDate: java.util.Date,
+ updatedDate: java.util.Date,
isPullRequest: Boolean)
\ No newline at end of file
diff --git a/src/main/scala/model/IssueComment.scala b/src/main/scala/model/IssueComment.scala
index f9fd983..0452b5d 100644
--- a/src/main/scala/model/IssueComment.scala
+++ b/src/main/scala/model/IssueComment.scala
@@ -1,28 +1,28 @@
-package model
-
-import scala.slick.driver.H2Driver.simple._
-
-object IssueComments extends Table[IssueComment]("ISSUE_COMMENT") with IssueTemplate {
- def commentId = column[Int]("COMMENT_ID", O AutoInc)
- def action = column[String]("ACTION")
- def commentedUserName = column[String]("COMMENTED_USER_NAME")
- def content = column[String]("CONTENT")
- def registeredDate = column[java.util.Date]("REGISTERED_DATE")
- def updatedDate = column[java.util.Date]("UPDATED_DATE")
- def * = userName ~ repositoryName ~ issueId ~ commentId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate <> (IssueComment, IssueComment.unapply _)
-
- def autoInc = userName ~ repositoryName ~ issueId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate returning commentId
- def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
-}
-
-case class IssueComment(
- userName: String,
- repositoryName: String,
- issueId: Int,
- commentId: Int,
- action: String,
- commentedUserName: String,
- content: String,
- registeredDate: java.util.Date,
- updatedDate: java.util.Date
+package model
+
+import scala.slick.driver.H2Driver.simple._
+
+object IssueComments extends Table[IssueComment]("ISSUE_COMMENT") with IssueTemplate {
+ def commentId = column[Int]("COMMENT_ID", O AutoInc)
+ def action = column[String]("ACTION")
+ def commentedUserName = column[String]("COMMENTED_USER_NAME")
+ def content = column[String]("CONTENT")
+ def registeredDate = column[java.util.Date]("REGISTERED_DATE")
+ def updatedDate = column[java.util.Date]("UPDATED_DATE")
+ def * = userName ~ repositoryName ~ issueId ~ commentId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate <> (IssueComment, IssueComment.unapply _)
+
+ def autoInc = userName ~ repositoryName ~ issueId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate returning commentId
+ def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
+}
+
+case class IssueComment(
+ userName: String,
+ repositoryName: String,
+ issueId: Int,
+ commentId: Int,
+ action: String,
+ commentedUserName: String,
+ content: String,
+ registeredDate: java.util.Date,
+ updatedDate: java.util.Date
)
\ No newline at end of file
diff --git a/src/main/scala/model/package.scala b/src/main/scala/model/package.scala
index 3280c35..f80d679 100644
--- a/src/main/scala/model/package.scala
+++ b/src/main/scala/model/package.scala
@@ -1,20 +1,20 @@
-package object model {
- import scala.slick.driver.BasicDriver.Implicit._
- import scala.slick.lifted.{Column, MappedTypeMapper}
-
- // java.util.Date TypeMapper
- implicit val dateTypeMapper = MappedTypeMapper.base[java.util.Date, java.sql.Timestamp](
- d => new java.sql.Timestamp(d.getTime),
- t => new java.util.Date(t.getTime)
- )
-
- implicit class RichColumn(c1: Column[Boolean]){
- def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
- }
-
- /**
- * Returns system date.
- */
- def currentDate = new java.util.Date()
-
+package object model {
+ import scala.slick.driver.BasicDriver.Implicit._
+ import scala.slick.lifted.{Column, MappedTypeMapper}
+
+ // java.util.Date TypeMapper
+ implicit val dateTypeMapper = MappedTypeMapper.base[java.util.Date, java.sql.Timestamp](
+ d => new java.sql.Timestamp(d.getTime),
+ t => new java.util.Date(t.getTime)
+ )
+
+ implicit class RichColumn(c1: Column[Boolean]){
+ def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
+ }
+
+ /**
+ * Returns system date.
+ */
+ def currentDate = new java.util.Date()
+
}
\ No newline at end of file
diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala
index 590e300..ea8df53 100644
--- a/src/main/scala/service/IssuesService.scala
+++ b/src/main/scala/service/IssuesService.scala
@@ -1,380 +1,380 @@
-package service
-
-import scala.slick.driver.H2Driver.simple._
-import Database.threadLocalSession
-import scala.slick.jdbc.{StaticQuery => Q}
-import Q.interpolation
-
-import model._
-import util.Implicits._
-import util.StringUtil._
-
-trait IssuesService {
- import IssuesService._
-
- def getIssue(owner: String, repository: String, issueId: String) =
- if (issueId forall (_.isDigit))
- Query(Issues) filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
- else None
-
- def getComments(owner: String, repository: String, issueId: Int) =
- Query(IssueComments) filter (_.byIssue(owner, repository, issueId)) list
-
- def getComment(owner: String, repository: String, commentId: String) =
- if (commentId forall (_.isDigit))
- Query(IssueComments) filter { t =>
- t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
- } firstOption
- else None
-
- def getIssueLabels(owner: String, repository: String, issueId: Int) =
- IssueLabels
- .innerJoin(Labels).on { (t1, t2) =>
- t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
- }
- .filter ( _._1.byIssue(owner, repository, issueId) )
- .map ( _._2 )
- .list
-
- def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
- Query(IssueLabels) filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
-
- /**
- * Returns the count of the search result against issues.
- *
- * @param condition the search condition
- * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
- * @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
- * @param repos Tuple of the repository owner and the repository name
- * @return the count of the search result
- */
- def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
- repos: (String, String)*): Int =
- Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
- /**
- * Returns the Map which contains issue count for each labels.
- *
- * @param owner the repository owner
- * @param repository the repository name
- * @param condition the search condition
- * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
- * @return the Map which contains issue count for each labels (key is label name, value is issue count)
- */
- def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
- filterUser: Map[String, String]): Map[String, Int] = {
-
- searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
- .innerJoin(IssueLabels).on { (t1, t2) =>
- t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
- }
- .innerJoin(Labels).on { case ((t1, t2), t3) =>
- t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
- }
- .groupBy { case ((t1, t2), t3) =>
- t3.labelName
- }
- .map { case (labelName, t) =>
- labelName ~ t.length
- }
- .toMap
- }
- /**
- * Returns list which contains issue count for each repository.
- * If the issue does not exist, its repository is not included in the result.
- *
- * @param condition the search condition
- * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
- * @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
- * @param repos Tuple of the repository owner and the repository name
- * @return list which contains issue count for each repository
- */
- def countIssueGroupByRepository(
- condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
- repos: (String, String)*): List[(String, String, Int)] = {
- searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
- .groupBy { t =>
- t.userName ~ t.repositoryName
- }
- .map { case (repo, t) =>
- repo ~ t.length
- }
- .sortBy(_._3 desc)
- .list
- }
-
- /**
- * Returns the search result against issues.
- *
- * @param condition the search condition
- * @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name)
- * @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
- * @param offset the offset for pagination
- * @param limit the limit for pagination
- * @param repos Tuple of the repository owner and the repository name
- * @return the search result (list of tuples which contain issue, labels and comment count)
- */
- def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
- offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = {
-
- // get issues and comment count and labels
- searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
- .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
- .sortBy { case (t1, t2) =>
- (condition.sort match {
- case "created" => t1.registeredDate
- case "comments" => t2.commentCount
- case "updated" => t1.updatedDate
- }) match {
- case sort => condition.direction match {
- case "asc" => sort asc
- case "desc" => sort desc
- }
- }
- }
- .drop(offset).take(limit)
- .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
- .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
- .map { case (((t1, t2), t3), t4) =>
- (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
- }
- .list
- .splitWith { (c1, c2) =>
- c1._1.userName == c2._1.userName &&
- c1._1.repositoryName == c2._1.repositoryName &&
- c1._1.issueId == c2._1.issueId
- }
- .map { issues => issues.head match {
- case (issue, commentCount, _,_,_) =>
- (issue,
- issues.flatMap { t => t._3.map (
- Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
- )} toList,
- commentCount)
- }} toList
- }
-
- /**
- * Assembles query for conditional issue searching.
- */
- private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
- filterUser: Map[String, String], onlyPullRequest: Boolean) =
- Query(Issues) filter { t1 =>
- condition.repo
- .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
- .getOrElse (repos)
- .map { case (owner, repository) => t1.byRepository(owner, repository) }
- .foldLeft[Column[Boolean]](false) ( _ || _ ) &&
- (t1.closed is (condition.state == "closed").bind) &&
- (t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
- (t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
- (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
- (t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
- (t1.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
- (t1.pullRequest is true.bind, onlyPullRequest) &&
- (IssueLabels filter { t2 =>
- (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
- (t2.labelId in
- (Labels filter { t3 =>
- (t3.byRepository(t1.userName, t1.repositoryName)) &&
- (t3.labelName inSetBind condition.labels)
- } map(_.labelId)))
- } exists, condition.labels.nonEmpty)
- }
-
- def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
- assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false) =
- // next id number
- sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
- .firstOption.filter { id =>
- Issues insert Issue(
- owner,
- repository,
- id,
- loginUser,
- milestoneId,
- assignedUserName,
- title,
- content,
- false,
- currentDate,
- currentDate,
- isPullRequest)
-
- // increment issue id
- IssueId
- .filter (_.byPrimaryKey(owner, repository))
- .map (_.issueId)
- .update (id) > 0
- } get
-
- def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
- IssueLabels insert (IssueLabel(owner, repository, issueId, labelId))
-
- def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
- IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
-
- def createComment(owner: String, repository: String, loginUser: String,
- issueId: Int, content: String, action: String) =
- IssueComments.autoInc insert (
- owner,
- repository,
- issueId,
- action,
- loginUser,
- content,
- currentDate,
- currentDate)
-
- def updateIssue(owner: String, repository: String, issueId: Int,
- title: String, content: Option[String]) =
- Issues
- .filter (_.byPrimaryKey(owner, repository, issueId))
- .map { t =>
- t.title ~ t.content.? ~ t.updatedDate
- }
- .update (title, content, currentDate)
-
- def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String]) =
- Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
-
- def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int]) =
- Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
-
- def updateComment(commentId: Int, content: String) =
- IssueComments
- .filter (_.byPrimaryKey(commentId))
- .map { t =>
- t.content ~ t.updatedDate
- }
- .update (content, currentDate)
-
- def deleteComment(commentId: Int) =
- IssueComments filter (_.byPrimaryKey(commentId)) delete
-
- def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean) =
- Issues
- .filter (_.byPrimaryKey(owner, repository, issueId))
- .map { t =>
- t.closed ~ t.updatedDate
- }
- .update (closed, currentDate)
-
- /**
- * Search issues by keyword.
- *
- * @param owner the repository owner
- * @param repository the repository name
- * @param query the keywords separated by whitespace.
- * @return issues with comment count and matched content of issue or comment
- */
- def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
- import scala.slick.driver.H2Driver.likeEncode
- val keywords = splitWords(query.toLowerCase)
-
- // Search Issue
- val issues = Issues
- .innerJoin(IssueOutline).on { case (t1, t2) =>
- t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
- }
- .filter { case (t1, t2) =>
- keywords.map { keyword =>
- (t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
- (t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
- } .reduceLeft(_ && _)
- }
- .map { case (t1, t2) =>
- (t1, 0, t1.content.?, t2.commentCount)
- }
-
- // Search IssueComment
- val comments = IssueComments
- .innerJoin(Issues).on { case (t1, t2) =>
- t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
- }
- .innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
- t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
- }
- .filter { case ((t1, t2), t3) =>
- keywords.map { query =>
- t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
- }.reduceLeft(_ && _)
- }
- .map { case ((t1, t2), t3) =>
- (t2, t1.commentId, t1.content.?, t3.commentCount)
- }
-
- issues.union(comments).sortBy { case (issue, commentId, _, _) =>
- issue.issueId ~ commentId
- }.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
- issue1.issueId == issue2.issueId
- }.map { _.head match {
- case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
- }
- }.toList
- }
-
- def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = {
- extractCloseId(message).foreach { issueId =>
- for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
- createComment(owner, repository, userName, issue.issueId, "Close", "close")
- updateClosed(owner, repository, issue.issueId, true)
- }
- }
- }
-}
-
-object IssuesService {
- import javax.servlet.http.HttpServletRequest
-
- val IssueLimit = 30
-
- case class IssueSearchCondition(
- labels: Set[String] = Set.empty,
- milestoneId: Option[Option[Int]] = None,
- repo: Option[String] = None,
- state: String = "open",
- sort: String = "created",
- direction: String = "desc"){
-
- def toURL: String =
- "?" + List(
- if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
- milestoneId.map { id => "milestone=" + (id match {
- case Some(x) => x.toString
- case None => "none"
- })},
- repo.map("for=" + urlEncode(_)),
- Some("state=" + urlEncode(state)),
- Some("sort=" + urlEncode(sort)),
- Some("direction=" + urlEncode(direction))).flatten.mkString("&")
-
- }
-
- object IssueSearchCondition {
-
- private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
- val value = request.getParameter(name)
- if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
- }
-
- def apply(request: HttpServletRequest): IssueSearchCondition =
- IssueSearchCondition(
- param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
- param(request, "milestone").map{
- case "none" => None
- case x => x.toIntOpt
- },
- param(request, "for"),
- param(request, "state", Seq("open", "closed")).getOrElse("open"),
- param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
- param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
-
- def page(request: HttpServletRequest) = try {
- val i = param(request, "page").getOrElse("1").toInt
- if(i <= 0) 1 else i
- } catch {
- case e: NumberFormatException => 1
- }
- }
-
-}
+package service
+
+import scala.slick.driver.H2Driver.simple._
+import Database.threadLocalSession
+import scala.slick.jdbc.{StaticQuery => Q}
+import Q.interpolation
+
+import model._
+import util.Implicits._
+import util.StringUtil._
+
+trait IssuesService {
+ import IssuesService._
+
+ def getIssue(owner: String, repository: String, issueId: String) =
+ if (issueId forall (_.isDigit))
+ Query(Issues) filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
+ else None
+
+ def getComments(owner: String, repository: String, issueId: Int) =
+ Query(IssueComments) filter (_.byIssue(owner, repository, issueId)) list
+
+ def getComment(owner: String, repository: String, commentId: String) =
+ if (commentId forall (_.isDigit))
+ Query(IssueComments) filter { t =>
+ t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
+ } firstOption
+ else None
+
+ def getIssueLabels(owner: String, repository: String, issueId: Int) =
+ IssueLabels
+ .innerJoin(Labels).on { (t1, t2) =>
+ t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
+ }
+ .filter ( _._1.byIssue(owner, repository, issueId) )
+ .map ( _._2 )
+ .list
+
+ def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
+ Query(IssueLabels) filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
+
+ /**
+ * Returns the count of the search result against issues.
+ *
+ * @param condition the search condition
+ * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
+ * @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
+ * @param repos Tuple of the repository owner and the repository name
+ * @return the count of the search result
+ */
+ def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
+ repos: (String, String)*): Int =
+ Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
+ /**
+ * Returns the Map which contains issue count for each labels.
+ *
+ * @param owner the repository owner
+ * @param repository the repository name
+ * @param condition the search condition
+ * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
+ * @return the Map which contains issue count for each labels (key is label name, value is issue count)
+ */
+ def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
+ filterUser: Map[String, String]): Map[String, Int] = {
+
+ searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
+ .innerJoin(IssueLabels).on { (t1, t2) =>
+ t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
+ }
+ .innerJoin(Labels).on { case ((t1, t2), t3) =>
+ t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
+ }
+ .groupBy { case ((t1, t2), t3) =>
+ t3.labelName
+ }
+ .map { case (labelName, t) =>
+ labelName ~ t.length
+ }
+ .toMap
+ }
+ /**
+ * Returns list which contains issue count for each repository.
+ * If the issue does not exist, its repository is not included in the result.
+ *
+ * @param condition the search condition
+ * @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
+ * @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
+ * @param repos Tuple of the repository owner and the repository name
+ * @return list which contains issue count for each repository
+ */
+ def countIssueGroupByRepository(
+ condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
+ repos: (String, String)*): List[(String, String, Int)] = {
+ searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
+ .groupBy { t =>
+ t.userName ~ t.repositoryName
+ }
+ .map { case (repo, t) =>
+ repo ~ t.length
+ }
+ .sortBy(_._3 desc)
+ .list
+ }
+
+ /**
+ * Returns the search result against issues.
+ *
+ * @param condition the search condition
+ * @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name)
+ * @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
+ * @param offset the offset for pagination
+ * @param limit the limit for pagination
+ * @param repos Tuple of the repository owner and the repository name
+ * @return the search result (list of tuples which contain issue, labels and comment count)
+ */
+ def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
+ offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = {
+
+ // get issues and comment count and labels
+ searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
+ .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
+ .sortBy { case (t1, t2) =>
+ (condition.sort match {
+ case "created" => t1.registeredDate
+ case "comments" => t2.commentCount
+ case "updated" => t1.updatedDate
+ }) match {
+ case sort => condition.direction match {
+ case "asc" => sort asc
+ case "desc" => sort desc
+ }
+ }
+ }
+ .drop(offset).take(limit)
+ .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
+ .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
+ .map { case (((t1, t2), t3), t4) =>
+ (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
+ }
+ .list
+ .splitWith { (c1, c2) =>
+ c1._1.userName == c2._1.userName &&
+ c1._1.repositoryName == c2._1.repositoryName &&
+ c1._1.issueId == c2._1.issueId
+ }
+ .map { issues => issues.head match {
+ case (issue, commentCount, _,_,_) =>
+ (issue,
+ issues.flatMap { t => t._3.map (
+ Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
+ )} toList,
+ commentCount)
+ }} toList
+ }
+
+ /**
+ * Assembles query for conditional issue searching.
+ */
+ private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
+ filterUser: Map[String, String], onlyPullRequest: Boolean) =
+ Query(Issues) filter { t1 =>
+ condition.repo
+ .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
+ .getOrElse (repos)
+ .map { case (owner, repository) => t1.byRepository(owner, repository) }
+ .foldLeft[Column[Boolean]](false) ( _ || _ ) &&
+ (t1.closed is (condition.state == "closed").bind) &&
+ (t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
+ (t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
+ (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
+ (t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
+ (t1.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
+ (t1.pullRequest is true.bind, onlyPullRequest) &&
+ (IssueLabels filter { t2 =>
+ (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
+ (t2.labelId in
+ (Labels filter { t3 =>
+ (t3.byRepository(t1.userName, t1.repositoryName)) &&
+ (t3.labelName inSetBind condition.labels)
+ } map(_.labelId)))
+ } exists, condition.labels.nonEmpty)
+ }
+
+ def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
+ assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false) =
+ // next id number
+ sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
+ .firstOption.filter { id =>
+ Issues insert Issue(
+ owner,
+ repository,
+ id,
+ loginUser,
+ milestoneId,
+ assignedUserName,
+ title,
+ content,
+ false,
+ currentDate,
+ currentDate,
+ isPullRequest)
+
+ // increment issue id
+ IssueId
+ .filter (_.byPrimaryKey(owner, repository))
+ .map (_.issueId)
+ .update (id) > 0
+ } get
+
+ def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
+ IssueLabels insert (IssueLabel(owner, repository, issueId, labelId))
+
+ def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
+ IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
+
+ def createComment(owner: String, repository: String, loginUser: String,
+ issueId: Int, content: String, action: String) =
+ IssueComments.autoInc insert (
+ owner,
+ repository,
+ issueId,
+ action,
+ loginUser,
+ content,
+ currentDate,
+ currentDate)
+
+ def updateIssue(owner: String, repository: String, issueId: Int,
+ title: String, content: Option[String]) =
+ Issues
+ .filter (_.byPrimaryKey(owner, repository, issueId))
+ .map { t =>
+ t.title ~ t.content.? ~ t.updatedDate
+ }
+ .update (title, content, currentDate)
+
+ def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String]) =
+ Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
+
+ def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int]) =
+ Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
+
+ def updateComment(commentId: Int, content: String) =
+ IssueComments
+ .filter (_.byPrimaryKey(commentId))
+ .map { t =>
+ t.content ~ t.updatedDate
+ }
+ .update (content, currentDate)
+
+ def deleteComment(commentId: Int) =
+ IssueComments filter (_.byPrimaryKey(commentId)) delete
+
+ def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean) =
+ Issues
+ .filter (_.byPrimaryKey(owner, repository, issueId))
+ .map { t =>
+ t.closed ~ t.updatedDate
+ }
+ .update (closed, currentDate)
+
+ /**
+ * Search issues by keyword.
+ *
+ * @param owner the repository owner
+ * @param repository the repository name
+ * @param query the keywords separated by whitespace.
+ * @return issues with comment count and matched content of issue or comment
+ */
+ def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
+ import scala.slick.driver.H2Driver.likeEncode
+ val keywords = splitWords(query.toLowerCase)
+
+ // Search Issue
+ val issues = Issues
+ .innerJoin(IssueOutline).on { case (t1, t2) =>
+ t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
+ }
+ .filter { case (t1, t2) =>
+ keywords.map { keyword =>
+ (t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
+ (t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
+ } .reduceLeft(_ && _)
+ }
+ .map { case (t1, t2) =>
+ (t1, 0, t1.content.?, t2.commentCount)
+ }
+
+ // Search IssueComment
+ val comments = IssueComments
+ .innerJoin(Issues).on { case (t1, t2) =>
+ t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
+ }
+ .innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
+ t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
+ }
+ .filter { case ((t1, t2), t3) =>
+ keywords.map { query =>
+ t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
+ }.reduceLeft(_ && _)
+ }
+ .map { case ((t1, t2), t3) =>
+ (t2, t1.commentId, t1.content.?, t3.commentCount)
+ }
+
+ issues.union(comments).sortBy { case (issue, commentId, _, _) =>
+ issue.issueId ~ commentId
+ }.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
+ issue1.issueId == issue2.issueId
+ }.map { _.head match {
+ case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
+ }
+ }.toList
+ }
+
+ def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = {
+ extractCloseId(message).foreach { issueId =>
+ for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
+ createComment(owner, repository, userName, issue.issueId, "Close", "close")
+ updateClosed(owner, repository, issue.issueId, true)
+ }
+ }
+ }
+}
+
+object IssuesService {
+ import javax.servlet.http.HttpServletRequest
+
+ val IssueLimit = 30
+
+ case class IssueSearchCondition(
+ labels: Set[String] = Set.empty,
+ milestoneId: Option[Option[Int]] = None,
+ repo: Option[String] = None,
+ state: String = "open",
+ sort: String = "created",
+ direction: String = "desc"){
+
+ def toURL: String =
+ "?" + List(
+ if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
+ milestoneId.map { id => "milestone=" + (id match {
+ case Some(x) => x.toString
+ case None => "none"
+ })},
+ repo.map("for=" + urlEncode(_)),
+ Some("state=" + urlEncode(state)),
+ Some("sort=" + urlEncode(sort)),
+ Some("direction=" + urlEncode(direction))).flatten.mkString("&")
+
+ }
+
+ object IssueSearchCondition {
+
+ private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
+ val value = request.getParameter(name)
+ if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
+ }
+
+ def apply(request: HttpServletRequest): IssueSearchCondition =
+ IssueSearchCondition(
+ param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
+ param(request, "milestone").map{
+ case "none" => None
+ case x => x.toIntOpt
+ },
+ param(request, "for"),
+ param(request, "state", Seq("open", "closed")).getOrElse("open"),
+ param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
+ param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
+
+ def page(request: HttpServletRequest) = try {
+ val i = param(request, "page").getOrElse("1").toInt
+ if(i <= 0) 1 else i
+ } catch {
+ case e: NumberFormatException => 1
+ }
+ }
+
+}
diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala
index fcceecb..42f3a75 100644
--- a/src/main/scala/service/SystemSettingsService.scala
+++ b/src/main/scala/service/SystemSettingsService.scala
@@ -1,190 +1,190 @@
-package service
-
-import util.Directory._
-import util.ControlUtil._
-import SystemSettingsService._
-import javax.servlet.http.HttpServletRequest
-
-trait SystemSettingsService {
-
- def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
-
- def saveSystemSettings(settings: SystemSettings): Unit = {
- defining(new java.util.Properties()){ props =>
- settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
- props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
- props.setProperty(Gravatar, settings.gravatar.toString)
- props.setProperty(Notification, settings.notification.toString)
- props.setProperty(Ssh, settings.ssh.toString)
- settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
- if(settings.notification) {
- settings.smtp.foreach { smtp =>
- props.setProperty(SmtpHost, smtp.host)
- smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
- smtp.user.foreach(props.setProperty(SmtpUser, _))
- smtp.password.foreach(props.setProperty(SmtpPassword, _))
- smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
- smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
- smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
- }
- }
- props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
- if(settings.ldapAuthentication){
- settings.ldap.map { ldap =>
- props.setProperty(LdapHost, ldap.host)
- ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
- ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
- ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
- props.setProperty(LdapBaseDN, ldap.baseDN)
- props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
- ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
- props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
- ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
- ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
- }
- }
- using(new java.io.FileOutputStream(GitBucketConf)){ out =>
- props.store(out, null)
- }
- }
- }
-
-
- def loadSystemSettings(): SystemSettings = {
- defining(new java.util.Properties()){ props =>
- if(GitBucketConf.exists){
- using(new java.io.FileInputStream(GitBucketConf)){ in =>
- props.load(in)
- }
- }
- SystemSettings(
- getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
- getValue(props, AllowAccountRegistration, false),
- getValue(props, Gravatar, true),
- getValue(props, Notification, false),
- getValue(props, Ssh, false),
- getOptionValue(props, SshPort, Some(DefaultSshPort)),
- if(getValue(props, Notification, false)){
- Some(Smtp(
- getValue(props, SmtpHost, ""),
- getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
- getOptionValue(props, SmtpUser, None),
- getOptionValue(props, SmtpPassword, None),
- getOptionValue[Boolean](props, SmtpSsl, None),
- getOptionValue(props, SmtpFromAddress, None),
- getOptionValue(props, SmtpFromName, None)))
- } else {
- None
- },
- getValue(props, LdapAuthentication, false),
- if(getValue(props, LdapAuthentication, false)){
- Some(Ldap(
- getValue(props, LdapHost, ""),
- getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
- getOptionValue(props, LdapBindDN, None),
- getOptionValue(props, LdapBindPassword, None),
- getValue(props, LdapBaseDN, ""),
- getValue(props, LdapUserNameAttribute, ""),
- getOptionValue(props, LdapFullNameAttribute, None),
- getValue(props, LdapMailAddressAttribute, ""),
- getOptionValue[Boolean](props, LdapTls, None),
- getOptionValue(props, LdapKeystore, None)))
- } else {
- None
- }
- )
- }
- }
-
-}
-
-object SystemSettingsService {
- import scala.reflect.ClassTag
-
- case class SystemSettings(
- baseUrl: Option[String],
- allowAccountRegistration: Boolean,
- gravatar: Boolean,
- notification: Boolean,
- ssh: Boolean,
- sshPort: Option[Int],
- smtp: Option[Smtp],
- ldapAuthentication: Boolean,
- ldap: Option[Ldap]){
- def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse {
- defining(request.getRequestURL.toString){ url =>
- url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
- }
- }.stripSuffix("/")
- }
-
- case class Ldap(
- host: String,
- port: Option[Int],
- bindDN: Option[String],
- bindPassword: Option[String],
- baseDN: String,
- userNameAttribute: String,
- fullNameAttribute: Option[String],
- mailAttribute: String,
- tls: Option[Boolean],
- keystore: Option[String])
-
- case class Smtp(
- host: String,
- port: Option[Int],
- user: Option[String],
- password: Option[String],
- ssl: Option[Boolean],
- fromAddress: Option[String],
- fromName: Option[String])
-
- val DefaultSshPort = 29418
- val DefaultSmtpPort = 25
- val DefaultLdapPort = 389
-
- private val BaseURL = "base_url"
- private val AllowAccountRegistration = "allow_account_registration"
- private val Gravatar = "gravatar"
- private val Notification = "notification"
- private val Ssh = "ssh"
- private val SshPort = "ssh.port"
- private val SmtpHost = "smtp.host"
- private val SmtpPort = "smtp.port"
- private val SmtpUser = "smtp.user"
- private val SmtpPassword = "smtp.password"
- private val SmtpSsl = "smtp.ssl"
- private val SmtpFromAddress = "smtp.from_address"
- private val SmtpFromName = "smtp.from_name"
- private val LdapAuthentication = "ldap_authentication"
- private val LdapHost = "ldap.host"
- private val LdapPort = "ldap.port"
- private val LdapBindDN = "ldap.bindDN"
- private val LdapBindPassword = "ldap.bind_password"
- private val LdapBaseDN = "ldap.baseDN"
- private val LdapUserNameAttribute = "ldap.username_attribute"
- private val LdapFullNameAttribute = "ldap.fullname_attribute"
- private val LdapMailAddressAttribute = "ldap.mail_attribute"
- private val LdapTls = "ldap.tls"
- private val LdapKeystore = "ldap.keystore"
-
- private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
- defining(props.getProperty(key)){ value =>
- if(value == null || value.isEmpty) default
- else convertType(value).asInstanceOf[A]
- }
-
- private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
- defining(props.getProperty(key)){ value =>
- if(value == null || value.isEmpty) default
- else Some(convertType(value)).asInstanceOf[Option[A]]
- }
-
- private def convertType[A: ClassTag](value: String) =
- defining(implicitly[ClassTag[A]].runtimeClass){ c =>
- if(c == classOf[Boolean]) value.toBoolean
- else if(c == classOf[Int]) value.toInt
- else value
- }
-
-}
+package service
+
+import util.Directory._
+import util.ControlUtil._
+import SystemSettingsService._
+import javax.servlet.http.HttpServletRequest
+
+trait SystemSettingsService {
+
+ def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
+
+ def saveSystemSettings(settings: SystemSettings): Unit = {
+ defining(new java.util.Properties()){ props =>
+ settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
+ props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
+ props.setProperty(Gravatar, settings.gravatar.toString)
+ props.setProperty(Notification, settings.notification.toString)
+ props.setProperty(Ssh, settings.ssh.toString)
+ settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
+ if(settings.notification) {
+ settings.smtp.foreach { smtp =>
+ props.setProperty(SmtpHost, smtp.host)
+ smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
+ smtp.user.foreach(props.setProperty(SmtpUser, _))
+ smtp.password.foreach(props.setProperty(SmtpPassword, _))
+ smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
+ smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
+ smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
+ }
+ }
+ props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
+ if(settings.ldapAuthentication){
+ settings.ldap.map { ldap =>
+ props.setProperty(LdapHost, ldap.host)
+ ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
+ ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
+ ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
+ props.setProperty(LdapBaseDN, ldap.baseDN)
+ props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
+ ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
+ props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
+ ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
+ ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
+ }
+ }
+ using(new java.io.FileOutputStream(GitBucketConf)){ out =>
+ props.store(out, null)
+ }
+ }
+ }
+
+
+ def loadSystemSettings(): SystemSettings = {
+ defining(new java.util.Properties()){ props =>
+ if(GitBucketConf.exists){
+ using(new java.io.FileInputStream(GitBucketConf)){ in =>
+ props.load(in)
+ }
+ }
+ SystemSettings(
+ getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
+ getValue(props, AllowAccountRegistration, false),
+ getValue(props, Gravatar, true),
+ getValue(props, Notification, false),
+ getValue(props, Ssh, false),
+ getOptionValue(props, SshPort, Some(DefaultSshPort)),
+ if(getValue(props, Notification, false)){
+ Some(Smtp(
+ getValue(props, SmtpHost, ""),
+ getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
+ getOptionValue(props, SmtpUser, None),
+ getOptionValue(props, SmtpPassword, None),
+ getOptionValue[Boolean](props, SmtpSsl, None),
+ getOptionValue(props, SmtpFromAddress, None),
+ getOptionValue(props, SmtpFromName, None)))
+ } else {
+ None
+ },
+ getValue(props, LdapAuthentication, false),
+ if(getValue(props, LdapAuthentication, false)){
+ Some(Ldap(
+ getValue(props, LdapHost, ""),
+ getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
+ getOptionValue(props, LdapBindDN, None),
+ getOptionValue(props, LdapBindPassword, None),
+ getValue(props, LdapBaseDN, ""),
+ getValue(props, LdapUserNameAttribute, ""),
+ getOptionValue(props, LdapFullNameAttribute, None),
+ getValue(props, LdapMailAddressAttribute, ""),
+ getOptionValue[Boolean](props, LdapTls, None),
+ getOptionValue(props, LdapKeystore, None)))
+ } else {
+ None
+ }
+ )
+ }
+ }
+
+}
+
+object SystemSettingsService {
+ import scala.reflect.ClassTag
+
+ case class SystemSettings(
+ baseUrl: Option[String],
+ allowAccountRegistration: Boolean,
+ gravatar: Boolean,
+ notification: Boolean,
+ ssh: Boolean,
+ sshPort: Option[Int],
+ smtp: Option[Smtp],
+ ldapAuthentication: Boolean,
+ ldap: Option[Ldap]){
+ def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse {
+ defining(request.getRequestURL.toString){ url =>
+ url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
+ }
+ }.stripSuffix("/")
+ }
+
+ case class Ldap(
+ host: String,
+ port: Option[Int],
+ bindDN: Option[String],
+ bindPassword: Option[String],
+ baseDN: String,
+ userNameAttribute: String,
+ fullNameAttribute: Option[String],
+ mailAttribute: String,
+ tls: Option[Boolean],
+ keystore: Option[String])
+
+ case class Smtp(
+ host: String,
+ port: Option[Int],
+ user: Option[String],
+ password: Option[String],
+ ssl: Option[Boolean],
+ fromAddress: Option[String],
+ fromName: Option[String])
+
+ val DefaultSshPort = 29418
+ val DefaultSmtpPort = 25
+ val DefaultLdapPort = 389
+
+ private val BaseURL = "base_url"
+ private val AllowAccountRegistration = "allow_account_registration"
+ private val Gravatar = "gravatar"
+ private val Notification = "notification"
+ private val Ssh = "ssh"
+ private val SshPort = "ssh.port"
+ private val SmtpHost = "smtp.host"
+ private val SmtpPort = "smtp.port"
+ private val SmtpUser = "smtp.user"
+ private val SmtpPassword = "smtp.password"
+ private val SmtpSsl = "smtp.ssl"
+ private val SmtpFromAddress = "smtp.from_address"
+ private val SmtpFromName = "smtp.from_name"
+ private val LdapAuthentication = "ldap_authentication"
+ private val LdapHost = "ldap.host"
+ private val LdapPort = "ldap.port"
+ private val LdapBindDN = "ldap.bindDN"
+ private val LdapBindPassword = "ldap.bind_password"
+ private val LdapBaseDN = "ldap.baseDN"
+ private val LdapUserNameAttribute = "ldap.username_attribute"
+ private val LdapFullNameAttribute = "ldap.fullname_attribute"
+ private val LdapMailAddressAttribute = "ldap.mail_attribute"
+ private val LdapTls = "ldap.tls"
+ private val LdapKeystore = "ldap.keystore"
+
+ private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
+ defining(props.getProperty(key)){ value =>
+ if(value == null || value.isEmpty) default
+ else convertType(value).asInstanceOf[A]
+ }
+
+ private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
+ defining(props.getProperty(key)){ value =>
+ if(value == null || value.isEmpty) default
+ else Some(convertType(value)).asInstanceOf[Option[A]]
+ }
+
+ private def convertType[A: ClassTag](value: String) =
+ defining(implicitly[ClassTag[A]].runtimeClass){ c =>
+ if(c == classOf[Boolean]) value.toBoolean
+ else if(c == classOf[Int]) value.toInt
+ else value
+ }
+
+}
diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala
index 7f5f908..4016a6e 100644
--- a/src/main/scala/service/WikiService.scala
+++ b/src/main/scala/service/WikiService.scala
@@ -1,282 +1,282 @@
-package service
-
-import java.util.Date
-import org.eclipse.jgit.api.Git
-import org.apache.commons.io.FileUtils
-import util._
-import _root_.util.ControlUtil._
-import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser}
-import org.eclipse.jgit.lib._
-import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry}
-import org.eclipse.jgit.revwalk.RevWalk
-import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
-import java.io.ByteArrayInputStream
-import org.eclipse.jgit.patch._
-import org.eclipse.jgit.api.errors.PatchFormatException
-import scala.collection.JavaConverters._
-import scala.Some
-import service.RepositoryService.RepositoryInfo
-
-
-object WikiService {
-
- /**
- * The model for wiki page.
- *
- * @param name the page name
- * @param content the page content
- * @param committer the last committer
- * @param time the last modified time
- * @param id the latest commit id
- */
- case class WikiPageInfo(name: String, content: String, committer: String, time: Date, id: String)
-
- /**
- * The model for wiki page history.
- *
- * @param name the page name
- * @param committer the committer the committer
- * @param message the commit message
- * @param date the commit date
- */
- case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
-
- def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git")
-
- def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) =
- repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git")
-}
-
-trait WikiService {
- import WikiService._
-
- def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit =
- LockUtil.lock(s"${owner}/${repository}/wiki"){
- defining(Directory.getWikiRepositoryDir(owner, repository)){ dir =>
- if(!dir.exists){
- JGitUtil.initRepository(dir)
- saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None)
- }
- }
- }
-
- /**
- * Returns the wiki page.
- */
- def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
- using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
- if(!JGitUtil.isEmpty(git)){
- JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
- WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
- file.committer, file.time, file.commitId)
- }
- } else None
- }
- }
-
- /**
- * Returns the content of the specified file.
- */
- def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] =
- using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
- if(!JGitUtil.isEmpty(git)){
- val index = path.lastIndexOf('/')
- val parentPath = if(index < 0) "." else path.substring(0, index)
- val fileName = if(index < 0) path else path.substring(index + 1)
-
- JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
- git.getRepository.open(file.id).getBytes
- }
- } else None
- }
-
- /**
- * Returns the list of wiki page names.
- */
- def getWikiPageList(owner: String, repository: String): List[String] = {
- using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
- JGitUtil.getFileList(git, "master", ".")
- .filter(_.name.endsWith(".md"))
- .map(_.name.stripSuffix(".md"))
- .sortBy(x => x)
- }
- }
-
- /**
- * Reverts specified changes.
- */
- def revertWikiPage(owner: String, repository: String, from: String, to: String,
- committer: model.Account, pageName: Option[String]): Boolean = {
-
- case class RevertInfo(operation: String, filePath: String, source: String)
-
- try {
- LockUtil.lock(s"${owner}/${repository}/wiki"){
- using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
-
- val reader = git.getRepository.newObjectReader
- val oldTreeIter = new CanonicalTreeParser
- oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
-
- val newTreeIter = new CanonicalTreeParser
- newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}"))
-
- val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff =>
- pageName match {
- case Some(x) => diff.getNewPath == x + ".md"
- case None => true
- }
- }
-
- val patch = using(new java.io.ByteArrayOutputStream()){ out =>
- val formatter = new DiffFormatter(out)
- formatter.setRepository(git.getRepository)
- formatter.format(diffs.asJava)
- new String(out.toByteArray, "UTF-8")
- }
-
- val p = new Patch()
- p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8")))
- if(!p.getErrors.isEmpty){
- throw new PatchFormatException(p.getErrors())
- }
- val revertInfo = (p.getFiles.asScala.map { fh =>
- fh.getChangeType match {
- case DiffEntry.ChangeType.MODIFY => {
- val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
- val applied = PatchUtil.apply(source, patch, fh)
- if(applied != null){
- Seq(RevertInfo("ADD", fh.getNewPath, applied))
- } else Nil
- }
- case DiffEntry.ChangeType.ADD => {
- val applied = PatchUtil.apply("", patch, fh)
- if(applied != null){
- Seq(RevertInfo("ADD", fh.getNewPath, applied))
- } else Nil
- }
- case DiffEntry.ChangeType.DELETE => {
- Seq(RevertInfo("DELETE", fh.getNewPath, ""))
- }
- case DiffEntry.ChangeType.RENAME => {
- val applied = PatchUtil.apply("", patch, fh)
- if(applied != null){
- Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied))
- } else {
- Seq(RevertInfo("DELETE", fh.getOldPath, ""))
- }
- }
- case _ => Nil
- }
- }).flatten
-
- if(revertInfo.nonEmpty){
- val builder = DirCache.newInCore.builder()
- val inserter = git.getRepository.newObjectInserter()
- val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
-
- JGitUtil.processTree(git, headId){ (path, tree) =>
- if(revertInfo.find(x => x.filePath == path).isEmpty){
- builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
- }
- }
-
- revertInfo.filter(_.operation == "ADD").foreach { x =>
- builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8"))))
- }
- builder.finish()
-
- JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
- pageName match {
- case Some(x) => s"Revert ${from} ... ${to} on ${x}"
- case None => s"Revert ${from} ... ${to}"
- })
- }
- }
- }
- true
- } catch {
- case e: Exception => {
- e.printStackTrace()
- false
- }
- }
- }
-
- /**
- * Save the wiki page.
- */
- def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
- content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = {
- LockUtil.lock(s"${owner}/${repository}/wiki"){
- using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
- val builder = DirCache.newInCore.builder()
- val inserter = git.getRepository.newObjectInserter()
- val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
- var created = true
- var updated = false
- var removed = false
-
- if(headId != null){
- JGitUtil.processTree(git, headId){ (path, tree) =>
- if(path == currentPageName + ".md" && currentPageName != newPageName){
- removed = true
- } else if(path != newPageName + ".md"){
- builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
- } else {
- created = false
- updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false)
- }
- }
- }
-
- if(created || updated || removed){
- builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
- builder.finish()
- val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
- if(message.trim.length == 0) {
- if(removed){
- s"Rename ${currentPageName} to ${newPageName}"
- } else if(created){
- s"Created ${newPageName}"
- } else {
- s"Updated ${newPageName}"
- }
- } else {
- message
- })
-
- Some(newHeadId.getName)
- } else None
- }
- }
- }
-
- /**
- * Delete the wiki page.
- */
- def deleteWikiPage(owner: String, repository: String, pageName: String,
- committer: String, mailAddress: String, message: String): Unit = {
- LockUtil.lock(s"${owner}/${repository}/wiki"){
- using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
- val builder = DirCache.newInCore.builder()
- val inserter = git.getRepository.newObjectInserter()
- val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
- var removed = false
-
- JGitUtil.processTree(git, headId){ (path, tree) =>
- if(path != pageName + ".md"){
- builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
- } else {
- removed = true
- }
- }
- if(removed){
- builder.finish()
- JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message)
- }
- }
- }
- }
-
-}
+package service
+
+import java.util.Date
+import org.eclipse.jgit.api.Git
+import org.apache.commons.io.FileUtils
+import util._
+import _root_.util.ControlUtil._
+import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser}
+import org.eclipse.jgit.lib._
+import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry}
+import org.eclipse.jgit.revwalk.RevWalk
+import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
+import java.io.ByteArrayInputStream
+import org.eclipse.jgit.patch._
+import org.eclipse.jgit.api.errors.PatchFormatException
+import scala.collection.JavaConverters._
+import scala.Some
+import service.RepositoryService.RepositoryInfo
+
+
+object WikiService {
+
+ /**
+ * The model for wiki page.
+ *
+ * @param name the page name
+ * @param content the page content
+ * @param committer the last committer
+ * @param time the last modified time
+ * @param id the latest commit id
+ */
+ case class WikiPageInfo(name: String, content: String, committer: String, time: Date, id: String)
+
+ /**
+ * The model for wiki page history.
+ *
+ * @param name the page name
+ * @param committer the committer the committer
+ * @param message the commit message
+ * @param date the commit date
+ */
+ case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
+
+ def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git")
+
+ def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) =
+ repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git")
+}
+
+trait WikiService {
+ import WikiService._
+
+ def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit =
+ LockUtil.lock(s"${owner}/${repository}/wiki"){
+ defining(Directory.getWikiRepositoryDir(owner, repository)){ dir =>
+ if(!dir.exists){
+ JGitUtil.initRepository(dir)
+ saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None)
+ }
+ }
+ }
+
+ /**
+ * Returns the wiki page.
+ */
+ def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
+ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
+ if(!JGitUtil.isEmpty(git)){
+ JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
+ WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
+ file.committer, file.time, file.commitId)
+ }
+ } else None
+ }
+ }
+
+ /**
+ * Returns the content of the specified file.
+ */
+ def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] =
+ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
+ if(!JGitUtil.isEmpty(git)){
+ val index = path.lastIndexOf('/')
+ val parentPath = if(index < 0) "." else path.substring(0, index)
+ val fileName = if(index < 0) path else path.substring(index + 1)
+
+ JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
+ git.getRepository.open(file.id).getBytes
+ }
+ } else None
+ }
+
+ /**
+ * Returns the list of wiki page names.
+ */
+ def getWikiPageList(owner: String, repository: String): List[String] = {
+ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
+ JGitUtil.getFileList(git, "master", ".")
+ .filter(_.name.endsWith(".md"))
+ .map(_.name.stripSuffix(".md"))
+ .sortBy(x => x)
+ }
+ }
+
+ /**
+ * Reverts specified changes.
+ */
+ def revertWikiPage(owner: String, repository: String, from: String, to: String,
+ committer: model.Account, pageName: Option[String]): Boolean = {
+
+ case class RevertInfo(operation: String, filePath: String, source: String)
+
+ try {
+ LockUtil.lock(s"${owner}/${repository}/wiki"){
+ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
+
+ val reader = git.getRepository.newObjectReader
+ val oldTreeIter = new CanonicalTreeParser
+ oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
+
+ val newTreeIter = new CanonicalTreeParser
+ newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}"))
+
+ val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff =>
+ pageName match {
+ case Some(x) => diff.getNewPath == x + ".md"
+ case None => true
+ }
+ }
+
+ val patch = using(new java.io.ByteArrayOutputStream()){ out =>
+ val formatter = new DiffFormatter(out)
+ formatter.setRepository(git.getRepository)
+ formatter.format(diffs.asJava)
+ new String(out.toByteArray, "UTF-8")
+ }
+
+ val p = new Patch()
+ p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8")))
+ if(!p.getErrors.isEmpty){
+ throw new PatchFormatException(p.getErrors())
+ }
+ val revertInfo = (p.getFiles.asScala.map { fh =>
+ fh.getChangeType match {
+ case DiffEntry.ChangeType.MODIFY => {
+ val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
+ val applied = PatchUtil.apply(source, patch, fh)
+ if(applied != null){
+ Seq(RevertInfo("ADD", fh.getNewPath, applied))
+ } else Nil
+ }
+ case DiffEntry.ChangeType.ADD => {
+ val applied = PatchUtil.apply("", patch, fh)
+ if(applied != null){
+ Seq(RevertInfo("ADD", fh.getNewPath, applied))
+ } else Nil
+ }
+ case DiffEntry.ChangeType.DELETE => {
+ Seq(RevertInfo("DELETE", fh.getNewPath, ""))
+ }
+ case DiffEntry.ChangeType.RENAME => {
+ val applied = PatchUtil.apply("", patch, fh)
+ if(applied != null){
+ Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied))
+ } else {
+ Seq(RevertInfo("DELETE", fh.getOldPath, ""))
+ }
+ }
+ case _ => Nil
+ }
+ }).flatten
+
+ if(revertInfo.nonEmpty){
+ val builder = DirCache.newInCore.builder()
+ val inserter = git.getRepository.newObjectInserter()
+ val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
+
+ JGitUtil.processTree(git, headId){ (path, tree) =>
+ if(revertInfo.find(x => x.filePath == path).isEmpty){
+ builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
+ }
+ }
+
+ revertInfo.filter(_.operation == "ADD").foreach { x =>
+ builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8"))))
+ }
+ builder.finish()
+
+ JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
+ pageName match {
+ case Some(x) => s"Revert ${from} ... ${to} on ${x}"
+ case None => s"Revert ${from} ... ${to}"
+ })
+ }
+ }
+ }
+ true
+ } catch {
+ case e: Exception => {
+ e.printStackTrace()
+ false
+ }
+ }
+ }
+
+ /**
+ * Save the wiki page.
+ */
+ def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
+ content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = {
+ LockUtil.lock(s"${owner}/${repository}/wiki"){
+ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
+ val builder = DirCache.newInCore.builder()
+ val inserter = git.getRepository.newObjectInserter()
+ val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
+ var created = true
+ var updated = false
+ var removed = false
+
+ if(headId != null){
+ JGitUtil.processTree(git, headId){ (path, tree) =>
+ if(path == currentPageName + ".md" && currentPageName != newPageName){
+ removed = true
+ } else if(path != newPageName + ".md"){
+ builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
+ } else {
+ created = false
+ updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false)
+ }
+ }
+ }
+
+ if(created || updated || removed){
+ builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
+ builder.finish()
+ val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
+ if(message.trim.length == 0) {
+ if(removed){
+ s"Rename ${currentPageName} to ${newPageName}"
+ } else if(created){
+ s"Created ${newPageName}"
+ } else {
+ s"Updated ${newPageName}"
+ }
+ } else {
+ message
+ })
+
+ Some(newHeadId.getName)
+ } else None
+ }
+ }
+ }
+
+ /**
+ * Delete the wiki page.
+ */
+ def deleteWikiPage(owner: String, repository: String, pageName: String,
+ committer: String, mailAddress: String, message: String): Unit = {
+ LockUtil.lock(s"${owner}/${repository}/wiki"){
+ using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
+ val builder = DirCache.newInCore.builder()
+ val inserter = git.getRepository.newObjectInserter()
+ val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
+ var removed = false
+
+ JGitUtil.processTree(git, headId){ (path, tree) =>
+ if(path != pageName + ".md"){
+ builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
+ } else {
+ removed = true
+ }
+ }
+ if(removed){
+ builder.finish()
+ JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message)
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala
index 801ff68..f47c489 100644
--- a/src/main/scala/servlet/AutoUpdateListener.scala
+++ b/src/main/scala/servlet/AutoUpdateListener.scala
@@ -1,193 +1,193 @@
-package servlet
-
-import java.io.File
-import java.sql.{DriverManager, Connection}
-import org.apache.commons.io.FileUtils
-import javax.servlet.{ServletContext, ServletContextListener, ServletContextEvent}
-import org.apache.commons.io.IOUtils
-import org.slf4j.LoggerFactory
-import util.Directory._
-import util.ControlUtil._
-import org.eclipse.jgit.api.Git
-import util.Directory
-
-object AutoUpdate {
-
- /**
- * Version of GitBucket
- *
- * @param majorVersion the major version
- * @param minorVersion the minor version
- */
- case class Version(majorVersion: Int, minorVersion: Int){
-
- private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version])
-
- /**
- * Execute update/MAJOR_MINOR.sql to update schema to this version.
- * If corresponding SQL file does not exist, this method do nothing.
- */
- def update(conn: Connection): Unit = {
- val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
-
- using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
- if(in != null){
- val sql = IOUtils.toString(in, "UTF-8")
- using(conn.createStatement()){ stmt =>
- logger.debug(sqlPath + "=" + sql)
- stmt.executeUpdate(sql)
- }
- }
- }
- }
-
- /**
- * MAJOR.MINOR
- */
- val versionString = s"${majorVersion}.${minorVersion}"
- }
-
- /**
- * The history of versions. A head of this sequence is the current BitBucket version.
- */
- val versions = Seq(
- new Version(2, 0){
- override def update(conn: Connection): Unit = {
- import eu.medsea.mimeutil.{MimeUtil2, MimeType}
-
- val mimeUtil = new MimeUtil2()
- mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
-
- super.update(conn)
- using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
- while(rs.next){
- defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
- if(dir.exists && dir.isDirectory){
- dir.listFiles.foreach { file =>
- if(file.getName.indexOf('.') < 0){
- val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
- if(mimeType.startsWith("image/")){
- file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
- }
- }
- }
- }
- }
- }
- }
- }
- },
- Version(1, 13),
- Version(1, 12),
- Version(1, 11),
- Version(1, 10),
- Version(1, 9),
- Version(1, 8),
- Version(1, 7),
- Version(1, 6),
- Version(1, 5),
- Version(1, 4),
- new Version(1, 3){
- override def update(conn: Connection): Unit = {
- super.update(conn)
- // Fix wiki repository configuration
- using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
- while(rs.next){
- using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
- defining(git.getRepository.getConfig){ config =>
- if(!config.getBoolean("http", "receivepack", false)){
- config.setBoolean("http", null, "receivepack", true)
- config.save
- }
- }
- }
- }
- }
- }
- },
- Version(1, 2),
- Version(1, 1),
- Version(1, 0),
- Version(0, 0)
- )
-
- /**
- * The head version of BitBucket.
- */
- val headVersion = versions.head
-
- /**
- * The version file (GITBUCKET_HOME/version).
- */
- lazy val versionFile = new File(GitBucketHome, "version")
-
- /**
- * Returns the current version from the version file.
- */
- def getCurrentVersion(): Version = {
- if(versionFile.exists){
- FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
- case Array(majorVersion, minorVersion) => {
- versions.find { v =>
- v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
- }.getOrElse(Version(0, 0))
- }
- case _ => Version(0, 0)
- }
- } else Version(0, 0)
- }
-
-}
-
-/**
- * Update database schema automatically in the context initializing.
- */
-class AutoUpdateListener extends ServletContextListener {
- import AutoUpdate._
- private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
-
- override def contextInitialized(event: ServletContextEvent): Unit = {
- val datadir = event.getServletContext.getInitParameter("gitbucket.home")
- if(datadir != null){
- System.setProperty("gitbucket.home", datadir)
- }
- org.h2.Driver.load()
- event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true")
-
- logger.debug("Start schema update")
- defining(getConnection(event.getServletContext)){ conn =>
- try {
- defining(getCurrentVersion()){ currentVersion =>
- if(currentVersion == headVersion){
- logger.debug("No update")
- } else if(!versions.contains(currentVersion)){
- logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
- } else {
- versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
- FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
- conn.commit()
- logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
- }
- }
- } catch {
- case ex: Throwable => {
- logger.error("Failed to schema update", ex)
- ex.printStackTrace()
- conn.rollback()
- }
- }
- }
- logger.debug("End schema update")
- }
-
- def contextDestroyed(sce: ServletContextEvent): Unit = {
- // Nothing to do.
- }
-
- private def getConnection(servletContext: ServletContext): Connection =
- DriverManager.getConnection(
- servletContext.getInitParameter("db.url"),
- servletContext.getInitParameter("db.user"),
- servletContext.getInitParameter("db.password"))
-
-}
+package servlet
+
+import java.io.File
+import java.sql.{DriverManager, Connection}
+import org.apache.commons.io.FileUtils
+import javax.servlet.{ServletContext, ServletContextListener, ServletContextEvent}
+import org.apache.commons.io.IOUtils
+import org.slf4j.LoggerFactory
+import util.Directory._
+import util.ControlUtil._
+import org.eclipse.jgit.api.Git
+import util.Directory
+
+object AutoUpdate {
+
+ /**
+ * Version of GitBucket
+ *
+ * @param majorVersion the major version
+ * @param minorVersion the minor version
+ */
+ case class Version(majorVersion: Int, minorVersion: Int){
+
+ private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version])
+
+ /**
+ * Execute update/MAJOR_MINOR.sql to update schema to this version.
+ * If corresponding SQL file does not exist, this method do nothing.
+ */
+ def update(conn: Connection): Unit = {
+ val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
+
+ using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
+ if(in != null){
+ val sql = IOUtils.toString(in, "UTF-8")
+ using(conn.createStatement()){ stmt =>
+ logger.debug(sqlPath + "=" + sql)
+ stmt.executeUpdate(sql)
+ }
+ }
+ }
+ }
+
+ /**
+ * MAJOR.MINOR
+ */
+ val versionString = s"${majorVersion}.${minorVersion}"
+ }
+
+ /**
+ * The history of versions. A head of this sequence is the current BitBucket version.
+ */
+ val versions = Seq(
+ new Version(2, 0){
+ override def update(conn: Connection): Unit = {
+ import eu.medsea.mimeutil.{MimeUtil2, MimeType}
+
+ val mimeUtil = new MimeUtil2()
+ mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
+
+ super.update(conn)
+ using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
+ while(rs.next){
+ defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
+ if(dir.exists && dir.isDirectory){
+ dir.listFiles.foreach { file =>
+ if(file.getName.indexOf('.') < 0){
+ val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
+ if(mimeType.startsWith("image/")){
+ file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ Version(1, 13),
+ Version(1, 12),
+ Version(1, 11),
+ Version(1, 10),
+ Version(1, 9),
+ Version(1, 8),
+ Version(1, 7),
+ Version(1, 6),
+ Version(1, 5),
+ Version(1, 4),
+ new Version(1, 3){
+ override def update(conn: Connection): Unit = {
+ super.update(conn)
+ // Fix wiki repository configuration
+ using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
+ while(rs.next){
+ using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
+ defining(git.getRepository.getConfig){ config =>
+ if(!config.getBoolean("http", "receivepack", false)){
+ config.setBoolean("http", null, "receivepack", true)
+ config.save
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ Version(1, 2),
+ Version(1, 1),
+ Version(1, 0),
+ Version(0, 0)
+ )
+
+ /**
+ * The head version of BitBucket.
+ */
+ val headVersion = versions.head
+
+ /**
+ * The version file (GITBUCKET_HOME/version).
+ */
+ lazy val versionFile = new File(GitBucketHome, "version")
+
+ /**
+ * Returns the current version from the version file.
+ */
+ def getCurrentVersion(): Version = {
+ if(versionFile.exists){
+ FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
+ case Array(majorVersion, minorVersion) => {
+ versions.find { v =>
+ v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
+ }.getOrElse(Version(0, 0))
+ }
+ case _ => Version(0, 0)
+ }
+ } else Version(0, 0)
+ }
+
+}
+
+/**
+ * Update database schema automatically in the context initializing.
+ */
+class AutoUpdateListener extends ServletContextListener {
+ import AutoUpdate._
+ private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
+
+ override def contextInitialized(event: ServletContextEvent): Unit = {
+ val datadir = event.getServletContext.getInitParameter("gitbucket.home")
+ if(datadir != null){
+ System.setProperty("gitbucket.home", datadir)
+ }
+ org.h2.Driver.load()
+ event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true")
+
+ logger.debug("Start schema update")
+ defining(getConnection(event.getServletContext)){ conn =>
+ try {
+ defining(getCurrentVersion()){ currentVersion =>
+ if(currentVersion == headVersion){
+ logger.debug("No update")
+ } else if(!versions.contains(currentVersion)){
+ logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
+ } else {
+ versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
+ FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
+ conn.commit()
+ logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
+ }
+ }
+ } catch {
+ case ex: Throwable => {
+ logger.error("Failed to schema update", ex)
+ ex.printStackTrace()
+ conn.rollback()
+ }
+ }
+ }
+ logger.debug("End schema update")
+ }
+
+ def contextDestroyed(sce: ServletContextEvent): Unit = {
+ // Nothing to do.
+ }
+
+ private def getConnection(servletContext: ServletContext): Connection =
+ DriverManager.getConnection(
+ servletContext.getInitParameter("db.url"),
+ servletContext.getInitParameter("db.user"),
+ servletContext.getInitParameter("db.password"))
+
+}
diff --git a/src/main/scala/servlet/TransactionFilter.scala b/src/main/scala/servlet/TransactionFilter.scala
index 12363ad..f62e61a 100644
--- a/src/main/scala/servlet/TransactionFilter.scala
+++ b/src/main/scala/servlet/TransactionFilter.scala
@@ -1,38 +1,38 @@
-package servlet
-
-import javax.servlet._
-import org.slf4j.LoggerFactory
-import javax.servlet.http.HttpServletRequest
-
-/**
- * Controls the transaction with the open session in view pattern.
- */
-class TransactionFilter extends Filter {
-
- private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
-
- def init(config: FilterConfig) = {}
-
- def destroy(): Unit = {}
-
- def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
- if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
- // assets don't need transaction
- chain.doFilter(req, res)
- } else {
- Database(req.getServletContext) withTransaction {
- logger.debug("begin transaction")
- chain.doFilter(req, res)
- logger.debug("end transaction")
- }
- }
- }
-
-}
-
-object Database {
- def apply(context: ServletContext): scala.slick.session.Database =
- scala.slick.session.Database.forURL(context.getInitParameter("db.url"),
- context.getInitParameter("db.user"),
- context.getInitParameter("db.password"))
-}
+package servlet
+
+import javax.servlet._
+import org.slf4j.LoggerFactory
+import javax.servlet.http.HttpServletRequest
+
+/**
+ * Controls the transaction with the open session in view pattern.
+ */
+class TransactionFilter extends Filter {
+
+ private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
+
+ def init(config: FilterConfig) = {}
+
+ def destroy(): Unit = {}
+
+ def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
+ if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
+ // assets don't need transaction
+ chain.doFilter(req, res)
+ } else {
+ Database(req.getServletContext) withTransaction {
+ logger.debug("begin transaction")
+ chain.doFilter(req, res)
+ logger.debug("end transaction")
+ }
+ }
+ }
+
+}
+
+object Database {
+ def apply(context: ServletContext): scala.slick.session.Database =
+ scala.slick.session.Database.forURL(context.getInitParameter("db.url"),
+ context.getInitParameter("db.user"),
+ context.getInitParameter("db.password"))
+}
diff --git a/src/main/scala/util/Notifier.scala b/src/main/scala/util/Notifier.scala
index 21dfce6..304f440 100644
--- a/src/main/scala/util/Notifier.scala
+++ b/src/main/scala/util/Notifier.scala
@@ -1,116 +1,116 @@
-package util
-
-import scala.concurrent._
-import ExecutionContext.Implicits.global
-import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
-import org.slf4j.LoggerFactory
-
-import app.Context
-import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
-import servlet.Database
-import SystemSettingsService.Smtp
-import _root_.util.ControlUtil.defining
-
-trait Notifier extends RepositoryService with AccountService with IssuesService {
- def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
- (msg: String => String)(implicit context: Context): Unit
-
- protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit context: Context) =
- (
- // individual repository's owner
- issue.userName ::
- // collaborators
- getCollaborators(issue.userName, issue.repositoryName) :::
- // participants
- issue.openedUserName ::
- getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
- )
- .distinct
- .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
- .foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
-
-}
-
-object Notifier {
- // TODO We want to be able to switch to mock.
- def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
- case settings if settings.notification => new Mailer(settings.smtp.get)
- case _ => new MockMailer
- }
-
- def msgIssue(url: String) = (content: String) => s"""
- |${content}
- |--
- |View it on GitBucket
- """.stripMargin
-
- def msgPullRequest(url: String) = (content: String) => s"""
- |${content}
- |View, comment on, or merge it at:
- |${url}
- """.stripMargin
-
- def msgComment(url: String) = (content: String) => s"""
- |${content}
- |--
- |View it on GitBucket
- """.stripMargin
-
- def msgStatus(url: String) = (content: String) => s"""
- |${content} #${url split('/') last}
- """.stripMargin
-}
-
-class Mailer(private val smtp: Smtp) extends Notifier {
- private val logger = LoggerFactory.getLogger(classOf[Mailer])
-
- def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
- (msg: String => String)(implicit context: Context) = {
- val database = Database(context.request.getServletContext)
-
- val f = future {
- // TODO Can we use the Database Session in other than Transaction Filter?
- database withSession {
- getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
- defining(
- s"[${r.name}] ${issue.title} (#${issueId})" ->
- msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
- recipients(issue) { to =>
- val email = new HtmlEmail
- email.setHostName(smtp.host)
- email.setSmtpPort(smtp.port.get)
- smtp.user.foreach { user =>
- email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
- }
- smtp.ssl.foreach { ssl =>
- email.setSSLOnConnect(ssl)
- }
- smtp.fromAddress
- .map (_ -> smtp.fromName.orNull)
- .orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
- .foreach { case (address, name) =>
- email.setFrom(address, name)
- }
- email.setCharset("UTF-8")
- email.setSubject(subject)
- email.setHtmlMsg(msg)
-
- email.addTo(to).send
- }
- }
- }
- }
- "Notifications Successful."
- }
- f onSuccess {
- case s => logger.debug(s)
- }
- f onFailure {
- case t => logger.error("Notifications Failed.", t)
- }
- }
-}
-class MockMailer extends Notifier {
- def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
- (msg: String => String)(implicit context: Context): Unit = {}
+package util
+
+import scala.concurrent._
+import ExecutionContext.Implicits.global
+import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
+import org.slf4j.LoggerFactory
+
+import app.Context
+import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
+import servlet.Database
+import SystemSettingsService.Smtp
+import _root_.util.ControlUtil.defining
+
+trait Notifier extends RepositoryService with AccountService with IssuesService {
+ def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
+ (msg: String => String)(implicit context: Context): Unit
+
+ protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit context: Context) =
+ (
+ // individual repository's owner
+ issue.userName ::
+ // collaborators
+ getCollaborators(issue.userName, issue.repositoryName) :::
+ // participants
+ issue.openedUserName ::
+ getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
+ )
+ .distinct
+ .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
+ .foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
+
+}
+
+object Notifier {
+ // TODO We want to be able to switch to mock.
+ def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
+ case settings if settings.notification => new Mailer(settings.smtp.get)
+ case _ => new MockMailer
+ }
+
+ def msgIssue(url: String) = (content: String) => s"""
+ |${content}
+ |--
+ |View it on GitBucket
+ """.stripMargin
+
+ def msgPullRequest(url: String) = (content: String) => s"""
+ |${content}
+ |View, comment on, or merge it at:
+ |${url}
+ """.stripMargin
+
+ def msgComment(url: String) = (content: String) => s"""
+ |${content}
+ |--
+ |View it on GitBucket
+ """.stripMargin
+
+ def msgStatus(url: String) = (content: String) => s"""
+ |${content} #${url split('/') last}
+ """.stripMargin
+}
+
+class Mailer(private val smtp: Smtp) extends Notifier {
+ private val logger = LoggerFactory.getLogger(classOf[Mailer])
+
+ def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
+ (msg: String => String)(implicit context: Context) = {
+ val database = Database(context.request.getServletContext)
+
+ val f = future {
+ // TODO Can we use the Database Session in other than Transaction Filter?
+ database withSession {
+ getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
+ defining(
+ s"[${r.name}] ${issue.title} (#${issueId})" ->
+ msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
+ recipients(issue) { to =>
+ val email = new HtmlEmail
+ email.setHostName(smtp.host)
+ email.setSmtpPort(smtp.port.get)
+ smtp.user.foreach { user =>
+ email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
+ }
+ smtp.ssl.foreach { ssl =>
+ email.setSSLOnConnect(ssl)
+ }
+ smtp.fromAddress
+ .map (_ -> smtp.fromName.orNull)
+ .orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
+ .foreach { case (address, name) =>
+ email.setFrom(address, name)
+ }
+ email.setCharset("UTF-8")
+ email.setSubject(subject)
+ email.setHtmlMsg(msg)
+
+ email.addTo(to).send
+ }
+ }
+ }
+ }
+ "Notifications Successful."
+ }
+ f onSuccess {
+ case s => logger.debug(s)
+ }
+ f onFailure {
+ case t => logger.error("Notifications Failed.", t)
+ }
+ }
+}
+class MockMailer extends Notifier {
+ def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
+ (msg: String => String)(implicit context: Context): Unit = {}
}
\ No newline at end of file
diff --git a/src/main/twirl/admin/users/list.scala.html b/src/main/twirl/admin/users/list.scala.html
index 942f6ee..3b2029c 100644
--- a/src/main/twirl/admin/users/list.scala.html
+++ b/src/main/twirl/admin/users/list.scala.html
@@ -1,71 +1,71 @@
-@(users: List[model.Account], members: Map[String, List[String]], includeRemoved: Boolean)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@html.main("Manage Users"){
- @admin.html.menu("users"){
-
-
-
- @users.map { account =>
-
-
-
- @if(account.isGroupAccount){
- Edit
- } else {
- Edit
- }
-
-
- @avatar(account.userName, 20)
- @account.userName
- @if(account.isGroupAccount){
- (Group)
- } else {
- @if(account.isAdmin){
- (Administrator)
- } else {
- (Normal)
- }
- }
- @if(account.isGroupAccount){
- @members(account.userName).map { userName =>
- @avatar(userName, 20, tooltip = true)
- }
- }
-
-
-
- @if(!account.isGroupAccount){
- @account.mailAddress
- }
- @account.url.map { url =>
- @url
- }
-
-
- Registered: @datetime(account.registeredDate)
- Updated: @datetime(account.updatedDate)
- @if(!account.isGroupAccount){
- Last Login: @account.lastLoginDate.map(datetime)
- }
-
- |
-
- }
-
- }
-}
-
\ No newline at end of file
diff --git a/src/main/twirl/admin/users/user.scala.html b/src/main/twirl/admin/users/user.scala.html
index 3a2430c..fb022c0 100644
--- a/src/main/twirl/admin/users/user.scala.html
+++ b/src/main/twirl/admin/users/user.scala.html
@@ -1,80 +1,80 @@
-@(account: Option[model.Account])(implicit context: app.Context)
-@import context._
-@html.main(if(account.isEmpty) "New User" else "Update User"){
- @admin.html.menu("users"){
-
- }
-}
+@(account: Option[model.Account])(implicit context: app.Context)
+@import context._
+@html.main(if(account.isEmpty) "New User" else "Update User"){
+ @admin.html.menu("users"){
+
+ }
+}
diff --git a/src/main/twirl/helper/activities.scala.html b/src/main/twirl/helper/activities.scala.html
index 6e7f324..980eaf5 100644
--- a/src/main/twirl/helper/activities.scala.html
+++ b/src/main/twirl/helper/activities.scala.html
@@ -1,98 +1,98 @@
-@(activities: List[model.Activity])(implicit context: app.Context)
-@import context._
-@import view.helpers._
-
-@if(activities.isEmpty){
- No activity
-} else {
- @activities.map { activity =>
-
- @(activity.activityType match {
- case "open_issue" => detailActivity(activity, "activity-issue.png")
- case "comment_issue" => detailActivity(activity, "activity-comment.png")
- case "close_issue" => detailActivity(activity, "activity-issue-close.png")
- case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png")
- case "open_pullreq" => detailActivity(activity, "activity-merge.png")
- case "merge_pullreq" => detailActivity(activity, "activity-merge.png")
- case "create_repository" => simpleActivity(activity, "activity-create-repository.png")
- case "create_branch" => simpleActivity(activity, "activity-branch.png")
- case "delete_branch" => simpleActivity(activity, "activity-delete.png")
- case "create_tag" => simpleActivity(activity, "activity-tag.png")
- case "delete_tag" => simpleActivity(activity, "activity-delete.png")
- case "fork" => simpleActivity(activity, "activity-fork.png")
- case "push" => customActivity(activity, "activity-commit.png"){
-
- {activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
- if(i == 3){
-
...
- } else {
- if(commit.nonEmpty){
-
- }
- }
- }}
-
- }
- case "create_wiki" => customActivity(activity, "activity-wiki.png"){
-
- }
- case "edit_wiki" => customActivity(activity, "activity-wiki.png"){
- activity.additionalInfo.get.split(":") match {
- case Array(pageName, commitId) =>
-
- case Array(pageName) =>
-
- }
- }
- })
-
- }
-}
-
-@detailActivity(activity: model.Activity, image: String) = {
- 
-
-
@datetime(activity.activityDate)
-
- @avatar(activity.activityUserName, 16)
- @activityMessage(activity.message)
-
- @activity.additionalInfo.map { additionalInfo =>
-
@additionalInfo
- }
-
-}
-
-@customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = {
-
-
-
@datetime(activity.activityDate)
-
- @avatar(activity.activityUserName, 16)
- @activityMessage(activity.message)
-
- @additionalInfo
-
-}
-
-@simpleActivity(activity: model.Activity, image: String) = {
-
-
-
- @avatar(activity.activityUserName, 16)
- @activityMessage(activity.message)
- @datetime(activity.activityDate)
-
-
-}
-
+@(activities: List[model.Activity])(implicit context: app.Context)
+@import context._
+@import view.helpers._
+
+@if(activities.isEmpty){
+ No activity
+} else {
+ @activities.map { activity =>
+
+ @(activity.activityType match {
+ case "open_issue" => detailActivity(activity, "activity-issue.png")
+ case "comment_issue" => detailActivity(activity, "activity-comment.png")
+ case "close_issue" => detailActivity(activity, "activity-issue-close.png")
+ case "reopen_issue" => detailActivity(activity, "activity-issue-reopen.png")
+ case "open_pullreq" => detailActivity(activity, "activity-merge.png")
+ case "merge_pullreq" => detailActivity(activity, "activity-merge.png")
+ case "create_repository" => simpleActivity(activity, "activity-create-repository.png")
+ case "create_branch" => simpleActivity(activity, "activity-branch.png")
+ case "delete_branch" => simpleActivity(activity, "activity-delete.png")
+ case "create_tag" => simpleActivity(activity, "activity-tag.png")
+ case "delete_tag" => simpleActivity(activity, "activity-delete.png")
+ case "fork" => simpleActivity(activity, "activity-fork.png")
+ case "push" => customActivity(activity, "activity-commit.png"){
+
+ {activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
+ if(i == 3){
+
...
+ } else {
+ if(commit.nonEmpty){
+
+ }
+ }
+ }}
+
+ }
+ case "create_wiki" => customActivity(activity, "activity-wiki.png"){
+
+ }
+ case "edit_wiki" => customActivity(activity, "activity-wiki.png"){
+ activity.additionalInfo.get.split(":") match {
+ case Array(pageName, commitId) =>
+
+ case Array(pageName) =>
+
+ }
+ }
+ })
+
+ }
+}
+
+@detailActivity(activity: model.Activity, image: String) = {
+ 
+
+
@datetime(activity.activityDate)
+
+ @avatar(activity.activityUserName, 16)
+ @activityMessage(activity.message)
+
+ @activity.additionalInfo.map { additionalInfo =>
+
@additionalInfo
+ }
+
+}
+
+@customActivity(activity: model.Activity, image: String)(additionalInfo: Any) = {
+
+
+
@datetime(activity.activityDate)
+
+ @avatar(activity.activityUserName, 16)
+ @activityMessage(activity.message)
+
+ @additionalInfo
+
+}
+
+@simpleActivity(activity: model.Activity, image: String) = {
+
+
+
+ @avatar(activity.activityUserName, 16)
+ @activityMessage(activity.message)
+ @datetime(activity.activityDate)
+
+
+}
+
diff --git a/src/main/twirl/helper/diff.scala.html b/src/main/twirl/helper/diff.scala.html
index 9b9e25a..e1ab2d7 100644
--- a/src/main/twirl/helper/diff.scala.html
+++ b/src/main/twirl/helper/diff.scala.html
@@ -1,105 +1,105 @@
-@(diffs: Seq[util.JGitUtil.DiffInfo],
- repository: service.RepositoryService.RepositoryInfo,
- newCommitId: Option[String],
- oldCommitId: Option[String],
- showIndex: Boolean)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@import org.eclipse.jgit.diff.DiffEntry.ChangeType
-@if(showIndex){
-
-
-
-
- Showing @diffs.size changed @plural(diffs.size, "file")
-
-
-}
-@diffs.zipWithIndex.map { case (diff, i) =>
-
-
-
-
-
-
-
- @if(diff.newContent != None || diff.oldContent != None){
-
-
-
- } else {
- Not supported
- }
- |
-
-
-}
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/src/main/twirl/index.scala.html b/src/main/twirl/index.scala.html
index d019510..ec8b7ce 100644
--- a/src/main/twirl/index.scala.html
+++ b/src/main/twirl/index.scala.html
@@ -1,70 +1,70 @@
-@(activities: List[model.Activity],
- recentRepositories: List[service.RepositoryService.RepositoryInfo],
- userRepositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@main("GitBucket"){
-
- @dashboard.html.tab()
-
-
- @helper.html.activities(activities)
-
-
- @if(loginAccount.isEmpty){
- @signinform(settings)
- } else {
-
-
-
-
- Your repositories (@userRepositories.size)
- |
-
- @if(userRepositories.isEmpty){
-
- No repositories |
-
- } else {
- @userRepositories.map { repository =>
-
-
- @helper.html.repositoryicon(repository, false)
- @if(repository.owner == loginAccount.get.userName){
- @repository.name
- } else {
- @repository.owner/@repository.name
- }
- |
-
- }
- }
-
- }
-
-
-
- Recent updated repositories
- |
-
- @if(recentRepositories.isEmpty){
-
- No repositories |
-
- } else {
- @recentRepositories.map { repository =>
-
-
- @helper.html.repositoryicon(repository, false)
- @repository.owner/@repository.name
- |
-
- }
- }
-
-
-
-
-}
+@(activities: List[model.Activity],
+ recentRepositories: List[service.RepositoryService.RepositoryInfo],
+ userRepositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@main("GitBucket"){
+
+ @dashboard.html.tab()
+
+
+ @helper.html.activities(activities)
+
+
+ @if(loginAccount.isEmpty){
+ @signinform(settings)
+ } else {
+
+
+
+
+ Your repositories (@userRepositories.size)
+ |
+
+ @if(userRepositories.isEmpty){
+
+ No repositories |
+
+ } else {
+ @userRepositories.map { repository =>
+
+
+ @helper.html.repositoryicon(repository, false)
+ @if(repository.owner == loginAccount.get.userName){
+ @repository.name
+ } else {
+ @repository.owner/@repository.name
+ }
+ |
+
+ }
+ }
+
+ }
+
+
+
+ Recent updated repositories
+ |
+
+ @if(recentRepositories.isEmpty){
+
+ No repositories |
+
+ } else {
+ @recentRepositories.map { repository =>
+
+
+ @helper.html.repositoryicon(repository, false)
+ @repository.owner/@repository.name
+ |
+
+ }
+ }
+
+
+
+
+}
diff --git a/src/main/twirl/issues/create.scala.html b/src/main/twirl/issues/create.scala.html
index dcffc7e..95c5091 100644
--- a/src/main/twirl/issues/create.scala.html
+++ b/src/main/twirl/issues/create.scala.html
@@ -1,147 +1,147 @@
-@(collaborators: List[String],
- milestones: List[model.Milestone],
- labels: List[model.Label],
- hasWritePermission: Boolean,
- repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
- @html.menu("issues", repository){
- @tab("", true, repository)
-
- }
-}
-
+@(collaborators: List[String],
+ milestones: List[model.Milestone],
+ labels: List[model.Label],
+ hasWritePermission: Boolean,
+ repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
+ @html.menu("issues", repository){
+ @tab("", true, repository)
+
+ }
+}
+
diff --git a/src/main/twirl/pulls/commits.scala.html b/src/main/twirl/pulls/commits.scala.html
index 9ee31e2..68636d5 100644
--- a/src/main/twirl/pulls/commits.scala.html
+++ b/src/main/twirl/pulls/commits.scala.html
@@ -1,25 +1,25 @@
-@(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
- repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-
-
- @commits.map { day =>
-
-
-
- @day.map { commit =>
-
-
- @avatar(commit, 20)
- @user(commit.committer, commit.mailAddress, "username")
- |
- @commit.shortMessage |
-
- @commit.id.substring(0, 7)
- |
-
- }
- }
-
-
+@(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
+ repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
+@import context._
+@import view.helpers._
+
+
+ @commits.map { day =>
+
+
+
+ @day.map { commit =>
+
+
+ @avatar(commit, 20)
+ @user(commit.committer, commit.mailAddress, "username")
+ |
+ @commit.shortMessage |
+
+ @commit.id.substring(0, 7)
+ |
+
+ }
+ }
+
+
diff --git a/src/main/twirl/repo/tags.scala.html b/src/main/twirl/repo/tags.scala.html
index 57a8a69..a04da07 100644
--- a/src/main/twirl/repo/tags.scala.html
+++ b/src/main/twirl/repo/tags.scala.html
@@ -1,24 +1,24 @@
-@(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
- @html.menu("code", repository){
- Tags
-
- }
+@(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
+ @html.menu("code", repository){
+ Tags
+
+ }
}
\ No newline at end of file
diff --git a/src/main/twirl/search/menu.scala.html b/src/main/twirl/search/menu.scala.html
index adc9156..c66b6ab 100644
--- a/src/main/twirl/search/menu.scala.html
+++ b/src/main/twirl/search/menu.scala.html
@@ -1,38 +1,38 @@
-@(active: String, fileCount: Int, issueCount: Int, query: String,
- repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@html.menu("", repository){
-
+@(active: String, fileCount: Int, issueCount: Int, query: String,
+ repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@html.menu("", repository){
+
}
\ No newline at end of file
diff --git a/src/main/twirl/settings/collaborators.scala.html b/src/main/twirl/settings/collaborators.scala.html
index 28efeb6..e225153 100644
--- a/src/main/twirl/settings/collaborators.scala.html
+++ b/src/main/twirl/settings/collaborators.scala.html
@@ -1,35 +1,35 @@
-@(collaborators: List[String],
- isGroupRepository: Boolean,
- repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@html.main("Settings", Some(repository)){
- @html.menu("settings", repository){
- @menu("collaborators", repository){
- Manage Collaborators
-
- @collaborators.map { collaboratorName =>
- -
- @collaboratorName
- @if(!isGroupRepository){
- (remove)
- } else {
- @if(repository.managers.contains(collaboratorName)){
- (Manager)
- }
- }
-
- }
-
- @if(!isGroupRepository){
-
- }
- }
- }
-}
+@(collaborators: List[String],
+ isGroupRepository: Boolean,
+ repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@html.main("Settings", Some(repository)){
+ @html.menu("settings", repository){
+ @menu("collaborators", repository){
+ Manage Collaborators
+
+ @collaborators.map { collaboratorName =>
+ -
+ @collaboratorName
+ @if(!isGroupRepository){
+ (remove)
+ } else {
+ @if(repository.managers.contains(collaboratorName)){
+ (Manager)
+ }
+ }
+
+ }
+
+ @if(!isGroupRepository){
+
+ }
+ }
+ }
+}
diff --git a/src/main/twirl/settings/menu.scala.html b/src/main/twirl/settings/menu.scala.html
index 616f4cf..5ce349f 100644
--- a/src/main/twirl/settings/menu.scala.html
+++ b/src/main/twirl/settings/menu.scala.html
@@ -1,26 +1,26 @@
-@(active: String, repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context)
-@import context._
-@import view.helpers._
-
+@(active: String, repository: service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: app.Context)
+@import context._
+@import view.helpers._
+
diff --git a/src/main/twirl/settings/options.scala.html b/src/main/twirl/settings/options.scala.html
index b6949dc..1483b43 100644
--- a/src/main/twirl/settings/options.scala.html
+++ b/src/main/twirl/settings/options.scala.html
@@ -1,100 +1,100 @@
-@(repository: service.RepositoryService.RepositoryInfo, info: Option[Any])(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@html.main("Settings", Some(repository)){
- @html.menu("settings", repository){
- @menu("options", repository){
- @helper.html.information(info)
-
- }
- }
-}
+@(repository: service.RepositoryService.RepositoryInfo, info: Option[Any])(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@html.main("Settings", Some(repository)){
+ @html.menu("settings", repository){
+ @menu("options", repository){
+ @helper.html.information(info)
+
+ }
+ }
+}
diff --git a/src/main/twirl/signin.scala.html b/src/main/twirl/signin.scala.html
index f70e844..c7eec08 100644
--- a/src/main/twirl/signin.scala.html
+++ b/src/main/twirl/signin.scala.html
@@ -1,7 +1,7 @@
-@()(implicit context: app.Context)
-@import context._
-@main("Sign in"){
-
- @signinform(settings)
-
-}
+@()(implicit context: app.Context)
+@import context._
+@main("Sign in"){
+
+ @signinform(settings)
+
+}
diff --git a/src/main/twirl/wiki/compare.scala.html b/src/main/twirl/wiki/compare.scala.html
index 190a824..3143676 100644
--- a/src/main/twirl/wiki/compare.scala.html
+++ b/src/main/twirl/wiki/compare.scala.html
@@ -1,40 +1,40 @@
-@(pageName: Option[String],
- from: String,
- to: String,
- diffs: Seq[util.JGitUtil.DiffInfo],
- repository: service.RepositoryService.RepositoryInfo,
- hasWritePermission: Boolean,
- info: Option[Any])(implicit context: app.Context)
-@import context._
-@import view.helpers._
-@import org.eclipse.jgit.diff.DiffEntry.ChangeType
-@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
- @helper.html.information(info)
- @html.menu("wiki", repository){
-
- -
-
Compare Revisions
-
- -
-
-
-
- @helper.html.diff(diffs, repository, None, None, false)
- @if(hasWritePermission){
-
- }
- }
-}
+@(pageName: Option[String],
+ from: String,
+ to: String,
+ diffs: Seq[util.JGitUtil.DiffInfo],
+ repository: service.RepositoryService.RepositoryInfo,
+ hasWritePermission: Boolean,
+ info: Option[Any])(implicit context: app.Context)
+@import context._
+@import view.helpers._
+@import org.eclipse.jgit.diff.DiffEntry.ChangeType
+@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
+ @helper.html.information(info)
+ @html.menu("wiki", repository){
+
+ -
+
Compare Revisions
+
+ -
+
+
+
+ @helper.html.diff(diffs, repository, None, None, false)
+ @if(hasWritePermission){
+
+ }
+ }
+}
diff --git a/src/main/webapp/assets/common/js/gitbucket.js b/src/main/webapp/assets/common/js/gitbucket.js
index 94de1c4..93e832e 100644
--- a/src/main/webapp/assets/common/js/gitbucket.js
+++ b/src/main/webapp/assets/common/js/gitbucket.js
@@ -1,109 +1,109 @@
-$(function(){
- // disable Ajax cache
- $.ajaxSetup({ cache: false });
-
- // repository url text field
- $('#repository-url').click(function(){
- this.select(0, this.value.length);
- });
-
- // activate tooltip
- $('img[data-toggle=tooltip]').tooltip();
- $('a[data-toggle=tooltip]').tooltip();
-
- // anchor icon for markdown
- $('.markdown-head').mouseenter(function(e){
- $(e.target).children('a.markdown-anchor-link').show();
- });
- $('.markdown-head').mouseleave(function(e){
- var anchorLink = $(e.target).children('a.markdown-anchor-link');
- if(anchorLink.data('active') != true){
- anchorLink.hide();
- }
- });
-
- $('a.markdown-anchor-link').mouseenter(function(e){
- $(e.target).data('active', true);
- });
-
- $('a.markdown-anchor-link').mouseleave(function(e){
- $(e.target).data('active', false);
- $(e.target).hide();
- });
-
- // syntax highlighting by google-code-prettify
- prettyPrint();
-});
-
-function displayErrors(data){
- var i = 0;
- $.each(data, function(key, value){
- $('#error-' + key.split(".").join("_")).text(value);
- if(i == 0){
- $('#' + key).focus();
- }
- i++;
- });
-}
-
-(function($){
- $.fn.watch = function(callback){
- var timer = null;
- var prevValue = this.val();
-
- this.on('focus', function(e){
- window.clearInterval(timer);
- timer = window.setInterval(function(){
- var newValue = $(e.target).val();
- if(prevValue != newValue){
- callback();
- }
- prevValue = newValue;
- }, 10);
- });
-
- this.on('blur', function(){
- window.clearInterval(timer);
- });
- };
-})(jQuery);
-
-function diffUsingJS(oldTextId, newTextId, outputId) {
- // get the baseText and newText values from the two textboxes, and split them into lines
- var oldText = document.getElementById(oldTextId).value;
- if(oldText == ''){
- var oldLines = [];
- } else {
- var oldLines = difflib.stringAsLines(oldText);
- }
-
- var newText = document.getElementById(newTextId).value
- if(newText == ''){
- var newLines = [];
- } else {
- var newLines = difflib.stringAsLines(newText);
- }
-
- // create a SequenceMatcher instance that diffs the two sets of lines
- var sm = new difflib.SequenceMatcher(oldLines, newLines);
-
- // get the opcodes from the SequenceMatcher instance
- // opcodes is a list of 3-tuples describing what changes should be made to the base text
- // in order to yield the new text
- var opcodes = sm.get_opcodes();
- var diffoutputdiv = document.getElementById(outputId);
- while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild);
-
- // build the diff view and add it to the current DOM
- diffoutputdiv.appendChild(diffview.buildView({
- baseTextLines: oldLines,
- newTextLines: newLines,
- opcodes: opcodes,
- contextSize: 4,
- viewType: 1
- }));
-}
-
-function jqSelectorEscape(val) {
- return val.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&');
+$(function(){
+ // disable Ajax cache
+ $.ajaxSetup({ cache: false });
+
+ // repository url text field
+ $('#repository-url').click(function(){
+ this.select(0, this.value.length);
+ });
+
+ // activate tooltip
+ $('img[data-toggle=tooltip]').tooltip();
+ $('a[data-toggle=tooltip]').tooltip();
+
+ // anchor icon for markdown
+ $('.markdown-head').mouseenter(function(e){
+ $(e.target).children('a.markdown-anchor-link').show();
+ });
+ $('.markdown-head').mouseleave(function(e){
+ var anchorLink = $(e.target).children('a.markdown-anchor-link');
+ if(anchorLink.data('active') != true){
+ anchorLink.hide();
+ }
+ });
+
+ $('a.markdown-anchor-link').mouseenter(function(e){
+ $(e.target).data('active', true);
+ });
+
+ $('a.markdown-anchor-link').mouseleave(function(e){
+ $(e.target).data('active', false);
+ $(e.target).hide();
+ });
+
+ // syntax highlighting by google-code-prettify
+ prettyPrint();
+});
+
+function displayErrors(data){
+ var i = 0;
+ $.each(data, function(key, value){
+ $('#error-' + key.split(".").join("_")).text(value);
+ if(i == 0){
+ $('#' + key).focus();
+ }
+ i++;
+ });
+}
+
+(function($){
+ $.fn.watch = function(callback){
+ var timer = null;
+ var prevValue = this.val();
+
+ this.on('focus', function(e){
+ window.clearInterval(timer);
+ timer = window.setInterval(function(){
+ var newValue = $(e.target).val();
+ if(prevValue != newValue){
+ callback();
+ }
+ prevValue = newValue;
+ }, 10);
+ });
+
+ this.on('blur', function(){
+ window.clearInterval(timer);
+ });
+ };
+})(jQuery);
+
+function diffUsingJS(oldTextId, newTextId, outputId) {
+ // get the baseText and newText values from the two textboxes, and split them into lines
+ var oldText = document.getElementById(oldTextId).value;
+ if(oldText == ''){
+ var oldLines = [];
+ } else {
+ var oldLines = difflib.stringAsLines(oldText);
+ }
+
+ var newText = document.getElementById(newTextId).value
+ if(newText == ''){
+ var newLines = [];
+ } else {
+ var newLines = difflib.stringAsLines(newText);
+ }
+
+ // create a SequenceMatcher instance that diffs the two sets of lines
+ var sm = new difflib.SequenceMatcher(oldLines, newLines);
+
+ // get the opcodes from the SequenceMatcher instance
+ // opcodes is a list of 3-tuples describing what changes should be made to the base text
+ // in order to yield the new text
+ var opcodes = sm.get_opcodes();
+ var diffoutputdiv = document.getElementById(outputId);
+ while (diffoutputdiv.firstChild) diffoutputdiv.removeChild(diffoutputdiv.firstChild);
+
+ // build the diff view and add it to the current DOM
+ diffoutputdiv.appendChild(diffview.buildView({
+ baseTextLines: oldLines,
+ newTextLines: newLines,
+ opcodes: opcodes,
+ contextSize: 4,
+ viewType: 1
+ }));
+}
+
+function jqSelectorEscape(val) {
+ return val.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&');
}
\ No newline at end of file
diff --git a/src/main/webapp/assets/common/js/validation.js b/src/main/webapp/assets/common/js/validation.js
index 135fb82..9be17d0 100644
--- a/src/main/webapp/assets/common/js/validation.js
+++ b/src/main/webapp/assets/common/js/validation.js
@@ -1,40 +1,40 @@
-$(function(){
- $.each($('form[validate=true]'), function(i, form){
- $(form).submit(validate);
- });
- $.each($('input[formaction]'), function(i, input){
- $(input).click(function(){
- var form = $(input).parents('form')
- $(form).attr('action', $(input).attr('formaction'))
- });
- });
-});
-
-function validate(e){
- var form = $(e.target);
-
- if(form.data('validated') == true){
- return true;
- }
-
- $.post(form.attr('action') + '/validate', $(e.target).serialize(), function(data){
- // clear all error messages
- $('.error').text('');
-
- if($.isEmptyObject(data)){
- form.data('validated', true);
- form.submit();
- form.data('validated', false);
- } else {
- form.data('validated', false);
- displayErrors(data);
- }
- }, 'json');
- return false;
-}
-
-function displayErrors(data){
- $.each(data, function(key, value){
- $('#error-' + key.split(".").join("_")).text(value);
- });
+$(function(){
+ $.each($('form[validate=true]'), function(i, form){
+ $(form).submit(validate);
+ });
+ $.each($('input[formaction]'), function(i, input){
+ $(input).click(function(){
+ var form = $(input).parents('form')
+ $(form).attr('action', $(input).attr('formaction'))
+ });
+ });
+});
+
+function validate(e){
+ var form = $(e.target);
+
+ if(form.data('validated') == true){
+ return true;
+ }
+
+ $.post(form.attr('action') + '/validate', $(e.target).serialize(), function(data){
+ // clear all error messages
+ $('.error').text('');
+
+ if($.isEmptyObject(data)){
+ form.data('validated', true);
+ form.submit();
+ form.data('validated', false);
+ } else {
+ form.data('validated', false);
+ displayErrors(data);
+ }
+ }, 'json');
+ return false;
+}
+
+function displayErrors(data){
+ $.each(data, function(key, value){
+ $('#error-' + key.split(".").join("_")).text(value);
+ });
}
\ No newline at end of file
diff --git a/src/main/webapp/assets/vendors/ace/mode-diff.js b/src/main/webapp/assets/vendors/ace/mode-diff.js
index 8c919ea..7cfa96e 100644
--- a/src/main/webapp/assets/vendors/ace/mode-diff.js
+++ b/src/main/webapp/assets/vendors/ace/mode-diff.js
Binary files differ
diff --git a/src/main/webapp/assets/vendors/ace/mode-jsoniq.js b/src/main/webapp/assets/vendors/ace/mode-jsoniq.js
index 094a3a6..cc17fee 100644
--- a/src/main/webapp/assets/vendors/ace/mode-jsoniq.js
+++ b/src/main/webapp/assets/vendors/ace/mode-jsoniq.js
Binary files differ
diff --git a/src/main/webapp/assets/vendors/ace/mode-xquery.js b/src/main/webapp/assets/vendors/ace/mode-xquery.js
index a8e2869..b59ae69 100644
--- a/src/main/webapp/assets/vendors/ace/mode-xquery.js
+++ b/src/main/webapp/assets/vendors/ace/mode-xquery.js
Binary files differ
diff --git a/src/main/webapp/assets/vendors/ace/worker-html.js b/src/main/webapp/assets/vendors/ace/worker-html.js
index 7e2fc82..f295b35 100644
--- a/src/main/webapp/assets/vendors/ace/worker-html.js
+++ b/src/main/webapp/assets/vendors/ace/worker-html.js
Binary files differ
diff --git a/src/main/webapp/assets/vendors/ace/worker-xquery.js b/src/main/webapp/assets/vendors/ace/worker-xquery.js
index 31a1c71..457acf0 100644
--- a/src/main/webapp/assets/vendors/ace/worker-xquery.js
+++ b/src/main/webapp/assets/vendors/ace/worker-xquery.js
Binary files differ