Merge pull request #2266 from kounoike/pr-release-api

Add release API feature.
This commit is contained in:
Naoki Takezoe
2019-02-22 02:10:14 +09:00
committed by GitHub
8 changed files with 283 additions and 1 deletions

View File

@@ -0,0 +1,42 @@
package gitbucket.core.api
import gitbucket.core.model.{Account, ReleaseAsset, ReleaseTag}
import gitbucket.core.util.RepositoryName
case class ApiReleaseAsset(name: String, size: Long)(tag: String, fileName: String, repositoryName: RepositoryName) {
val label = name
val file_id = fileName
val browser_download_url = ApiPath(
s"/api/v3/repos/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
)
}
object ApiReleaseAsset {
def apply(asset: ReleaseAsset, repositoryName: RepositoryName): ApiReleaseAsset =
ApiReleaseAsset(asset.label, asset.size)(asset.tag, asset.fileName, repositoryName)
}
case class ApiRelease(
name: String,
tag_name: String,
body: Option[String],
author: ApiUser,
assets: Seq[ApiReleaseAsset]
)
object ApiRelease {
def apply(
release: ReleaseTag,
assets: Seq[ReleaseAsset],
author: Account,
repositoryName: RepositoryName
): ApiRelease =
ApiRelease(
release.name,
release.tag,
release.content,
ApiUser(author),
assets.map { asset =>
ApiReleaseAsset(asset, repositoryName)
}
)
}

View File

@@ -0,0 +1,10 @@
package gitbucket.core.api
case class CreateARelease(
tag_name: String,
target_commitish: Option[String],
name: Option[String],
body: Option[String],
draft: Option[Boolean],
prerelease: Option[Boolean]
)

View File

@@ -43,6 +43,8 @@ object JsonFormat {
FieldSerializer[ApiCommits.Tree]() +
FieldSerializer[ApiCommits.Stats]() +
FieldSerializer[ApiCommits.File]() +
FieldSerializer[ApiRelease]() +
FieldSerializer[ApiReleaseAsset]() +
ApiBranchProtection.enforcementLevelSerializer
def apiPathSerializer(c: Context) =

View File

@@ -15,6 +15,7 @@ class ApiController
with ApiIssueLabelControllerBase
with ApiOrganizationControllerBase
with ApiPullRequestControllerBase
with ApiReleaseControllerBase
with ApiRepositoryBranchControllerBase
with ApiRepositoryCollaboratorControllerBase
with ApiRepositoryCommitControllerBase
@@ -31,6 +32,7 @@ class ApiController
with PullRequestService
with CommitsService
with CommitStatusService
with ReleaseService
with RepositoryCreationService
with RepositoryCommitFileService
with IssueCreationService

View File

@@ -0,0 +1,184 @@
package gitbucket.core.controller.api
import java.io.{ByteArrayInputStream, File}
import gitbucket.core.api._
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{AccountService, ReleaseService}
import gitbucket.core.util.Directory.getReleaseFilesDir
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars.defining
import org.apache.commons.io.FileUtils
import org.scalatra.{Created, NoContent}
trait ApiReleaseControllerBase extends ControllerBase {
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
/**
* i. List releases for a repository
* https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
*/
get("/api/v3/repos/:owner/:repository/releases")(referrersOnly { repository =>
val releases = getReleases(repository.owner, repository.name)
JsonFormat(releases.map { rel =>
val assets = getReleaseAssets(repository.owner, repository.name, rel.tag)
ApiRelease(rel, assets, getAccountByUserName(rel.author).get, RepositoryName(repository))
})
})
/**
* ii. Get a single release
* https://developer.github.com/v3/repos/releases/#get-a-single-release
* GitBucket doesn't have release id
*/
/**
* iii. Get the latest release
* https://developer.github.com/v3/repos/releases/#get-the-latest-release
*/
get("/api/v3/repos/:owner/:repository/releases/latest")(referrersOnly { repository =>
getReleases(repository.owner, repository.name).lastOption
.map { release =>
val assets = getReleaseAssets(repository.owner, repository.name, release.tag)
JsonFormat(ApiRelease(release, assets, getAccountByUserName(release.author).get, RepositoryName(repository)))
}
.getOrElse {
NotFound()
}
})
/**
* iv. Get a release by tag name
* https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name
*/
get("/api/v3/repos/:owner/:repository/releases/tags/:tag")(referrersOnly { repository =>
val tag = params("tag")
getRelease(repository.owner, repository.name, tag)
.map { release =>
val assets = getReleaseAssets(repository.owner, repository.name, tag)
JsonFormat(ApiRelease(release, assets, getAccountByUserName(release.author).get, RepositoryName(repository)))
}
.getOrElse {
NotFound()
}
})
/**
* v. Create a release
* https://developer.github.com/v3/repos/releases/#create-a-release
*/
post("/api/v3/repos/:owner/:repository/releases")(writableUsersOnly { repository =>
(for {
data <- extractFromJsonBody[CreateARelease]
} yield {
createRelease(
repository.owner,
repository.name,
data.name.getOrElse(data.tag_name),
data.body,
data.tag_name,
context.loginAccount.get
)
val release = getRelease(repository.owner, repository.name, data.tag_name).get
val assets = getReleaseAssets(repository.owner, repository.name, data.tag_name)
JsonFormat(ApiRelease(release, assets, context.loginAccount.get, RepositoryName(repository)))
})
})
/**
* vi. Edit a release
* https://developer.github.com/v3/repos/releases/#edit-a-release
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
*/
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
(for {
data <- extractFromJsonBody[CreateARelease]
} yield {
val tag = params("tag")
updateRelease(repository.owner, repository.name, tag, data.name.getOrElse(data.tag_name), data.body)
val release = getRelease(repository.owner, repository.name, data.tag_name).get
val assets = getReleaseAssets(repository.owner, repository.name, data.tag_name)
JsonFormat(ApiRelease(release, assets, context.loginAccount.get, RepositoryName(repository)))
})
})
/**
* vii. Delete a release
* https://developer.github.com/v3/repos/releases/#delete-a-release
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
*/
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
val tag = params("tag")
deleteRelease(repository.owner, repository.name, tag)
NoContent()
})
/**
* viii. List assets for a release
* https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
*/
/**
* ix. Upload a release asset
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset
*/
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
val name = params("name")
val tag = params("tag")
getRelease(repository.owner, repository.name, tag)
.map {
release =>
defining(FileUtil.generateFileId) { fileId =>
val buf = new Array[Byte](request.inputStream.available())
request.inputStream.read(buf)
FileUtils.writeByteArrayToFile(
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tag + "/" + fileId)
),
buf
)
createReleaseAsset(
repository.owner,
repository.name,
tag,
fileId,
name,
request.contentLength.getOrElse(0),
context.loginAccount.get
)
getReleaseAsset(repository.owner, repository.name, tag, fileId)
.map { asset =>
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
}
.getOrElse {
ApiError("Unknown error")
}
}
}
.getOrElse(NotFound())
})
/**
* x. Get a single release asset
* https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
* Incompatibility info: GitHub requires only asset_id, but GitBucket requires tag and fileId(file_id).
*/
get("/api/v3/repos/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
val tag = params("tag")
val fileId = params("fileId")
getReleaseAsset(repository.owner, repository.name, tag, fileId)
.map { asset =>
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
}
.getOrElse(NotFound())
})
/*
* xi. Edit a release asset
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
*/
/*
* xii. Delete a release asset
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
*/
}

