From 041b64e07f187bddb94d5d358fb180f12c9e5d00 Mon Sep 17 00:00:00 2001 From: Petrisor Lacatus Date: Sun, 3 Dec 2017 09:06:59 +0200 Subject: [PATCH 1/5] Model changes to support attachments in apis --- models/attachment.go | 60 +++++++++++++++++++++++++++++++++++++++++--- models/release.go | 37 +++++++++++++++++++-------- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/models/attachment.go b/models/attachment.go index 9c91613e1036c..a763c4dc324a4 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -12,17 +12,19 @@ import ( "path" "time" + api "code.gitea.io/sdk/gitea" gouuid "github.com/satori/go.uuid" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) // Attachment represent a attachment of issue/comment/release. type Attachment struct { - ID int64 `xorm:"pk autoincr"` - UUID string `xorm:"uuid UNIQUE"` - IssueID int64 `xorm:"INDEX"` - ReleaseID int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + IssueID int64 `xorm:"INDEX"` + ReleaseID int64 `xorm:"INDEX"` CommentID int64 Name string DownloadCount int64 `xorm:"DEFAULT 0"` @@ -49,6 +51,24 @@ func (a *Attachment) IncreaseDownloadCount() error { return nil } +// APIFormat converts a Attachment to an api.Attachment +func (a *Attachment) APIFormat() *api.Attachment { + apiAttachment := &api.Attachment{ + ID: a.ID, + Created: a.Created, + Name: a.Name, + UUID: a.UUID, + DownloadURL: setting.AppURL + "attachments/" + a.UUID, + DownloadCount: a.DownloadCount, + } + fileSize, err := a.getSize() + log.Warn("Error getting the file size for attachment %s. ", a.UUID, err) + if err == nil { + apiAttachment.Size = fileSize + } + return apiAttachment +} + // AttachmentLocalPath returns where attachment is stored in local file // system based on given UUID. func AttachmentLocalPath(uuid string) string { @@ -112,11 +132,32 @@ func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { return attachments, e.In("uuid", uuids).Find(&attachments) } +// getSize gets the size of the attachment in bytes +func (a *Attachment) getSize() (int64, error) { + info, err := os.Stat(a.LocalPath()) + if err != nil { + return 0, err + } + return info.Size(), nil +} + // GetAttachmentByUUID returns attachment by given UUID. func GetAttachmentByUUID(uuid string) (*Attachment, error) { return getAttachmentByUUID(x, uuid) } +// GetAttachmentByID returns attachment by given ID. +func GetAttachmentByID(id int64) (*Attachment, error) { + attach := &Attachment{ID: id} + has, err := x.Get(attach) + if err != nil { + return nil, err + } else if !has { + return nil, ErrAttachmentNotExist{id, ""} + } + return attach, nil +} + func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { attachments := make([]*Attachment, 0, 10) return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) @@ -137,6 +178,17 @@ func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) return attachments, x.Where("comment_id=?", commentID).Find(&attachments) } +// GetAttachmentsByReleaseID returns all attachments of a release +func GetAttachmentsByReleaseID(releaseID int64) ([]*Attachment, error) { + return getAttachmentsByReleaseID(x, releaseID) +} + +// getAttachmentsByReleaseID returns all attachments of a release +func getAttachmentsByReleaseID(e Engine, releaseID int64) ([]*Attachment, error) { + attachments := make([]*Attachment, 0, 10) + return attachments, e.Where("release_id=?", releaseID).Find(&attachments) +} + // DeleteAttachment deletes the given attachment and optionally the associated file. func DeleteAttachment(a *Attachment, remove bool) error { _, err := DeleteAttachments([]*Attachment{a}, remove) diff --git a/models/release.go b/models/release.go index ddaf6d6aa1a42..f47bffc651526 100644 --- a/models/release.go +++ b/models/release.go @@ -28,13 +28,13 @@ type Release struct { LowerTagName string Target string Title string - Sha1 string `xorm:"VARCHAR(40)"` + Sha1 string `xorm:"VARCHAR(40)"` NumCommits int64 - NumCommitsBehind int64 `xorm:"-"` - Note string `xorm:"TEXT"` - IsDraft bool `xorm:"NOT NULL DEFAULT false"` - IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` - IsTag bool `xorm:"NOT NULL DEFAULT false"` + NumCommitsBehind int64 `xorm:"-"` + Note string `xorm:"TEXT"` + IsDraft bool `xorm:"NOT NULL DEFAULT false"` + IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` + IsTag bool `xorm:"NOT NULL DEFAULT false"` Attachments []*Attachment `xorm:"-"` @@ -68,6 +68,14 @@ func (r *Release) loadAttributes(e Engine) error { return err } } + // load the attachments of this release + if r.Attachments == nil { + attachments, err := GetAttachmentsByReleaseID(r.ID) + if err != nil { + return err + } + r.Attachments = attachments + } return nil } @@ -94,6 +102,10 @@ func (r *Release) TarURL() string { // APIFormat convert a Release to api.Release func (r *Release) APIFormat() *api.Release { + apiAttachments := make([]*api.Attachment, len(r.Attachments)) + for i := range r.Attachments { + apiAttachments[i] = r.Attachments[i].APIFormat() + } return &api.Release{ ID: r.ID, TagName: r.TagName, @@ -107,6 +119,7 @@ func (r *Release) APIFormat() *api.Release { CreatedAt: r.Created, PublishedAt: r.Created, Publisher: r.Publisher.APIFormat(), + Attachments: apiAttachments, } } @@ -233,9 +246,10 @@ func GetReleaseByID(id int64) (*Release, error) { // FindReleasesOptions describes the conditions to Find releases type FindReleasesOptions struct { - IncludeDrafts bool - IncludeTags bool - TagNames []string + IncludeDrafts bool + IncludeTags bool + ExcludePrereleases bool + TagNames []string } func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { @@ -248,13 +262,16 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { if !opts.IncludeTags { cond = cond.And(builder.Eq{"is_tag": false}) } + if opts.ExcludePrereleases { + cond = cond.And(builder.Eq{"is_prerelease": false}) + } if len(opts.TagNames) > 0 { cond = cond.And(builder.In("tag_name", opts.TagNames)) } return cond } -// GetReleasesByRepoID returns a list of releases of repository. +// GetReleasesByRepoID returns a list of releases of repository. The results are sorted by created date and id descending func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions, page, pageSize int) (rels []*Release, err error) { if page <= 0 { page = 1 From 87bcd3bfa689afdc53388b1f0e614e8b6f05b63c Mon Sep 17 00:00:00 2001 From: Petrisor Lacatus Date: Sun, 3 Dec 2017 09:08:04 +0200 Subject: [PATCH 2/5] Router API changes to support releases attachments in apis --- routers/api/v1/api.go | 10 +++++ routers/api/v1/repo/release.go | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 2aa27af09147d..970a8afc5b49e 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -461,6 +461,16 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo("/:id").Get(repo.GetRelease). Patch(reqToken(), reqRepoWriter(), bind(api.EditReleaseOption{}), repo.EditRelease). Delete(reqToken(), reqRepoWriter(), repo.DeleteRelease) + m.Combo("/latest").Get(repo.GetLatestRelease) + m.Group("/:id", func() { + m.Combo("").Get(repo.GetRelease). + Patch(bind(api.EditReleaseOption{}), repo.EditRelease). + Delete(repo.DeleteRelease) + m.Group("/assets", func() { + m.Combo("").Get(repo.ListReleaseAttachments) + m.Combo("/:assetId").Get(repo.GetReleaseAttachment) + }) + }) }) m.Post("/mirror-sync", reqToken(), reqRepoWriter(), repo.MirrorSync) m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index 17cddc5652224..86baadf83aaf8 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -54,6 +54,56 @@ func GetRelease(ctx *context.APIContext) { ctx.JSON(200, release.APIFormat()) } +// ListReleaseAttachments get all the attachments of a release +func ListReleaseAttachments(ctx *context.APIContext) { + id := ctx.ParamsInt64(":id") + release, err := models.GetReleaseByID(id) + if err != nil { + ctx.Error(500, "GetReleaseByID", err) + return + } + if release.RepoID != ctx.Repo.Repository.ID { + ctx.Status(404) + return + } + // load the attachments of this release + attachments, err := models.GetAttachmentsByReleaseID(id) + if err != nil { + ctx.Error(500, "GetAttachmentsByReleaseID", err) + return + } + // build the attachment list + apiAttachments := make([]*api.Attachment, len(attachments)) + for i := range attachments { + apiAttachments[i] = attachments[i].APIFormat() + } + ctx.JSON(200, apiAttachments) +} + +// GetReleaseAttachment get a single attachment of a release +func GetReleaseAttachment(ctx *context.APIContext) { + id := ctx.ParamsInt64(":id") + attachmentID := ctx.ParamsInt64(":assetId") + release, err := models.GetReleaseByID(id) + if err != nil { + ctx.Error(500, "GetReleaseByID", err) + return + } + if release.RepoID != ctx.Repo.Repository.ID { + ctx.Status(404) + return + } + // load the attachments of this release + attachment, err := models.GetAttachmentByID(attachmentID) + // if the attachment was not found, or it was found but is not associated with this release, then throw 404 + if err != nil || id != attachment.ReleaseID { + ctx.Status(404) + return + } + + ctx.JSON(200, attachment.APIFormat()) +} + // ListReleases list a repository's releases func ListReleases(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/releases repository repoListReleases @@ -94,6 +144,29 @@ func ListReleases(ctx *context.APIContext) { ctx.JSON(200, rels) } +// GetLatestRelease Gets the latest release in a repository. Draft releases and prereleases are not returned +func GetLatestRelease(ctx *context.APIContext) { + // we set the pageSize to 1 to get back only one release + releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ + IncludeDrafts: false, + ExcludePrereleases: true, + }, 1, 1) + if err != nil { + ctx.Error(500, "GetReleasesByRepoID", err) + return + } + if len(releases) <= 0 { + // no releases found, just return 404 + ctx.Status(404) + return + } + if err := releases[0].LoadAttributes(); err != nil { + ctx.Error(500, "LoadAttributes", err) + return + } + ctx.JSON(200, releases[0].APIFormat()) +} + // CreateRelease create a release func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) { // swagger:operation GET /repos/{owner}/{repo}/releases repository repoCreateRelease From c788b3d95a03f4739725ea2f40a3cc3ae98fb1c3 Mon Sep 17 00:00:00 2001 From: Petrisor Lacatus Date: Sun, 3 Dec 2017 09:34:34 +0200 Subject: [PATCH 3/5] Model changes to support attachments in apis --- models/attachment.go | 8 ++++---- models/release.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/models/attachment.go b/models/attachment.go index a763c4dc324a4..e50cc0dd7b567 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -21,10 +21,10 @@ import ( // Attachment represent a attachment of issue/comment/release. type Attachment struct { - ID int64 `xorm:"pk autoincr"` - UUID string `xorm:"uuid UNIQUE"` - IssueID int64 `xorm:"INDEX"` - ReleaseID int64 `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + IssueID int64 `xorm:"INDEX"` + ReleaseID int64 `xorm:"INDEX"` CommentID int64 Name string DownloadCount int64 `xorm:"DEFAULT 0"` diff --git a/models/release.go b/models/release.go index f47bffc651526..d62279f72ff7d 100644 --- a/models/release.go +++ b/models/release.go @@ -28,13 +28,13 @@ type Release struct { LowerTagName string Target string Title string - Sha1 string `xorm:"VARCHAR(40)"` + Sha1 string `xorm:"VARCHAR(40)"` NumCommits int64 - NumCommitsBehind int64 `xorm:"-"` - Note string `xorm:"TEXT"` - IsDraft bool `xorm:"NOT NULL DEFAULT false"` - IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` - IsTag bool `xorm:"NOT NULL DEFAULT false"` + NumCommitsBehind int64 `xorm:"-"` + Note string `xorm:"TEXT"` + IsDraft bool `xorm:"NOT NULL DEFAULT false"` + IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` + IsTag bool `xorm:"NOT NULL DEFAULT false"` Attachments []*Attachment `xorm:"-"` From a253b347d18d8bcecf673b8812dfbdef25d3b653 Mon Sep 17 00:00:00 2001 From: Petrisor Lacatus Date: Mon, 4 Dec 2017 23:16:08 +0200 Subject: [PATCH 4/5] Fixed adding the same endpoints twice. Fixed small typo --- routers/api/v1/api.go | 7 ++----- routers/api/v1/repo/release.go | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 970a8afc5b49e..18e46d790b47b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -458,14 +458,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/releases", func() { m.Combo("").Get(repo.ListReleases). Post(reqToken(), reqRepoWriter(), bind(api.CreateReleaseOption{}), repo.CreateRelease) - m.Combo("/:id").Get(repo.GetRelease). - Patch(reqToken(), reqRepoWriter(), bind(api.EditReleaseOption{}), repo.EditRelease). - Delete(reqToken(), reqRepoWriter(), repo.DeleteRelease) m.Combo("/latest").Get(repo.GetLatestRelease) m.Group("/:id", func() { m.Combo("").Get(repo.GetRelease). - Patch(bind(api.EditReleaseOption{}), repo.EditRelease). - Delete(repo.DeleteRelease) + Patch(reqToken(), reqRepoWriter(), bind(api.EditReleaseOption{}), repo.EditRelease). + Delete(reqToken(), reqRepoWriter(), repo.DeleteRelease) m.Group("/assets", func() { m.Combo("").Get(repo.ListReleaseAttachments) m.Combo("/:assetId").Get(repo.GetReleaseAttachment) diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index 86baadf83aaf8..d969e69d16b84 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -144,7 +144,7 @@ func ListReleases(ctx *context.APIContext) { ctx.JSON(200, rels) } -// GetLatestRelease Gets the latest release in a repository. Draft releases and prereleases are not returned +// GetLatestRelease gets the latest release in a repository. Draft releases and prereleases are not returned func GetLatestRelease(ctx *context.APIContext) { // we set the pageSize to 1 to get back only one release releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ From 25b23c22c9abcb8bf189541e48bceadac5e23576 Mon Sep 17 00:00:00 2001 From: Petrisor Lacatus Date: Tue, 5 Dec 2017 16:15:42 +0200 Subject: [PATCH 5/5] Added swagger definitions to the new endpoints Attachments are in misc because they could be reused for issues and comments --- routers/api/v1/repo/release.go | 75 +++++++++++++++++++++++++++++++++- routers/api/v1/swagger/misc.go | 12 ++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index d969e69d16b84..04076629cc619 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -56,6 +56,30 @@ func GetRelease(ctx *context.APIContext) { // ListReleaseAttachments get all the attachments of a release func ListReleaseAttachments(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets repository getReleaseAttachments + // --- + // summary: List the assets (attachments in a release) + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: id + // in: path + // description: id of the release in the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/AttachmentList" id := ctx.ParamsInt64(":id") release, err := models.GetReleaseByID(id) if err != nil { @@ -82,6 +106,35 @@ func ListReleaseAttachments(ctx *context.APIContext) { // GetReleaseAttachment get a single attachment of a release func GetReleaseAttachment(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{assetId} repository getReleaseAttachment + // --- + // summary: Get a specific asset (attachment) from a release of a repository + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // - name: id + // in: path + // description: id of the release in the repo + // type: string + // required: true + // - name: assetId + // in: path + // description: assetId of the asset (attachment) in the release of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Attachment" id := ctx.ParamsInt64(":id") attachmentID := ctx.ParamsInt64(":assetId") release, err := models.GetReleaseByID(id) @@ -144,8 +197,28 @@ func ListReleases(ctx *context.APIContext) { ctx.JSON(200, rels) } -// GetLatestRelease gets the latest release in a repository. Draft releases and prereleases are not returned +// GetLatestRelease gets the latest release in a repository. Draft releases and prereleases are excluded func GetLatestRelease(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/releases/latest repository repoGetLatestRelease + // --- + // summary: Gets the latest release in a repository. Draft releases and prereleases are excluded + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Release" + // we set the pageSize to 1 to get back only one release releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{ IncludeDrafts: false, diff --git a/routers/api/v1/swagger/misc.go b/routers/api/v1/swagger/misc.go index 8ec0d53a7954e..afc91932d9220 100644 --- a/routers/api/v1/swagger/misc.go +++ b/routers/api/v1/swagger/misc.go @@ -13,3 +13,15 @@ type swaggerResponseServerVersion struct { // in:body Body api.ServerVersion `json:"body"` } + +// swagger:response Attachment +type swaggerResponseAttachment struct { + // in:body + Body api.Attachment `json:"body"` +} + +// swagger:response AttachmentList +type swaggerResponseAttachmentList struct { + // in:body + Body []api.Attachment `json:"body"` +}