diff --git a/src/main/resources/update/1_2.sql b/src/main/resources/update/1_2.sql new file mode 100644 index 0000000..0496caf --- /dev/null +++ b/src/main/resources/update/1_2.sql @@ -0,0 +1,12 @@ +CREATE TABLE ACTIVITY( + ACTIVITY_ID INT AUTO_INCREMENT, + USER_NAME VARCHAR(100) NOT NULL, + REPOSITORY_NAME VARCHAR(100) NOT NULL, + ACTIVITY_USER_NAME VARCHAR(100) NOT NULL, + MESSAGE TEXT NOT NULL, + ACTIVITY_DATE TIMESTAMP NOT NULL +); + +ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_PK PRIMARY KEY (ACTIVITY_ID); +ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME); +ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK1 FOREIGN KEY (ACTIVITY_USER_NAME) REFERENCES ACCOUNT (USER_NAME); diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index 9fa0956..de5ed9c 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -6,10 +6,12 @@ import jp.sf.amateras.scalatra.forms._ class AccountController extends AccountControllerBase - with SystemSettingsService with AccountService with RepositoryService with OneselfAuthenticator + with SystemSettingsService with AccountService with RepositoryService with ActivityService + with OneselfAuthenticator trait AccountControllerBase extends ControllerBase { - self: SystemSettingsService with AccountService with RepositoryService with OneselfAuthenticator => + self: SystemSettingsService with AccountService with RepositoryService with ActivityService + with OneselfAuthenticator => case class AccountNewForm(userName: String, password: String,mailAddress: String, url: Option[String]) @@ -33,8 +35,13 @@ */ get("/:userName") { val userName = params("userName") - getAccountByUserName(userName).map { - account.html.info(_, getVisibleRepositories(userName, baseUrl, context.loginAccount.map(_.userName))) + getAccountByUserName(userName).map { x => + params.getOrElse("tab", "repositories") match { + // Public Activity + case "activity" => account.html.activity(x, getActivitiesByUser(userName, true)) + // Repositories + case _ => account.html.repositories(x, getVisibleRepositories(userName, baseUrl, context.loginAccount.map(_.userName))) + } } getOrElse NotFound } diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateRepositoryController.scala index 55df75a..8d50a95 100644 --- a/src/main/scala/app/CreateRepositoryController.scala +++ b/src/main/scala/app/CreateRepositoryController.scala @@ -10,13 +10,15 @@ import jp.sf.amateras.scalatra.forms._ class CreateRepositoryController extends CreateRepositoryControllerBase - with RepositoryService with AccountService with WikiService with LabelsService with UsersAuthenticator + with RepositoryService with AccountService with WikiService with LabelsService with ActivityService + with UsersAuthenticator /** * Creates new repository. */ trait CreateRepositoryControllerBase extends ControllerBase { - self: RepositoryService with WikiService with LabelsService with UsersAuthenticator => + self: RepositoryService with WikiService with LabelsService with ActivityService + with UsersAuthenticator => case class RepositoryCreationForm(name: String, description: Option[String]) @@ -36,7 +38,8 @@ * Create new repository. */ post("/new", form)(usersOnly { form => - val loginUserName = context.loginAccount.get.userName + val loginAccount = context.loginAccount.get + val loginUserName = loginAccount.userName // Insert to the database at first createRepository(form.name, loginUserName, form.description) @@ -82,7 +85,10 @@ } // Create Wiki repository - createWikiRepository(context.loginAccount.get, form.name) + createWikiRepository(loginAccount, form.name) + + // Record activity + recordCreateRepository(loginUserName, form.name, loginUserName) // redirect to the repository redirect("/%s/%s".format(loginUserName, form.name)) diff --git a/src/main/scala/model/Activity.scala b/src/main/scala/model/Activity.scala new file mode 100644 index 0000000..8177c57 --- /dev/null +++ b/src/main/scala/model/Activity.scala @@ -0,0 +1,21 @@ +package model + +import scala.slick.driver.H2Driver.simple._ + +object Activities extends Table[Activity]("ACTIVITY") with BasicTemplate with Functions { + def activityId = column[Int]("ACTIVITY_ID", O AutoInc) + def activityUserName = column[String]("ACTIVITY_USER_NAME") + def message = column[String]("MESSAGE") + def activityDate = column[java.util.Date]("ACTIVITY_DATE") + def * = activityId ~ userName ~ repositoryName ~ activityUserName ~ message ~ activityDate <> (Activity, Activity.unapply _) + def autoInc = userName ~ repositoryName ~ activityUserName ~ message ~ activityDate returning activityId +} + +case class Activity( + activityId: Int, + userName: String, + repositoryName: String, + activityUserName: String, + message: String, + activityDate: java.util.Date +) diff --git a/src/main/scala/service/ActivityService.scala b/src/main/scala/service/ActivityService.scala new file mode 100644 index 0000000..2e9102f --- /dev/null +++ b/src/main/scala/service/ActivityService.scala @@ -0,0 +1,30 @@ +package service + +import model._ +import Activities._ +import scala.slick.driver.H2Driver.simple._ +import Database.threadLocalSession + +trait ActivityService { + + def getActivitiesByUser(activityUserName: String, isPublic: Boolean): List[Activity] = { + val q = Query(Activities) + .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) + + (if(isPublic){ + q filter { case (t1, t2) => (t1.activityUserName is activityUserName.bind) && (t2.isPrivate is false.bind) } + } else { + q filter { case (t1, t2) => t1.activityUserName is activityUserName.bind } + }) + .sortBy { case (t1, t2) => t1.activityId desc } + .map { case (t1, t2) => t1 } + .list + } + + def recordCreateRepository(userName: String, repositoryName: String, activityUserName: String): Unit = { + Activities.autoInc insert(userName, repositoryName, activityUserName, + "[[%s]] created [[%s/%s]]".format(activityUserName, userName, repositoryName), + currentDate) + } + +} diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 5453c22..46e8364 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -49,8 +49,9 @@ * The history of versions. A head of this sequence is the current BitBucket version. */ val versions = Seq( - Version(1, 1), - Version(1, 0) + Version(1, 2), + Version(1, 1), + Version(1, 0) ) /** diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index 7f4ffdf..88c24d6 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -33,6 +33,13 @@ Html(Markdown.toHtml(value, repository, enableWikiLink, enableCommitLink, enableIssueLink)) } + def activityMessage(message: String)(implicit context: app.Context): Html = { + Html(message + .replaceAll("\\[\\[([^\\s]+?)/([^\\s]+?)\\]\\]", "$1/$2".format(context.path)) + .replaceAll("\\[\\[([^\\s]+?)\\]\\]", "$1".format(context.path)) + ) + } + /** * Generates the url to the repository. */ @@ -61,6 +68,9 @@ // convert commit id to link .replaceAll("(^|\\W)([a-f0-9]{40})(\\W|$)", "$1$2$3").format(context.path, repository.owner, repository.name)) + /** + * Implicit conversion to add mkHtml() to Seq[Html]. + */ implicit def extendsHtmlSeq(seq: Seq[Html]) = new { def mkHtml(separator: String) = Html(seq.mkString(separator)) def mkHtml(separator: scala.xml.Elem) = Html(seq.mkString(separator.toString)) diff --git a/src/main/twirl/account/activity.scala.html b/src/main/twirl/account/activity.scala.html new file mode 100644 index 0000000..5ef58a9 --- /dev/null +++ b/src/main/twirl/account/activity.scala.html @@ -0,0 +1,31 @@ +@(account: model.Account, activities: List[model.Activity])(implicit context: app.Context) +@import context._ +@import view.helpers._ +@html.main(account.userName){ +
+
+
+
+
@account.userName
+
+
+
@account.url
+
Joined on @date(account.registeredDate)
+
+
+
+ @tab(account, "activity") + @if(activities.isEmpty){ + No activity + } else { + @activities.map { activity => +
+
@datetime(activity.activityDate)
+
@activityMessage(activity.message)
+
+ } + } +
+
+
+} diff --git a/src/main/twirl/account/info.scala.html b/src/main/twirl/account/info.scala.html deleted file mode 100644 index 4e41b1a..0000000 --- a/src/main/twirl/account/info.scala.html +++ /dev/null @@ -1,53 +0,0 @@ -@(account: model.Account, repositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context) -@import context._ -@import view.helpers._ -@html.main(account.userName){ -
-
-
-
-
@account.userName
-
-
-
@account.url
-
Joined on @date(account.registeredDate)
-
-
-
- - @if(repositories.isEmpty){ - No repositories - } else { - @repositories.map { repository => -
-
- @repository.owner - / - @repository.name - @if(repository.repository.isPrivate){ - - } -
- @if(repository.repository.description.isDefined){ -
@repository.repository.description
- } -
Last updated: @datetime(repository.repository.lastActivityDate)
-
- } - } -
-
-
-} diff --git a/src/main/twirl/account/repositories.scala.html b/src/main/twirl/account/repositories.scala.html new file mode 100644 index 0000000..61941b8 --- /dev/null +++ b/src/main/twirl/account/repositories.scala.html @@ -0,0 +1,41 @@ +@(account: model.Account, repositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context) +@import context._ +@import view.helpers._ +@html.main(account.userName){ +
+
+
+
+
@account.userName
+
+
+
@account.url
+
Joined on @date(account.registeredDate)
+
+
+
+ @tab(account, "repositories") + @if(repositories.isEmpty){ + No repositories + } else { + @repositories.map { repository => +
+
+ @repository.owner + / + @repository.name + @if(repository.repository.isPrivate){ + + } +
+ @if(repository.repository.description.isDefined){ +
@repository.repository.description
+ } +
Last updated: @datetime(repository.repository.lastActivityDate)
+
+ } + } +
+
+
+} diff --git a/src/main/twirl/account/tab.scala.html b/src/main/twirl/account/tab.scala.html new file mode 100644 index 0000000..2a12a0c --- /dev/null +++ b/src/main/twirl/account/tab.scala.html @@ -0,0 +1,14 @@ +@(account: model.Account, active: String)(implicit context: app.Context) +@import context._ +@import view.helpers._ +