diff --git a/lib/scalatra-forms_2.10-0.0.1.jar b/lib/scalatra-forms_2.10-0.0.1.jar
index 1720a2b..b53dd0b 100644
--- a/lib/scalatra-forms_2.10-0.0.1.jar
+++ b/lib/scalatra-forms_2.10-0.0.1.jar
Binary files differ
diff --git a/sbt.bat b/sbt.bat
index 9a0b378..6949ebb 100644
--- a/sbt.bat
+++ b/sbt.bat
@@ -1,2 +1,2 @@
set SCRIPT_DIR=%~dp0
-java -Dhttp.proxyHost=proxy.intellilink.co.jp -Dhttp.proxyPort=8080 -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar "%SCRIPT_DIR%\sbt-launch-0.12.3.jar" %*
+java -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar "%SCRIPT_DIR%\sbt-launch-0.12.3.jar" %*
diff --git a/src/main/resources/update/1_0.sql b/src/main/resources/update/1_0.sql
index e85b2b0..0470c0b 100644
--- a/src/main/resources/update/1_0.sql
+++ b/src/main/resources/update/1_0.sql
@@ -118,9 +118,9 @@
UPDATED_DATE,
LAST_LOGIN_DATE
) VALUES (
- 'admin',
- 'admin@localhost',
- 'admin',
+ 'root',
+ 'root@localhost',
+ 'root',
1,
'https://github.com/takezoe/gitbucket',
SYSDATE,
diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala
index ab91570..323a9a2 100644
--- a/src/main/scala/ScalatraBootstrap.scala
+++ b/src/main/scala/ScalatraBootstrap.scala
@@ -6,6 +6,7 @@
override def init(context: ServletContext) {
context.mount(new IndexController, "/")
context.mount(new SignInController, "/*")
+ context.mount(new UsersController, "/*")
context.mount(new WikiController, "/*")
context.mount(new CreateRepositoryController, "/*")
context.mount(new RepositoryViewerController, "/*")
diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateRepositoryController.scala
index ac815b5..1fa0bf7 100644
--- a/src/main/scala/app/CreateRepositoryController.scala
+++ b/src/main/scala/app/CreateRepositoryController.scala
@@ -18,11 +18,11 @@
trait CreateRepositoryControllerBase extends ControllerBase {
self: RepositoryService with WikiService with UsersOnlyAuthenticator =>
- case class RepositoryCreationForm(name: String, description: String) // TODO Option?
+ case class RepositoryCreationForm(name: String, description: Option[String])
val form = mapping(
"name" -> trim(label("Repository name", text(required, maxlength(40), repository))),
- "description" -> trim(label("Description" , text()))
+ "description" -> trim(label("Description" , optional(text())))
)(RepositoryCreationForm.apply)
/**
@@ -39,7 +39,7 @@
val loginUserName = context.loginAccount.get.userName
// Insert to the database at first
- createRepository(form.name, loginUserName, Some(form.description))
+ createRepository(form.name, loginUserName, form.description)
// Create the actual repository
val gitdir = getRepositoryDir(loginUserName, form.name)
diff --git a/src/main/scala/app/SettingsController.scala b/src/main/scala/app/SettingsController.scala
index 288d068..460f95e 100644
--- a/src/main/scala/app/SettingsController.scala
+++ b/src/main/scala/app/SettingsController.scala
@@ -7,22 +7,35 @@
class SettingsController extends SettingsControllerBase
with RepositoryService with AccountService with OwnerOnlyAuthenticator
-
trait SettingsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with OwnerOnlyAuthenticator =>
+ case class OptionsForm(description: Option[String], defaultBranch: String, repositoryType: Int)
+
+ val optionsForm = mapping(
+ "description" -> trim(label("Description" , optional(text()))),
+ "defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
+ "repositoryType" -> trim(label("Repository Type", number()))
+ )(OptionsForm.apply)
+
case class CollaboratorForm(userName: String)
- val form = mapping(
+ val collaboratorForm = mapping(
"userName" -> trim(label("Username", text(required, collaborator)))
)(CollaboratorForm.apply)
+ /**
+ * Redirect to the Options page.
+ */
get("/:owner/:repository/settings")(ownerOnly {
val owner = params("owner")
val repository = params("repository")
redirect("/%s/%s/settings/options".format(owner, repository))
})
+ /**
+ * Display the Options page.
+ */
get("/:owner/:repository/settings/options")(ownerOnly {
val owner = params("owner")
val repository = params("repository")
@@ -30,6 +43,22 @@
settings.html.options(getRepository(owner, repository, servletContext).get)
})
+ /**
+ * Save the repository options.
+ */
+ post("/:owner/:repository/settings/options", optionsForm){ form =>
+ val owner = params("owner")
+ val repository = params("repository")
+
+ // save repository options
+ saveRepositoryOptions(owner, repository, form.description, form.defaultBranch, form.repositoryType)
+
+ redirect("%s/%s/settings/options".format(owner, repository))
+ }
+
+ /**
+ * Display the Collaborators page.
+ */
get("/:owner/:repository/settings/collaborators")(ownerOnly {
val owner = params("owner")
val repository = params("repository")
@@ -37,13 +66,31 @@
settings.html.collaborators(getCollaborators(owner, repository), getRepository(owner, repository, servletContext).get)
})
- post("/:owner/:repository/settings/collaborators/_add", form)(ownerOnly { form =>
+ /**
+ * Add the collaborator.
+ */
+ post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { form =>
val owner = params("owner")
val repository = params("repository")
addCollaborator(owner, repository, form.userName)
redirect("/%s/%s/settings/collaborators".format(owner, repository))
})
+ /**
+ * Add the collaborator.
+ */
+ get("/:owner/:repository/settings/collaborators/remove")(ownerOnly {
+ val owner = params("owner")
+ val repository = params("repository")
+ val userName = params("name")
+ removeCollaborator(owner, repository, userName)
+ redirect("/%s/%s/settings/collaborators".format(owner, repository))
+ })
+
+
+ /**
+ * Provides Constraint to validate the collaborator name.
+ */
def collaborator: Constraint = new Constraint(){
def validate(name: String, value: String): Option[String] = {
getAccountByUserName(value) match {
diff --git a/src/main/scala/app/SignInController.scala b/src/main/scala/app/SignInController.scala
index 49e6d9b..ab626cb 100644
--- a/src/main/scala/app/SignInController.scala
+++ b/src/main/scala/app/SignInController.scala
@@ -24,6 +24,7 @@
redirect("/signin")
} else {
session.setAttribute("LOGIN_ACCOUNT", account.get)
+ updateLastLoginDate(account.get.userName)
redirect("/%s".format(account.get.userName))
}
}
diff --git a/src/main/scala/app/UsersController.scala b/src/main/scala/app/UsersController.scala
index ddd5c95..3ca0075 100644
--- a/src/main/scala/app/UsersController.scala
+++ b/src/main/scala/app/UsersController.scala
@@ -1,9 +1,76 @@
package app
-class UsersController extends ControllerBase {
+import model._
+import service._
+import jp.sf.amateras.scalatra.forms._
- get("/"){
-
+class UsersController extends UsersControllerBase with AccountService
+
+trait UsersControllerBase extends ControllerBase { self: AccountService =>
+
+ // TODO ユーザ名の先頭に_は使えないようにする&利用可能文字チェック
+ case class UserForm(userName: String, password: String, mailAddress: String, userType: Int, url: Option[String])
+
+ val newForm = mapping(
+ "userName" -> trim(label("Username" , text(required, maxlength(100), unique))),
+ "password" -> trim(label("Password" , text(required, maxlength(100)))),
+ "mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100)))),
+ "userType" -> trim(label("User Type" , number())),
+ "url" -> trim(label("URL" , optional(text(maxlength(200)))))
+ )(UserForm.apply)
+
+ val editForm = mapping(
+ "userName" -> trim(label("Username" , text())),
+ "password" -> trim(label("Password" , text(required, maxlength(100)))),
+ "mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100)))),
+ "userType" -> trim(label("User Type" , number())),
+ "url" -> trim(label("URL" , optional(text(maxlength(200)))))
+ )(UserForm.apply)
+
+ get("/admin/users"){
+ admin.html.userlist(getAllUsers())
}
+ get("/admin/users/_new"){
+ admin.html.useredit(None)
+ }
+
+ post("/admin/users/_new", newForm){ form =>
+ val currentDate = new java.sql.Date(System.currentTimeMillis)
+ createAccount(Account(
+ userName = form.userName,
+ password = form.password,
+ mailAddress = form.mailAddress,
+ userType = form.userType,
+ url = form.url,
+ registeredDate = currentDate,
+ updatedDate = currentDate,
+ lastLoginDate = None))
+
+ redirect("/admin/users")
+ }
+
+ get("/admin/users/:userName/_edit"){
+ val userName = params("userName")
+ admin.html.useredit(getAccountByUserName(userName))
+ }
+
+ post("/admin/users/:name/_edit", editForm){ form =>
+ val userName = params("userName")
+ val currentDate = new java.sql.Date(System.currentTimeMillis)
+ updateAccount(getAccountByUserName(userName).get.copy(
+ password = form.password,
+ mailAddress = form.mailAddress,
+ userType = form.userType,
+ url = form.url,
+ updatedDate = currentDate))
+
+ redirect("/admin/users")
+ }
+
+ def unique: Constraint = new Constraint(){
+ def validate(name: String, value: String): Option[String] =
+ getAccountByUserName(value).map { _ => "User already exists." }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/scala/app/WikiController.scala b/src/main/scala/app/WikiController.scala
index 4c5ebe5..d9ed92b 100644
--- a/src/main/scala/app/WikiController.scala
+++ b/src/main/scala/app/WikiController.scala
@@ -11,6 +11,7 @@
trait WikiControllerBase extends ControllerBase {
self: WikiService with RepositoryService with CollaboratorsOnlyAuthenticator =>
+ // TODO ユーザ名の先頭に_は使えないようにする
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String)
val newForm = mapping(
@@ -32,8 +33,8 @@
val repository = params("repository")
getWikiPage(owner, repository, "Home") match {
- case Some(page) => wiki.html.wiki("Home", page, getRepository(owner, repository, servletContext).get)
- case None => wiki.html.wikiedit("Home", None, getRepository(owner, repository, servletContext).get)
+ case Some(page) => wiki.html.wiki("Home", page, getRepository(owner, repository, servletContext).get, isWritable(owner, repository))
+ case None => redirect("/%s/%s/wiki/Home/_edit".format(owner, repository))
}
}
@@ -43,8 +44,8 @@
val pageName = params("page")
getWikiPage(owner, repository, pageName) match {
- case Some(page) => wiki.html.wiki(pageName, page, getRepository(owner, repository, servletContext).get)
- case None => wiki.html.wikiedit(pageName, None, getRepository(owner, repository, servletContext).get)
+ case Some(page) => wiki.html.wiki(pageName, page, getRepository(owner, repository, servletContext).get, isWritable(owner, repository))
+ case None => redirect("/%s/%s/wiki/%s/_edit".format(owner, repository, pageName)) // TODO URLEncode
}
}
@@ -132,7 +133,7 @@
val owner = params("owner")
val repository = params("repository")
- wiki.html.wikipages(getWikiPageList(owner, repository), getRepository(owner, repository, servletContext).get)
+ wiki.html.wikipages(getWikiPageList(owner, repository), getRepository(owner, repository, servletContext).get, isWritable(owner, repository))
}
get("/:owner/:repository/wiki/_history"){
@@ -166,6 +167,15 @@
}
}
+ def isWritable(owner: String, repository: String): Boolean = {
+ context.loginAccount match {
+ case Some(a) if(a.userType == AccountService.Administrator) => true
+ case Some(a) if(a.userName == owner) => true
+ case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true
+ case _ => false
+ }
+ }
+
def unique: Constraint = new Constraint(){
def validate(name: String, value: String): Option[String] = {
if(getWikiPageList(params("owner"), params("repository")).contains(value)){
diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala
index 45ee379..af152cf 100644
--- a/src/main/scala/service/AccountService.scala
+++ b/src/main/scala/service/AccountService.scala
@@ -6,9 +6,30 @@
trait AccountService {
- def getAccountByUserName(userName: String): Option[Account] =
+ def getAccountByUserName(userName: String): Option[Account] =
Query(Accounts) filter(_.userName is userName.bind) firstOption
+
+ def getAllUsers(): List[Account] = Query(Accounts) sortBy(_.userName) list
+
+ def createAccount(account: Account): Unit = Accounts.* insert account
+ def updateAccount(account: Account): Unit =
+ Query(Accounts)
+ .filter { a => a.userName is account.userName.bind }
+ .map { a => a.password ~ a.mailAddress ~ a.userType ~ a.url.? ~ a.registeredDate ~ a.updatedDate ~ a.lastLoginDate.? }
+ .update (
+ account.password,
+ account.mailAddress,
+ account.userType,
+ account.url,
+ account.registeredDate,
+ account.updatedDate,
+ account.lastLoginDate)
+
+ def updateLastLoginDate(userName: String): Unit =
+ Query(Accounts).filter(_.userName is userName.bind).map(_.lastLoginDate)
+ .update(new java.sql.Date(System.currentTimeMillis))
+
}
object AccountService {
diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala
index c422a29..d6f0bf4 100644
--- a/src/main/scala/service/RepositoryService.scala
+++ b/src/main/scala/service/RepositoryService.scala
@@ -26,7 +26,7 @@
val currentDate = new java.sql.Date(System.currentTimeMillis)
- Repositories.* insert
+ Repositories insert
Repository(
repositoryName = repositoryName,
userName = userName,
@@ -104,11 +104,23 @@
}
/**
- * TODO Updates the last activity date of the repository.
+ * Updates the last activity date of the repository.
*/
- def updateLastActivityDate(userName: String, repositoryName: String): Unit = {
-
- }
+ def updateLastActivityDate(userName: String, repositoryName: String): Unit =
+ Query(Repositories)
+ .filter { r => (r.userName is userName.bind) && (r.repositoryName is repositoryName.bind) }
+ .map { _.lastActivityDate }
+ .update (new java.sql.Date(System.currentTimeMillis))
+
+ /**
+ * Save repository options.
+ */
+ def saveRepositoryOptions(userName: String, repositoryName: String,
+ description: Option[String], defaultBranch: String, repositoryType: Int): Unit =
+ Query(Repositories)
+ .filter { r => (r.userName is userName.bind) && (r.repositoryName is repositoryName.bind) }
+ .map { r => r.description.? ~ r.defaultBranch ~ r.repositoryType ~ r.updatedDate }
+ .update (description, defaultBranch, repositoryType, new java.sql.Date(System.currentTimeMillis))
/**
* Add collaborator to the repository.
@@ -118,11 +130,31 @@
* @param collaboratorName the collaborator name
*/
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String): Unit =
- Collaborators.* insert(Collaborator(userName, repositoryName, collaboratorName))
+ Collaborators insert(Collaborator(userName, repositoryName, collaboratorName))
+ /**
+ * Remove collaborator from the repository.
+ *
+ * @param userName the user name of the repository owner
+ * @param repositoryName the repository name
+ * @param collaboratorName the collaborator name
+ */
+ def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String): Unit =
+ (Query(Collaborators) filter { c =>
+ (c.userName is userName.bind) && (c.repositoryName is repositoryName.bind) && (c.collaboratorName is collaboratorName.bind)
+ }).delete
+
+
+ /**
+ * Returns the list of collaborators name which is sorted with ascending order.
+ *
+ * @param userName the user name of the repository owner
+ * @param repositoryName the repository name
+ * @return the list of collaborators name
+ */
def getCollaborators(userName: String, repositoryName: String): List[String] =
- (Query(Collaborators) filter { collaborator =>
- (collaborator.userName is userName.bind) && (collaborator.repositoryName is repositoryName.bind)
+ (Query(Collaborators) filter { c =>
+ (c.userName is userName.bind) && (c.repositoryName is repositoryName.bind)
} sortBy(_.collaboratorName) list) map(_.collaboratorName)
}
diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala
index 7c82131..ad47676 100644
--- a/src/main/scala/view/helpers.scala
+++ b/src/main/scala/view/helpers.scala
@@ -2,6 +2,8 @@
import java.util.Date
import java.text.SimpleDateFormat
+import twirl.api.Html
+
import org.pegdown._
import org.pegdown.LinkRenderer.Rendering
import org.pegdown.ast.WikiLinkNode
@@ -19,9 +21,9 @@
def date(date: Date): String = new SimpleDateFormat("yyyy/MM/dd").format(date)
// TODO escape html tags using HtmlEscapeUtils (Commons Lang)
- def format(value: String): twirl.api.Html = twirl.api.Html(
+ def format(value: String): Html = Html(
value.replaceAll(" ", " ").replaceAll("\t", " ").replaceAll("\n", "
"))
-
+
/**
* Converts the issue number and the commit id to the link.
*/
@@ -59,7 +61,8 @@
}
}
})
- twirl.api.Html(html)
+
+ Html(html)
}
/**
diff --git a/src/main/twirl/admin/useredit.scala.html b/src/main/twirl/admin/useredit.scala.html
new file mode 100644
index 0000000..1515018
--- /dev/null
+++ b/src/main/twirl/admin/useredit.scala.html
@@ -0,0 +1,40 @@
+@(account: Option[model.Account])(implicit context: app.Context)
+@import context._
+@import service.AccountService._
+@html.main(if(account.isEmpty) "New User" else "Update User"){
+
+}
\ No newline at end of file
diff --git a/src/main/twirl/admin/userlist.scala.html b/src/main/twirl/admin/userlist.scala.html
new file mode 100644
index 0000000..359a304
--- /dev/null
+++ b/src/main/twirl/admin/userlist.scala.html
@@ -0,0 +1,37 @@
+@(users: List[model.Account])(implicit context: app.Context)
+@import context._
+@import service.AccountService._
+@html.main("Manage Users"){
+
+
+
+ Username |
+ Mail Address |
+ Type |
+ URL |
+ Registered |
+ Updated |
+ Last Login |
+
+ @users.map { account =>
+
+ @account.userName |
+ @account.mailAddress |
+
+ @if(account.userType == Normal){
+ Normal
+ }
+ @if(account.userType == Administrator){
+ Administrator
+ }
+ |
+ @account.url |
+ @account.registeredDate |
+ @account.updatedDate |
+ @account.lastLoginDate |
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/twirl/header.scala.html b/src/main/twirl/header.scala.html
index 7f90f49..800f6b9 100644
--- a/src/main/twirl/header.scala.html
+++ b/src/main/twirl/header.scala.html
@@ -1,7 +1,12 @@
@(active: String, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._
+@import service.AccountService._
+@import service.RepositoryService._