diff --git a/src/main/scala/api/ApiCombinedCommitStatus.scala b/src/main/scala/api/ApiCombinedCommitStatus.scala new file mode 100644 index 0000000..29d8fa7 --- /dev/null +++ b/src/main/scala/api/ApiCombinedCommitStatus.scala @@ -0,0 +1,26 @@ +package api + +import model.Account +import model.CommitStatus +import model.CommitState + +/** + * https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref + */ +case class ApiCombinedCommitStatus( + state: String, + sha: String, + total_count: Int, + statuses: Iterable[ApiCommitStatus], + repository: ApiRepository){ + // val commit_url = ApiPath(s"/api/v3/repos/${repository.full_name}/${sha}") + val url = ApiPath(s"/api/v3/repos/${repository.full_name}/commits/${sha}/status") +} +object ApiCombinedCommitStatus { + def apply(sha:String, statuses: Iterable[(CommitStatus, Account)], repository:ApiRepository): ApiCombinedCommitStatus = ApiCombinedCommitStatus( + state = CommitState.combine(statuses.map(_._1.state).toSet).name, + sha = sha, + total_count= statuses.size, + statuses = statuses.map{ case (s, a)=> ApiCommitStatus(s, ApiUser(a)) }, + repository = repository) +} diff --git a/src/main/scala/api/ApiComment.scala b/src/main/scala/api/ApiComment.scala new file mode 100644 index 0000000..1733fb3 --- /dev/null +++ b/src/main/scala/api/ApiComment.scala @@ -0,0 +1,24 @@ +package api + +import java.util.Date +import model.IssueComment + +/** + * https://developer.github.com/v3/issues/comments/ + */ +case class ApiComment( + id: Int, + user: ApiUser, + body: String, + created_at: Date, + updated_at: Date) + +object ApiComment{ + def apply(comment: IssueComment, user: ApiUser): ApiComment = + ApiComment( + id = comment.commentId, + user = user, + body = comment.content, + created_at = comment.registeredDate, + updated_at = comment.updatedDate) +} diff --git a/src/main/scala/api/ApiCommit.scala b/src/main/scala/api/ApiCommit.scala new file mode 100644 index 0000000..f4ef17a --- /dev/null +++ b/src/main/scala/api/ApiCommit.scala @@ -0,0 +1,41 @@ +package api + +import java.util.Date +import org.eclipse.jgit.diff.DiffEntry +import util.JGitUtil +import util.JGitUtil.CommitInfo +import org.eclipse.jgit.api.Git +import util.RepositoryName + +/** + * https://developer.github.com/v3/repos/commits/ + */ +case class ApiCommit( + id: String, + message: String, + timestamp: Date, + added: List[String], + removed: List[String], + modified: List[String], + author: ApiPersonIdent, + committer: ApiPersonIdent)(repositoryName:RepositoryName){ + val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}") + val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}") +} + +object ApiCommit{ + def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = { + val diffs = JGitUtil.getDiffs(git, commit.id, false) + ApiCommit( + id = commit.id, + message = commit.fullMessage, + timestamp = commit.commitTime, + added = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD) => x.newPath }, + removed = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath }, + modified = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD && + x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath }, + author = ApiPersonIdent.author(commit), + committer = ApiPersonIdent.committer(commit) + )(repositoryName) + } +} diff --git a/src/main/scala/api/ApiCommitListItem.scala b/src/main/scala/api/ApiCommitListItem.scala new file mode 100644 index 0000000..c345661 --- /dev/null +++ b/src/main/scala/api/ApiCommitListItem.scala @@ -0,0 +1,41 @@ +package api + +import util.JGitUtil.CommitInfo +import ApiCommitListItem._ +import util.RepositoryName + +/** + * https://developer.github.com/v3/repos/commits/ + */ +case class ApiCommitListItem( + sha: String, + commit: Commit, + author: Option[ApiUser], + committer: Option[ApiUser], + parents: Seq[Parent])(repositoryName: RepositoryName) { + val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}") +} + +object ApiCommitListItem { + def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem = ApiCommitListItem( + sha = commit.id, + commit = Commit( + message = commit.fullMessage, + author = ApiPersonIdent.author(commit), + committer = ApiPersonIdent.committer(commit) + )(commit.id, repositoryName), + author = None, + committer = None, + parents = commit.parents.map(Parent(_)(repositoryName)))(repositoryName) + + case class Parent(sha: String)(repositoryName: RepositoryName){ + val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}") + } + + case class Commit( + message: String, + author: ApiPersonIdent, + committer: ApiPersonIdent)(sha:String, repositoryName: RepositoryName) { + val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${sha}") + } +} diff --git a/src/main/scala/api/ApiCommitStatus.scala b/src/main/scala/api/ApiCommitStatus.scala new file mode 100644 index 0000000..e6e2ea1 --- /dev/null +++ b/src/main/scala/api/ApiCommitStatus.scala @@ -0,0 +1,35 @@ +package api + +import java.util.Date +import model.CommitStatus +import util.RepositoryName + +/** + * https://developer.github.com/v3/repos/statuses/#create-a-status + * https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref + */ +case class ApiCommitStatus( + created_at: Date, + updated_at: Date, + state: String, + target_url: Option[String], + description: Option[String], + id: Int, + context: String, + creator: ApiUser +)(sha: String,repositoryName: RepositoryName) { + val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}/statuses") +} + +object ApiCommitStatus { + def apply(status: CommitStatus, creator:ApiUser): ApiCommitStatus = ApiCommitStatus( + created_at = status.registeredDate, + updated_at = status.updatedDate, + state = status.state.name, + target_url = status.targetUrl, + description= status.description, + id = status.commitStatusId, + context = status.context, + creator = creator + )(status.commitId, RepositoryName(status)) +} diff --git a/src/main/scala/api/ApiIssue.scala b/src/main/scala/api/ApiIssue.scala new file mode 100644 index 0000000..6c31d6a --- /dev/null +++ b/src/main/scala/api/ApiIssue.scala @@ -0,0 +1,29 @@ +package api + +import java.util.Date +import model.Issue + +/** + * https://developer.github.com/v3/issues/ + */ +case class ApiIssue( + number: Int, + title: String, + user: ApiUser, + // labels, + state: String, + created_at: Date, + updated_at: Date, + body: String) + +object ApiIssue{ + def apply(issue: Issue, user: ApiUser): ApiIssue = + ApiIssue( + number = issue.issueId, + title = issue.title, + user = user, + state = if(issue.closed){ "closed" }else{ "open" }, + body = issue.content.getOrElse(""), + created_at = issue.registeredDate, + updated_at = issue.updatedDate) +} diff --git a/src/main/scala/api/ApiPath.scala b/src/main/scala/api/ApiPath.scala new file mode 100644 index 0000000..2572945 --- /dev/null +++ b/src/main/scala/api/ApiPath.scala @@ -0,0 +1,6 @@ +package api + +/** + * path for api url. if set path '/repos/aa/bb' then, expand 'http://server:post/repos/aa/bb' when converted to json. + */ +case class ApiPath(path: String) diff --git a/src/main/scala/api/ApiPersonIdent.scala b/src/main/scala/api/ApiPersonIdent.scala new file mode 100644 index 0000000..c9b3b97 --- /dev/null +++ b/src/main/scala/api/ApiPersonIdent.scala @@ -0,0 +1,22 @@ +package api + +import java.util.Date +import util.JGitUtil.CommitInfo + +case class ApiPersonIdent( + name: String, + email: String, + date: Date) + +object ApiPersonIdent { + def author(commit: CommitInfo): ApiPersonIdent = + ApiPersonIdent( + name = commit.authorName, + email = commit.authorEmailAddress, + date = commit.authorTime) + def committer(commit: CommitInfo): ApiPersonIdent = + ApiPersonIdent( + name = commit.committerName, + email = commit.committerEmailAddress, + date = commit.commitTime) +} diff --git a/src/main/scala/api/ApiPullRequest.scala b/src/main/scala/api/ApiPullRequest.scala new file mode 100644 index 0000000..975f562 --- /dev/null +++ b/src/main/scala/api/ApiPullRequest.scala @@ -0,0 +1,58 @@ +package api + +import java.util.Date +import model.{Issue, PullRequest} +import ApiPullRequest._ + +/** + * https://developer.github.com/v3/pulls/ + */ +case class ApiPullRequest( + number: Int, + updated_at: Date, + created_at: Date, + head: ApiPullRequest.Commit, + base: ApiPullRequest.Commit, + mergeable: Option[Boolean], + title: String, + body: String, + user: ApiUser) { + val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}") + //val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff") + //val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch") + val url = ApiPath(s"${base.repo.url.path}/pulls/${number}") + //val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}") + val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits") + val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments") + val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}") + val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments") + val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}") +} + +object ApiPullRequest{ + def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = ApiPullRequest( + number = issue.issueId, + updated_at = issue.updatedDate, + created_at = issue.registeredDate, + head = Commit( + sha = pullRequest.commitIdTo, + ref = pullRequest.requestBranch, + repo = headRepo)(issue.userName), + base = Commit( + sha = pullRequest.commitIdFrom, + ref = pullRequest.branch, + repo = baseRepo)(issue.userName), + mergeable = None, // TODO: need check mergeable. + title = issue.title, + body = issue.content.getOrElse(""), + user = user + ) + + case class Commit( + sha: String, + ref: String, + repo: ApiRepository)(baseOwner:String){ + val label = if( baseOwner == repo.owner.login ){ ref }else{ s"${repo.owner.login}:${ref}" } + val user = repo.owner + } +} diff --git a/src/main/scala/api/ApiRepository.scala b/src/main/scala/api/ApiRepository.scala new file mode 100644 index 0000000..05949b8 --- /dev/null +++ b/src/main/scala/api/ApiRepository.scala @@ -0,0 +1,48 @@ +package api + +import util.JGitUtil.CommitInfo +import service.RepositoryService.RepositoryInfo +import model.{Account, Repository} + +// https://developer.github.com/v3/repos/ +case class ApiRepository( + name: String, + full_name: String, + description: String, + watchers: Int, + forks: Int, + `private`: Boolean, + default_branch: String, + owner: ApiUser) { + val forks_count = forks + val watchers_coun = watchers + val url = ApiPath(s"/api/v3/repos/${full_name}") + val http_url = ApiPath(s"/git/${full_name}.git") + val clone_url = ApiPath(s"/git/${full_name}.git") + val html_url = ApiPath(s"/${full_name}") +} + +object ApiRepository{ + def apply( + repository: Repository, + owner: ApiUser, + forkedCount: Int =0, + watchers: Int = 0): ApiRepository = + ApiRepository( + name = repository.repositoryName, + full_name = s"${repository.userName}/${repository.repositoryName}", + description = repository.description.getOrElse(""), + watchers = 0, + forks = forkedCount, + `private` = repository.isPrivate, + default_branch = repository.defaultBranch, + owner = owner + ) + + def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository = + ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount) + + def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository = + this(repositoryInfo.repository, ApiUser(owner)) + +} diff --git a/src/main/scala/api/ApiUser.scala b/src/main/scala/api/ApiUser.scala new file mode 100644 index 0000000..5cbc326 --- /dev/null +++ b/src/main/scala/api/ApiUser.scala @@ -0,0 +1,33 @@ +package api + +import java.util.Date +import model.Account + +case class ApiUser( + login: String, + email: String, + `type`: String, + site_admin: Boolean, + created_at: Date) { + val url = ApiPath(s"/api/v3/users/${login}") + val html_url = ApiPath(s"/${login}") + // val followers_url = ApiPath(s"/api/v3/users/${login}/followers") + // val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}") + // val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}") + // val starred_url = ApiPath(s"/api/v3/users/${login}/starred{/owner}{/repo}") + // val subscriptions_url = ApiPath(s"/api/v3/users/${login}/subscriptions") + // val organizations_url = ApiPath(s"/api/v3/users/${login}/orgs") + // val repos_url = ApiPath(s"/api/v3/users/${login}/repos") + // val events_url = ApiPath(s"/api/v3/users/${login}/events{/privacy}") + // val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events") +} + +object ApiUser{ + def apply(user: Account): ApiUser = ApiUser( + login = user.fullName, + email = user.mailAddress, + `type` = if(user.isGroupAccount){ "Organization" }else{ "User" }, + site_admin = user.isAdmin, + created_at = user.registeredDate + ) +} diff --git a/src/main/scala/api/CreateAComment.scala b/src/main/scala/api/CreateAComment.scala new file mode 100644 index 0000000..733758d --- /dev/null +++ b/src/main/scala/api/CreateAComment.scala @@ -0,0 +1,7 @@ +package api + +/** + * https://developer.github.com/v3/issues/comments/#create-a-comment + * api form + */ +case class CreateAComment(body: String) diff --git a/src/main/scala/api/CreateAStatus.scala b/src/main/scala/api/CreateAStatus.scala new file mode 100644 index 0000000..5237cac --- /dev/null +++ b/src/main/scala/api/CreateAStatus.scala @@ -0,0 +1,26 @@ +package api + +import model.CommitState + +/** + * https://developer.github.com/v3/repos/statuses/#create-a-status + * api form + */ +case class CreateAStatus( + /* state is Required. The state of the status. Can be one of pending, success, error, or failure. */ + state: String, + /* context is a string label to differentiate this status from the status of other systems. Default: "default" */ + context: Option[String], + /* The target URL to associate with this status. This URL will be linked from the GitHub UI to allow users to easily see the ‘source’ of the Status. */ + target_url: Option[String], + /* description is a short description of the status.*/ + description: Option[String] +) { + def isValid: Boolean = { + CommitState.valueOf(state).isDefined && + // only http + target_url.filterNot(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length<255).isEmpty && + context.filterNot(f => f.length<255).isEmpty && + description.filterNot(f => f.length<1000).isEmpty + } +} diff --git a/src/main/scala/api/JsonFormat.scala b/src/main/scala/api/JsonFormat.scala new file mode 100644 index 0000000..f743534 --- /dev/null +++ b/src/main/scala/api/JsonFormat.scala @@ -0,0 +1,37 @@ +package api +import org.json4s._ +import org.json4s.jackson.Serialization +import scala.util.Try +import org.joda.time.format._ +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import java.util.Date +object JsonFormat { + case class Context(baseUrl:String) + val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") + val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format => + ( + { case JString(s) => Try(parserISO.parseDateTime(s)).toOption.map(_.toDate) + .getOrElse(throw new MappingException("Can't convert " + s + " to Date")) }, + { case x: Date => JString(parserISO.print(new DateTime(x).withZone(DateTimeZone.UTC))) } + ) + ) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() + + FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() + + FieldSerializer[ApiCommitStatus]() + FieldSerializer[ApiCommit]() + FieldSerializer[ApiCombinedCommitStatus]() + + FieldSerializer[ApiPullRequest.Commit]() + def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format => + ( + { + case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) + case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") + }, + { + case ApiPath(path) => JString(c.baseUrl+path) + } + ) + ) + /** + * convert object to json string + */ + def apply(obj: AnyRef)(implicit c: Context): String = Serialization.write(obj)(jsonFormats + apiPathSerializer(c)) +} diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index c0b3c89..53f353a 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -15,6 +15,7 @@ import org.eclipse.jgit.dircache.DirCache import model.GroupMember import service.WebHookService._ +import api._ class AccountController extends AccountControllerBase with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService @@ -156,7 +157,7 @@ */ get("/api/v3/users/:userName") { getAccountByUserName(params("userName")).map { account => - apiJson(WebHookApiUser(account)) + JsonFormat(ApiUser(account)) } getOrElse NotFound } @@ -165,7 +166,7 @@ */ get("/api/v3/user") { context.loginAccount.map { account => - apiJson(WebHookApiUser(account)) + JsonFormat(ApiUser(account)) } getOrElse NotFound } diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index 077652e..43e24f4 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -11,6 +11,7 @@ import model.Issue import service.WebHookService._ import scala.util.Try +import api._ class IssuesController extends IssuesControllerBase with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService @@ -83,7 +84,7 @@ issueId <- params("id").toIntOpt comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt) } yield { - apiJson(comments.map{ case (issueComment, user) => WebHookComment(issueComment, WebHookApiUser(user)) }) + JsonFormat(comments.map{ case (issueComment, user) => ApiComment(issueComment, ApiUser(user)) }) }).getOrElse(NotFound) }) @@ -187,7 +188,7 @@ (issue, id) <- handleComment(issueId, Some(body), repository)() issueComment <- getComment(repository.owner, repository.name, id.toString()) } yield { - apiJson(WebHookComment(issueComment, WebHookApiUser(context.loginAccount.get))) + JsonFormat(ApiComment(issueComment, ApiUser(context.loginAccount.get))) }) getOrElse NotFound }) diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index a946ee4..f3c8cba 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -19,7 +19,7 @@ import util.JGitUtil.DiffInfo import util.JGitUtil.CommitInfo import model.{PullRequest, Issue, CommitState} - +import api._ class PullRequestsController extends PullRequestsControllerBase with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService @@ -80,13 +80,13 @@ val condition = IssueSearchCondition(request) val baseOwner = getAccountByUserName(repository.owner).get val issues:List[(model.Issue, model.Account, Int, model.PullRequest, model.Repository, model.Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name) - apiJson(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) => - WebHookPullRequest( + JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) => + ApiPullRequest( issue, pullRequest, - WebHookRepository(headRepo, WebHookApiUser(headOwner)), - WebHookRepository(repository, WebHookApiUser(baseOwner)), - WebHookApiUser(issueUser)) }) + ApiRepository(headRepo, ApiUser(headOwner)), + ApiRepository(repository, ApiUser(baseOwner)), + ApiUser(issueUser)) }) }) get("/:owner/:repository/pull/:id")(referrersOnly { repository => @@ -127,12 +127,12 @@ issueUser <- users.get(issue.userName) headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) } yield { - apiJson(WebHookPullRequest( + JsonFormat(ApiPullRequest( issue, pullRequest, - WebHookRepository(headRepo, WebHookApiUser(headOwner)), - WebHookRepository(repository, WebHookApiUser(baseOwner)), - WebHookApiUser(issueUser))) + ApiRepository(headRepo, ApiUser(headOwner)), + ApiRepository(repository, ApiUser(baseOwner)), + ApiUser(issueUser))) }).getOrElse(NotFound) }) @@ -147,9 +147,9 @@ using(Git.open(getRepositoryDir(owner, name))){ git => val oldId = git.getRepository.resolve(pullreq.commitIdFrom) val newId = git.getRepository.resolve(pullreq.commitIdTo) - val repoFullName = s"${owner}/${name}" - val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => WebHookCommitListItem(new CommitInfo(c), repoFullName)).toList - apiJson(commits) + val repoFullName = util.RepositoryName(repository) + val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList + JsonFormat(commits) } } } getOrElse NotFound diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 3ec28e3..e710d06 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -19,6 +19,7 @@ import org.eclipse.jgit.revwalk.RevCommit import service.WebHookService._ import model.CommitState +import api._ class RepositoryViewerController extends RepositoryViewerControllerBase with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService @@ -110,7 +111,7 @@ * https://developer.github.com/v3/repos/#get */ get("/api/v3/repos/:owner/:repository")(referrersOnly { repository => - apiJson(WebHookRepository(repository, WebHookApiUser(getAccountByUserName(repository.owner).get))) + JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get))) }) /** @@ -158,7 +159,7 @@ state, data.target_url, data.description, new java.util.Date(), creator) status <- getCommitStatus(repository.owner, repository.name, statusId) } yield { - apiJson(WebHookCommitStatus(status, WebHookApiUser(creator))) + JsonFormat(ApiCommitStatus(status, ApiUser(creator))) }) getOrElse NotFound }) @@ -172,8 +173,8 @@ ref <- params.get("ref") sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) } yield { - apiJson(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) => - WebHookCommitStatus(status, WebHookApiUser(creator)) + JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) => + ApiCommitStatus(status, ApiUser(creator)) }) }) getOrElse NotFound }) @@ -190,7 +191,7 @@ sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) } yield { val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha) - apiJson(WebHookCombinedCommitStatus(sha, statuses, WebHookRepository(repository, owner))) + JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner))) }) getOrElse NotFound }) diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala index bae3a71..34ece5b 100644 --- a/src/main/scala/service/RepositoryService.scala +++ b/src/main/scala/service/RepositoryService.scala @@ -397,5 +397,4 @@ } case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode]) - } diff --git a/src/main/scala/service/WebHookService.scala b/src/main/scala/service/WebHookService.scala index 25dae0b..c03ce95 100644 --- a/src/main/scala/service/WebHookService.scala +++ b/src/main/scala/service/WebHookService.scala @@ -6,13 +6,13 @@ import org.slf4j.LoggerFactory import service.RepositoryService.RepositoryInfo import util.JGitUtil -import org.eclipse.jgit.diff.DiffEntry import util.JGitUtil.CommitInfo import org.eclipse.jgit.api.Git import org.apache.http.message.BasicNameValuePair import org.apache.http.client.entity.UrlEncodedFormEntity import org.apache.http.NameValuePair import java.util.Date +import api._ trait WebHookService { import WebHookService._ @@ -28,25 +28,21 @@ def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit = WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete - def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: ApiContext): Unit = { + def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = { val webHookURLs = getWebHookURLs(owner, repository) if(webHookURLs.nonEmpty){ makePayload.map(callWebHook(eventName, webHookURLs, _)) } } - def apiJson(obj: AnyRef)(implicit c: ApiContext): String = { - org.json4s.jackson.Serialization.write(obj)(jsonFormats + apiPathSerializer(c)) - } - - def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: ApiContext): Unit = { + def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): Unit = { import org.apache.http.client.methods.HttpPost import org.apache.http.impl.client.HttpClientBuilder import scala.concurrent._ import ExecutionContext.Implicits.global if(webHookURLs.nonEmpty){ - val json = apiJson(payload) + val json = JsonFormat(payload) val httpClient = HttpClientBuilder.create.build webHookURLs.foreach { webHookUrl => @@ -81,7 +77,7 @@ import WebHookService._ // https://developer.github.com/v3/activity/events/types/#issuesevent - def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: model.Account)(implicit s: Session, context:ApiContext): Unit = { + def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { callWebHookOf(repository.owner, repository.name, "issues"){ val users = getAccountsByUserNames(Set(repository.owner, issue.userName), Set(sender)) for{ @@ -91,14 +87,14 @@ WebHookIssuesPayload( action = action, number = issue.issueId, - repository = WebHookRepository(repository, WebHookApiUser(repoOwner)), - issue = WebHookIssue(issue, WebHookApiUser(issueUser)), - sender = WebHookApiUser(sender)) + repository = ApiRepository(repository, ApiUser(repoOwner)), + issue = ApiIssue(issue, ApiUser(issueUser)), + sender = ApiUser(sender)) } } } - def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: model.Account)(implicit s: Session, context:ApiContext): Unit = { + def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { import WebHookService._ callWebHookOf(repository.owner, repository.name, "pull_request"){ for{ @@ -126,7 +122,7 @@ self: AccountService with RepositoryService with PullRequestService with IssuesService => import WebHookService._ - def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: model.Account)(implicit s: Session, context:ApiContext): Unit = { + def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { callWebHookOf(repository.owner, repository.name, "issue_comment"){ for{ issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString()) @@ -149,58 +145,26 @@ } object WebHookService { - case class ApiContext(baseUrl:String) - - case class ApiPath(path:String) - - import org.json4s._ - import org.json4s.jackson.Serialization - val jsonFormats = { - import scala.util.Try - import org.joda.time.format._ - import org.joda.time.DateTime - import org.joda.time.DateTimeZone - val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") - Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format => - ( - { case JString(s) => Try(parserISO.parseDateTime(s)).toOption.map(_.toDate) - .getOrElse(throw new MappingException("Can't convert " + s + " to Date")) }, - { case x: Date => JString(parserISO.print(new DateTime(x).withZone(DateTimeZone.UTC))) } - ) - ) + FieldSerializer[WebHookApiUser]() + FieldSerializer[WebHookPullRequest]() + FieldSerializer[WebHookRepository]() + - FieldSerializer[WebHookCommitListItemParent]() + FieldSerializer[WebHookCommitListItem]() + FieldSerializer[WebHookCommitListItemCommit]() + - FieldSerializer[WebHookCommitStatus]() + FieldSerializer[WebHookCombinedCommitStatus]() - } - def apiPathSerializer(c:ApiContext) = new CustomSerializer[ApiPath](format => - ( - { - case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) - case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") - }, - { case ApiPath(path) => JString(c.baseUrl+path) } - ) - ) - trait WebHookPayload // https://developer.github.com/v3/activity/events/types/#pushevent case class WebHookPushPayload( - pusher: WebHookApiUser, + pusher: ApiUser, ref: String, - commits: List[WebHookCommit], - repository: WebHookRepository + commits: List[ApiCommit], + repository: ApiRepository ) extends WebHookPayload object WebHookPushPayload { def apply(git: Git, pusher: Account, refName: String, repositoryInfo: RepositoryInfo, commits: List[CommitInfo], repositoryOwner: Account): WebHookPushPayload = WebHookPushPayload( - WebHookApiUser(pusher), + ApiUser(pusher), refName, - commits.map{ commit => WebHookCommit(git, repositoryInfo, commit) }, - WebHookRepository( + commits.map{ commit => ApiCommit(git, util.RepositoryName(repositoryInfo), commit) }, + ApiRepository( repositoryInfo, - owner= WebHookApiUser(repositoryOwner) + owner= ApiUser(repositoryOwner) ) ) } @@ -209,17 +173,17 @@ case class WebHookIssuesPayload( action: String, number: Int, - repository: WebHookRepository, - issue: WebHookIssue, - sender: WebHookApiUser) extends WebHookPayload + repository: ApiRepository, + issue: ApiIssue, + sender: ApiUser) extends WebHookPayload // https://developer.github.com/v3/activity/events/types/#pullrequestevent case class WebHookPullRequestPayload( action: String, number: Int, - repository: WebHookRepository, - pull_request: WebHookPullRequest, - sender: WebHookApiUser + repository: ApiRepository, + pull_request: ApiPullRequest, + sender: ApiUser ) extends WebHookPayload object WebHookPullRequestPayload{ @@ -231,10 +195,10 @@ baseRepository: RepositoryInfo, baseOwner: Account, sender: model.Account): WebHookPullRequestPayload = { - val headRepoPayload = WebHookRepository(headRepository, headOwner) - val baseRepoPayload = WebHookRepository(baseRepository, baseOwner) - val senderPayload = WebHookApiUser(sender) - val pr = WebHookPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, senderPayload) + val headRepoPayload = ApiRepository(headRepository, headOwner) + val baseRepoPayload = ApiRepository(baseRepository, baseOwner) + val senderPayload = ApiUser(sender) + val pr = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, senderPayload) WebHookPullRequestPayload( action = action, number = issue.issueId, @@ -248,10 +212,10 @@ // https://developer.github.com/v3/activity/events/types/#issuecommentevent case class WebHookIssueCommentPayload( action: String, - repository: WebHookRepository, - issue: WebHookIssue, - comment: WebHookComment, - sender: WebHookApiUser + repository: ApiRepository, + issue: ApiIssue, + comment: ApiComment, + sender: ApiUser ) extends WebHookPayload object WebHookIssueCommentPayload{ @@ -265,341 +229,9 @@ sender: Account): WebHookIssueCommentPayload = WebHookIssueCommentPayload( action = "created", - repository = WebHookRepository(repository, repositoryUser), - issue = WebHookIssue(issue, WebHookApiUser(issueUser)), - comment = WebHookComment(comment, WebHookApiUser(commentUser)), - sender = WebHookApiUser(sender)) - } - - case class WebHookCommit( - id: String, - message: String, - timestamp: Date, - url: String, - added: List[String], - removed: List[String], - modified: List[String], - author: WebHookCommitUser, - committer: WebHookCommitUser) - - object WebHookCommit{ - def apply(git: Git, repositoryInfo: RepositoryInfo, commit: CommitInfo): WebHookCommit = { - val diffs = JGitUtil.getDiffs(git, commit.id, false) - val commitUrl = repositoryInfo.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/commit/" + commit.id - WebHookCommit( - id = commit.id, - message = commit.fullMessage, - timestamp = commit.commitTime, - url = commitUrl, - added = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD) => x.newPath }, - removed = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath }, - modified = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD && - x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath }, - author = WebHookCommitUser.author(commit), - committer = WebHookCommitUser.committer(commit) - ) - } - } - - case class WebHookApiUser( - login: String, - email: String, - `type`: String, - site_admin: Boolean, - created_at: Date) { - val url = ApiPath(s"/api/v3/users/${login}") - val html_url = ApiPath(s"/${login}") - // val followers_url = ApiPath(s"/api/v3/users/${login}/followers") - // val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}") - // val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}") - // val starred_url = ApiPath(s"/api/v3/users/${login}/starred{/owner}{/repo}") - // val subscriptions_url = ApiPath(s"/api/v3/users/${login}/subscriptions") - // val organizations_url = ApiPath(s"/api/v3/users/${login}/orgs") - // val repos_url = ApiPath(s"/api/v3/users/${login}/repos") - // val events_url = ApiPath(s"/api/v3/users/${login}/events{/privacy}") - // val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events") - } - - object WebHookApiUser{ - def apply(user: Account): WebHookApiUser = WebHookApiUser( - login = user.fullName, - email = user.mailAddress, - `type` = if(user.isGroupAccount){ "Organization" }else{ "User" }, - site_admin = user.isAdmin, - created_at = user.registeredDate - ) - } - - case class WebHookCommitUser( - name: String, - email: String, - date: Date) - - object WebHookCommitUser { - def author(commit: CommitInfo): WebHookCommitUser = - WebHookCommitUser( - name = commit.authorName, - email = commit.authorEmailAddress, - date = commit.authorTime) - def committer(commit: CommitInfo): WebHookCommitUser = - WebHookCommitUser( - name = commit.committerName, - email = commit.committerEmailAddress, - date = commit.commitTime) - } - - // https://developer.github.com/v3/repos/ - case class WebHookRepository( - name: String, - full_name: String, - description: String, - watchers: Int, - forks: Int, - `private`: Boolean, - default_branch: String, - owner: WebHookApiUser) { - val forks_count = forks - val watchers_coun = watchers - val url = ApiPath(s"/api/v3/repos/${full_name}") - val http_url = ApiPath(s"/git/${full_name}.git") - val clone_url = ApiPath(s"/git/${full_name}.git") - val html_url = ApiPath(s"/${full_name}") - } - - object WebHookRepository{ - def apply( - repository: Repository, - owner: WebHookApiUser, - forkedCount: Int =0, - watchers: Int = 0): WebHookRepository = - WebHookRepository( - name = repository.repositoryName, - full_name = s"${repository.userName}/${repository.repositoryName}", - description = repository.description.getOrElse(""), - watchers = 0, - forks = forkedCount, - `private` = repository.isPrivate, - default_branch = repository.defaultBranch, - owner = owner - ) - - def apply(repositoryInfo: RepositoryInfo, owner: WebHookApiUser): WebHookRepository = - WebHookRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount) - - def apply(repositoryInfo: RepositoryInfo, owner: Account): WebHookRepository = - this(repositoryInfo.repository, WebHookApiUser(owner)) - - } - - // https://developer.github.com/v3/pulls/ - case class WebHookPullRequest( - number: Int, - updated_at: Date, - created_at: Date, - head: WebHookPullRequestCommit, - base: WebHookPullRequestCommit, - mergeable: Option[Boolean], - title: String, - body: String, - user: WebHookApiUser) { - val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}") - //val diff_url = ApiPath("${base.repo.html_url.path}/pull/${number}.diff") - //val patch_url = ApiPath("${base.repo.html_url.path}/pull/${number}.patch") - val url = ApiPath(s"${base.repo.url.path}/pulls/${number}") - //val issue_url = ApiPath("${base.repo.url.path}/issues/${number}") - //val commits_url = ApiPath("${base.repo.url.path}/pulls/${number}/commits") - //val review_comments_url = ApiPath("${base.repo.url.path}/pulls/${number}/comments") - //val review_comment_url = ApiPath("${base.repo.url.path}/pulls/comments/{number}") - //val comments_url = ApiPath("${base.repo.url.path}/issues/${number}/comments") - val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}") - } - - object WebHookPullRequest{ - def apply(issue: Issue, pullRequest: PullRequest, headRepo: WebHookRepository, baseRepo: WebHookRepository, user: WebHookApiUser): WebHookPullRequest = WebHookPullRequest( - number = issue.issueId, - updated_at = issue.updatedDate, - created_at = issue.registeredDate, - head = WebHookPullRequestCommit( - sha = pullRequest.commitIdTo, - ref = pullRequest.requestBranch, - repo = headRepo), - base = WebHookPullRequestCommit( - sha = pullRequest.commitIdFrom, - ref = pullRequest.branch, - repo = baseRepo), - mergeable = None, // TODO: need check mergeable. - title = issue.title, - body = issue.content.getOrElse(""), - user = user - ) - } - - case class WebHookPullRequestCommit( - label: String, - sha: String, - ref: String, - repo: WebHookRepository, - user: WebHookApiUser) - - object WebHookPullRequestCommit{ - def apply(sha: String, - ref: String, - repo: WebHookRepository): WebHookPullRequestCommit = - WebHookPullRequestCommit( - label = s"${repo.owner.login}:${ref}", - sha = sha, - ref = ref, - repo = repo, - user = repo.owner) - } - - // https://developer.github.com/v3/issues/ - case class WebHookIssue( - number: Int, - title: String, - user: WebHookApiUser, - // labels, - state: String, - created_at: Date, - updated_at: Date, - body: String) - - object WebHookIssue{ - def apply(issue: Issue, user: WebHookApiUser): WebHookIssue = - WebHookIssue( - number = issue.issueId, - title = issue.title, - user = user, - state = if(issue.closed){ "closed" }else{ "open" }, - body = issue.content.getOrElse(""), - created_at = issue.registeredDate, - updated_at = issue.updatedDate) - } - - // https://developer.github.com/v3/issues/comments/ - case class WebHookComment( - id: Int, - user: WebHookApiUser, - body: String, - created_at: Date, - updated_at: Date) - - object WebHookComment{ - def apply(comment: IssueComment, user: WebHookApiUser): WebHookComment = - WebHookComment( - id = comment.commentId, - user = user, - body = comment.content, - created_at = comment.registeredDate, - updated_at = comment.updatedDate) - } - - // https://developer.github.com/v3/issues/comments/#create-a-comment - case class CreateAComment(body: String) - - - // https://developer.github.com/v3/repos/commits/ - case class WebHookCommitListItemParent(sha: String)(repoFullName:String){ - val url = ApiPath(s"/api/v3/repos/${repoFullName}/commits/${sha}") - } - case class WebHookCommitListItemCommit( - message: String, - author: WebHookCommitUser, - committer: WebHookCommitUser)(sha:String, repoFullName: String) { - val url = ApiPath(s"/api/v3/repos/${repoFullName}/git/commits/${sha}") - } - - case class WebHookCommitListItem( - sha: String, - commit: WebHookCommitListItemCommit, - author: Option[WebHookApiUser], - committer: Option[WebHookApiUser], - parents: Seq[WebHookCommitListItemParent])(repoFullName: String) { - val url = ApiPath(s"/api/v3/repos/${repoFullName}/commits/${sha}") - } - - object WebHookCommitListItem { - def apply(commit: CommitInfo, repoFullName:String): WebHookCommitListItem = WebHookCommitListItem( - sha = commit.id, - commit = WebHookCommitListItemCommit( - message = commit.fullMessage, - author = WebHookCommitUser.author(commit), - committer = WebHookCommitUser.committer(commit) - )(commit.id, repoFullName), - author = None, - committer = None, - parents = commit.parents.map(WebHookCommitListItemParent(_)(repoFullName)))(repoFullName) - } - - /** - * https://developer.github.com/v3/repos/statuses/#create-a-status - */ - case class CreateAStatus( - /* state is Required. The state of the status. Can be one of pending, success, error, or failure. */ - state: String, - /* context is a string label to differentiate this status from the status of other systems. Default: "default" */ - context: Option[String], - /* The target URL to associate with this status. This URL will be linked from the GitHub UI to allow users to easily see the ‘source’ of the Status. */ - target_url: Option[String], - /* description is a short description of the status.*/ - description: Option[String] - ) { - def isValid: Boolean = { - CommitState.valueOf(state).isDefined && - target_url.filterNot(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length<255).isEmpty && - context.filterNot(f => f.length<255).isEmpty && - description.filterNot(f => f.length<1000).isEmpty - } - } - - /** - * https://developer.github.com/v3/repos/statuses/#create-a-status - * https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref - */ - case class WebHookCommitStatus( - created_at: Date, - updated_at: Date, - state: String, - target_url: Option[String], - description: Option[String], - id: Int, - context: String, - creator: WebHookApiUser - )(sha: String, repoFullName: String) { - def url = ApiPath(s"/api/v3/repos/${repoFullName}/commits/${sha}/statuses") - } - - object WebHookCommitStatus { - def apply(status: CommitStatus, creator:WebHookApiUser): WebHookCommitStatus = WebHookCommitStatus( - created_at = status.registeredDate, - updated_at = status.updatedDate, - state = status.state.name, - target_url = status.targetUrl, - description= status.description, - id = status.commitStatusId, - context = status.context, - creator = creator - )(status.commitId, s"${status.userName}/${status.repositoryName}") - } - - /** - * https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref - */ - case class WebHookCombinedCommitStatus( - state: String, - sha: String, - total_count: Int, - statuses: Iterable[WebHookCommitStatus], - repository: WebHookRepository){ - // val commit_url = ApiPath(s"/api/v3/repos/${repository.full_name}/${sha}") - val url = ApiPath(s"/api/v3/repos/${repository.full_name}/commits/${sha}/status") - } - object WebHookCombinedCommitStatus { - def apply(sha:String, statuses: Iterable[(CommitStatus, Account)], repository:WebHookRepository): WebHookCombinedCommitStatus = WebHookCombinedCommitStatus( - state = CommitState.combine(statuses.map(_._1.state).toSet).name, - sha = sha, - total_count= statuses.size, - statuses = statuses.map{ case (s, a)=> WebHookCommitStatus(s, WebHookApiUser(a)) }, - repository = repository) + repository = ApiRepository(repository, repositoryUser), + issue = ApiIssue(issue, ApiUser(issueUser)), + comment = ApiComment(comment, ApiUser(commentUser)), + sender = ApiUser(sender)) } } diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala index a569db5..a4e94ed 100644 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -180,7 +180,7 @@ } // call web hook - implicit val apiContext = ApiContext(baseUrl) + implicit val apiContext = api.JsonFormat.Context(baseUrl) callWebHookOf(owner, repository, "push"){ for(pusherAccount <- getAccountByUserName(pusher); ownerAccount <- getAccountByUserName(owner); diff --git a/src/main/scala/util/Implicits.scala b/src/main/scala/util/Implicits.scala index c9c2919..02ecb82 100644 --- a/src/main/scala/util/Implicits.scala +++ b/src/main/scala/util/Implicits.scala @@ -14,7 +14,7 @@ // Convert to slick session. implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request) - implicit def context2ApiContext(implicit context: app.Context): service.WebHookService.ApiContext = service.WebHookService.ApiContext(context.baseUrl) + implicit def context2ApiJsonFormatContext(implicit context: app.Context): api.JsonFormat.Context = api.JsonFormat.Context(context.baseUrl) implicit class RichSeq[A](seq: Seq[A]) { diff --git a/src/main/scala/util/RepoitoryName.scala b/src/main/scala/util/RepoitoryName.scala new file mode 100644 index 0000000..1642d9b --- /dev/null +++ b/src/main/scala/util/RepoitoryName.scala @@ -0,0 +1,18 @@ +package util + +case class RepositoryName(owner:String, name:String){ + val fullName = s"${owner}/${name}" +} + +object RepositoryName{ + def apply(fullName: String): RepositoryName = { + fullName.split("/").toList match { + case owner :: name :: Nil => RepositoryName(owner, name) + case _ => throw new IllegalArgumentException(s"${fullName} is not repositoryName (only 'owner/name')") + } + } + def apply(repository: model.Repository): RepositoryName = RepositoryName(repository.userName, repository.repositoryName) + def apply(repository: util.JGitUtil.RepositoryInfo): RepositoryName = RepositoryName(repository.owner, repository.name) + def apply(repository: service.RepositoryService.RepositoryInfo): RepositoryName = RepositoryName(repository.owner, repository.name) + def apply(repository: model.CommitStatus): RepositoryName = RepositoryName(repository.userName, repository.repositoryName) +} diff --git a/src/test/scala/api/JsonFormatSpec.scala b/src/test/scala/api/JsonFormatSpec.scala new file mode 100644 index 0000000..a000a52 --- /dev/null +++ b/src/test/scala/api/JsonFormatSpec.scala @@ -0,0 +1,275 @@ +package api +import org.specs2.mutable.Specification +import java.util.{Date, Calendar, TimeZone} +import util.RepositoryName +import org.json4s.jackson.JsonMethods.{pretty, parse} +import org.json4s._ +import org.specs2.matcher._ +class JsonFormatSpec extends Specification { + val date1 = { + val d = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + d.set(2011,3,14,16,0,49) + d.getTime + } + val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e" + val repo1Name = RepositoryName("octocat/Hello-World") + implicit val context = JsonFormat.Context("http://gitbucket.exmple.com") + + val apiUser = ApiUser( + login= "octocat", + email= "octocat@example.com", + `type`= "User", + site_admin= false, + created_at= date1) + val apiUserJson = """{ + "login":"octocat", + "email":"octocat@example.com", + "type":"User", + "site_admin":false, + "created_at":"2011-04-14T16:00:49Z", + "url":"http://gitbucket.exmple.com/api/v3/users/octocat", + "html_url":"http://gitbucket.exmple.com/octocat" + }""" + + val repository = ApiRepository( + name = repo1Name.name, + full_name = repo1Name.fullName, + description = "This your first repo!", + watchers = 0, + forks = 0, + `private` = false, + default_branch = "master", + owner = apiUser) + val repositoryJson = s"""{ + "name" : "Hello-World", + "full_name" : "octocat/Hello-World", + "description" : "This your first repo!", + "watchers" : 0, + "forks" : 0, + "private" : false, + "default_branch" : "master", + "owner" : $apiUserJson, + "forks_count" : 0, + "watchers_coun" : 0, + "url" : "${context.baseUrl}/api/v3/repos/octocat/Hello-World", + "http_url" : "${context.baseUrl}/git/octocat/Hello-World.git", + "clone_url" : "${context.baseUrl}/git/octocat/Hello-World.git", + "html_url" : "${context.baseUrl}/octocat/Hello-World" + }""" + + val apiCommitStatus = ApiCommitStatus( + created_at = date1, + updated_at = date1, + state = "success", + target_url = Some("https://ci.example.com/1000/output"), + description = Some("Build has completed successfully"), + id = 1, + context = "Default", + creator = apiUser + )(sha1, repo1Name) + val apiCommitStatusJson = s"""{ + "created_at":"2011-04-14T16:00:49Z", + "updated_at":"2011-04-14T16:00:49Z", + "state":"success", + "target_url":"https://ci.example.com/1000/output", + "description":"Build has completed successfully", + "id":1, + "context":"Default", + "creator":$apiUserJson, + "url": "http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/statuses" + }""" + + val apiComment = ApiComment( + id =1, + user = apiUser, + body= "Me too", + created_at= date1, + updated_at= date1) + val apiCommentJson = s"""{ + "id": 1, + "body": "Me too", + "user": $apiUserJson, + "created_at": "2011-04-14T16:00:49Z", + "updated_at": "2011-04-14T16:00:49Z" + }""" + + val apiPersonIdent = ApiPersonIdent("Monalisa Octocat","support@example.com",date1) + val apiPersonIdentJson = """ { + "name": "Monalisa Octocat", + "email": "support@example.com", + "date": "2011-04-14T16:00:49Z" + }""" + + val apiCommitListItem = ApiCommitListItem( + sha = sha1, + commit = ApiCommitListItem.Commit( + message = "Fix all the bugs", + author = apiPersonIdent, + committer = apiPersonIdent + )(sha1, repo1Name), + author = Some(apiUser), + committer= Some(apiUser), + parents= Seq(ApiCommitListItem.Parent("6dcb09b5b57875f334f61aebed695e2e4193db5e")(repo1Name)))(repo1Name) + val apiCommitListItemJson = s"""{ + "url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "commit": { + "url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "author": $apiPersonIdentJson, + "committer": $apiPersonIdentJson, + "message": "Fix all the bugs" + }, + "author": $apiUserJson, + "committer": $apiUserJson, + "parents": [ + { + "url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + ] + }""" + + val apiCombinedCommitStatus = ApiCombinedCommitStatus( + state = "success", + sha = sha1, + total_count = 2, + statuses = List(apiCommitStatus), + repository = repository) + val apiCombinedCommitStatusJson = s"""{ + "state": "success", + "sha": "$sha1", + "total_count": 2, + "statuses": [ $apiCommitStatusJson ], + "repository": $repositoryJson, + "url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/$sha1/status" + }""" + + val apiIssue = ApiIssue( + number = 1347, + title = "Found a bug", + user = apiUser, + state = "open", + body = "I'm having a problem with this.", + created_at = date1, + updated_at = date1) + val apiIssueJson = s"""{ + "number": 1347, + "state": "open", + "title": "Found a bug", + "body": "I'm having a problem with this.", + "user": $apiUserJson, + "created_at": "2011-04-14T16:00:49Z", + "updated_at": "2011-04-14T16:00:49Z" + }""" + + val apiPullRequest = ApiPullRequest( + number = 1347, + updated_at = date1, + created_at = date1, + head = ApiPullRequest.Commit( + sha = sha1, + ref = "new-topic", + repo = repository)("octocat"), + base = ApiPullRequest.Commit( + sha = sha1, + ref = "master", + repo = repository)("octocat"), + mergeable = None, + title = "new-feature", + body = "Please pull these awesome changes", + user = apiUser + ) + val apiPullRequestJson = s"""{ + "url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347", + "html_url": "${context.baseUrl}/octocat/Hello-World/pull/1347", + // "diff_url": "${context.baseUrl}/octocat/Hello-World/pull/1347.diff", + // "patch_url": "${context.baseUrl}/octocat/Hello-World/pull/1347.patch", + // "issue_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347", + "commits_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347/commits", + "review_comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/1347/comments", + "review_comment_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/pulls/comments/{number}", + "comments_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/issues/1347/comments", + "statuses_url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "number": 1347, + // "state": "open", + "title": "new-feature", + "body": "Please pull these awesome changes", + "created_at": "2011-04-14T16:00:49Z", + "updated_at": "2011-04-14T16:00:49Z", + // "closed_at": "2011-04-14T16:00:49Z", + // "merged_at": "2011-04-14T16:00:49Z", + "head": { + "label": "new-topic", + "ref": "new-topic", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "user": $apiUserJson, + "repo": $repositoryJson + }, + "base": { + "label": "master", + "ref": "master", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "user": $apiUserJson, + "repo": $repositoryJson + }, + "user": $apiUserJson + // "merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6", + // "merged": false, + // "mergeable": true, + // "merged_by": $$apiUserJson, + // "comments": 10, + // "commits": 3, + // "additions": 100, + // "deletions": 3, + // "changed_files": 5 + }""" + def beFormatted(json2Arg:String) = new Matcher[String] { + def apply[S <: String](e: Expectable[S]) = { + import java.util.regex.Pattern + val json2 = Pattern.compile("""^\s*//.*$""", Pattern.MULTILINE).matcher(json2Arg).replaceAll("") + val js2 = try{ + parse(json2) + }catch{ + case e:com.fasterxml.jackson.core.JsonParseException => { + val p = java.lang.Math.max(e.getLocation.getCharOffset()-10,0).toInt + val message = json2.substring(p,java.lang.Math.min(p+100,json2.length)) + throw new com.fasterxml.jackson.core.JsonParseException(message + e.getMessage , e.getLocation) + } + } + val js1 = parse(e.value) + result(js1 == js2, + "expected", + { + val diff = js2 diff js1 + s"${pretty(js1)} is not ${pretty(js2)} \n\n ${pretty(Extraction.decompose(diff)(org.json4s.DefaultFormats))}" + }, + e) + } + } + "JsonFormat" should { + "apiUser" in { + JsonFormat(apiUser) must beFormatted(apiUserJson) + } + "repository" in { + JsonFormat(repository) must beFormatted(repositoryJson) + } + "apiComment" in { + JsonFormat(apiComment) must beFormatted(apiCommentJson) + } + "apiCommitListItem" in { + JsonFormat(apiCommitListItem) must beFormatted(apiCommitListItemJson) + } + "apiCommitStatus" in { + JsonFormat(apiCommitStatus) must beFormatted(apiCommitStatusJson) + } + "apiCombinedCommitStatus" in { + JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson) + } + "apiIssue" in { + JsonFormat(apiIssue) must beFormatted(apiIssueJson) + } + "apiPullRequest" in { + JsonFormat(apiPullRequest) must beFormatted(apiPullRequestJson) + } + } +}