View File

@@ -73,7 +73,7 @@ trait ReleaseService {
}
def getReleases(owner: String, repository: String)(implicit s: Session): Seq[ReleaseTag] = {
ReleaseTags.filter(x => x.byRepository(owner, repository)).list
ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list
}
def getRelease(owner: String, repository: String, tag: String)(implicit s: Session): Option[ReleaseTag] = {

View File

@@ -388,6 +388,25 @@ object ApiSpecModels {
`object` = ApiObject(sha1)
)
val assetFileName = "010203040a0b0c0d"
val apiReleaseAsset = ApiReleaseAsset(
name = "release.zip",
size = 100
)(
tag = "tag1",
fileName = assetFileName,
repositoryName = repo1Name
)
val apiRelease = ApiRelease(
name = "release1",
tag_name = "tag1",
body = Some("content"),
author = apiUser,
assets = Seq(apiReleaseAsset)
)
// JSON String for APIs
val jsonUser = """{
@@ -649,4 +668,21 @@ object ApiSpecModels {
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonReleaseAsset =
s"""{
|"name":"release.zip",
|"size":100,
|"label":"release.zip",
|"file_id":"${assetFileName}",
|"browser_download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|}""".stripMargin
val jsonRelease =
s"""{
|"name":"release1",
|"tag_name":"tag1",
|"body":"content",
|"author":${jsonUser},
|"assets":[${jsonReleaseAsset}]
|}""".stripMargin
}

View File

@@ -73,4 +73,10 @@ class JsonFormatSpec extends FunSuite {
test("apiRef") {
assert(JsonFormat(apiRef) == expected(jsonRef))
}
test("apiReleaseAsset") {
assert(JsonFormat(apiReleaseAsset) == expected(jsonReleaseAsset))
}
test("apiRelease") {
assert(JsonFormat(apiRelease) == expected(jsonRelease))
}
}