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"){ +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + Cancel +
+
+} \ 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"){ +
+ New User +
+ + + + + + + + + + + @users.map { account => + + + + + + + + + + } +
UsernameMail AddressTypeURLRegisteredUpdatedLast Login
@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._
@repository.owner / @repository.name + @if(repository.repository.repositoryType == Private){ + + }
@@ -14,9 +19,11 @@ + @if(loginAccount.isDefined && (loginAccount.get.userType == Administrator || loginAccount.get.userName == repository.owner)){ + }