mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Release API endpoints
This commit is contained in:
		
				
					committed by
					
						 Kim "BKC" Carlbäcker
						Kim "BKC" Carlbäcker
					
				
			
			
				
	
			
			
			
						parent
						
							b7e1bccc50
						
					
				
				
					commit
					0c301f7b5c
				
			| @@ -15,12 +15,16 @@ import ( | |||||||
| 	"code.gitea.io/git" | 	"code.gitea.io/git" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/process" | 	"code.gitea.io/gitea/modules/process" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
|  | 	api "code.gitea.io/sdk/gitea" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Release represents a release of repository. | // Release represents a release of repository. | ||||||
| type Release struct { | type Release struct { | ||||||
| 	ID               int64 `xorm:"pk autoincr"` | 	ID               int64 `xorm:"pk autoincr"` | ||||||
| 	RepoID           int64 | 	RepoID           int64 | ||||||
|  | 	Repo             *Repository `xorm:"-"` | ||||||
| 	PublisherID      int64 | 	PublisherID      int64 | ||||||
| 	Publisher        *User `xorm:"-"` | 	Publisher        *User `xorm:"-"` | ||||||
| 	TagName          string | 	TagName          string | ||||||
| @@ -53,6 +57,62 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (r *Release) loadAttributes(e Engine) error { | ||||||
|  | 	var err error | ||||||
|  | 	if r.Repo == nil { | ||||||
|  | 		r.Repo, err = GetRepositoryByID(r.RepoID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if r.Publisher == nil { | ||||||
|  | 		r.Publisher, err = GetUserByID(r.PublisherID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LoadAttributes load repo and publisher attributes for a realease | ||||||
|  | func (r *Release) LoadAttributes() error { | ||||||
|  | 	return r.loadAttributes(x) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // APIURL the api url for a release. release must have attributes loaded | ||||||
|  | func (r *Release) APIURL() string { | ||||||
|  | 	return fmt.Sprintf("%sapi/v1/%s/releases/%d", | ||||||
|  | 		setting.AppURL, r.Repo.FullName(), r.ID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ZipURL the zip url for a release. release must have attributes loaded | ||||||
|  | func (r *Release) ZipURL() string { | ||||||
|  | 	return fmt.Sprintf("%s/archive/%s.zip", r.Repo.HTMLURL(), r.TagName) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TarURL the tar.gz url for a release. release must have attributes loaded | ||||||
|  | func (r *Release) TarURL() string { | ||||||
|  | 	return fmt.Sprintf("%s/archive/%s.tar.gz", r.Repo.HTMLURL(), r.TagName) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // APIFormat convert a Release to api.Release | ||||||
|  | func (r *Release) APIFormat() *api.Release { | ||||||
|  | 	return &api.Release{ | ||||||
|  | 		ID:           r.ID, | ||||||
|  | 		TagName:      r.TagName, | ||||||
|  | 		Target:       r.Target, | ||||||
|  | 		Note:         r.Note, | ||||||
|  | 		URL:          r.APIURL(), | ||||||
|  | 		TarURL:       r.TarURL(), | ||||||
|  | 		ZipURL:       r.ZipURL(), | ||||||
|  | 		IsDraft:      r.IsDraft, | ||||||
|  | 		IsPrerelease: r.IsPrerelease, | ||||||
|  | 		CreatedAt:    r.Created, | ||||||
|  | 		PublishedAt:  r.Created, | ||||||
|  | 		Publisher:    r.Publisher.APIFormat(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // IsReleaseExist returns true if release with given tag name already exists. | // IsReleaseExist returns true if release with given tag name already exists. | ||||||
| func IsReleaseExist(repoID int64, tagName string) (bool, error) { | func IsReleaseExist(repoID int64, tagName string) (bool, error) { | ||||||
| 	if len(tagName) == 0 { | 	if len(tagName) == 0 { | ||||||
|   | |||||||
| @@ -331,6 +331,13 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 					m.Put("", user.Watch) | 					m.Put("", user.Watch) | ||||||
| 					m.Delete("", user.Unwatch) | 					m.Delete("", user.Unwatch) | ||||||
| 				}) | 				}) | ||||||
|  | 				m.Group("/releases", func() { | ||||||
|  | 					m.Combo("").Get(repo.ListReleases). | ||||||
|  | 						Post(bind(api.CreateReleaseOption{}), repo.CreateRelease) | ||||||
|  | 					m.Combo("/:id").Get(repo.GetRelease). | ||||||
|  | 						Patch(bind(api.EditReleaseOption{}), repo.EditRelease). | ||||||
|  | 						Delete(repo.DeleteRelease) | ||||||
|  | 				}) | ||||||
| 				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) | 				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) | ||||||
| 				m.Group("/pulls", func() { | 				m.Group("/pulls", func() { | ||||||
| 					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) | 					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).Post(reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) | ||||||
|   | |||||||
							
								
								
									
										186
									
								
								routers/api/v1/repo/release.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								routers/api/v1/repo/release.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | |||||||
|  | // Copyright 2016 The Gitea Authors. All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
|  | package repo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	api "code.gitea.io/sdk/gitea" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // GetRelease get a single release of a repository | ||||||
|  | func GetRelease(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 | ||||||
|  | 	} | ||||||
|  | 	if err := release.LoadAttributes(); err != nil { | ||||||
|  | 		ctx.Error(500, "LoadAttributes", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(200, release.APIFormat()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ListReleases list a repository's releases | ||||||
|  | func ListReleases(ctx *context.APIContext) { | ||||||
|  | 	releases, err := models.GetReleasesByRepoID(ctx.Repo.Repository.ID, 1, 2147483647) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "GetReleasesByRepoID", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	rels := make([]*api.Release, len(releases)) | ||||||
|  | 	access, err := models.AccessLevel(ctx.User, ctx.Repo.Repository) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "AccessLevel", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	for i, release := range releases { | ||||||
|  | 		if release.IsDraft && access < models.AccessModeWrite { | ||||||
|  | 			// hide drafts from users without push access | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if err := release.LoadAttributes(); err != nil { | ||||||
|  | 			ctx.Error(500, "LoadAttributes", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		rels[i] = release.APIFormat() | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(200, rels) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreateRelease create a release | ||||||
|  | func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) { | ||||||
|  | 	if ctx.Repo.AccessMode < models.AccessModeWrite { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !ctx.Repo.GitRepo.IsTagExist(form.TagName) { | ||||||
|  | 		ctx.Status(404) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	tag, err := ctx.Repo.GitRepo.GetTag(form.TagName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "GetTag", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	commit, err := tag.Commit() | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "Commit", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	commitsCount, err := commit.CommitsCount() | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "CommitsCount", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	rel := &models.Release{ | ||||||
|  | 		RepoID:       ctx.Repo.Repository.ID, | ||||||
|  | 		PublisherID:  ctx.User.ID, | ||||||
|  | 		Publisher:    ctx.User, | ||||||
|  | 		TagName:      form.TagName, | ||||||
|  | 		LowerTagName: strings.ToLower(form.TagName), | ||||||
|  | 		Target:       form.Target, | ||||||
|  | 		Title:        form.Title, | ||||||
|  | 		Sha1:         commit.ID.String(), | ||||||
|  | 		NumCommits:   commitsCount, | ||||||
|  | 		Note:         form.Note, | ||||||
|  | 		IsDraft:      form.IsDraft, | ||||||
|  | 		IsPrerelease: form.IsPrerelease, | ||||||
|  | 		CreatedUnix:  commit.Author.When.Unix(), | ||||||
|  | 	} | ||||||
|  | 	if err := models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil { | ||||||
|  | 		if models.IsErrReleaseAlreadyExist(err) { | ||||||
|  | 			ctx.Status(409) | ||||||
|  | 		} else { | ||||||
|  | 			ctx.Error(500, "CreateRelease", err) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(201, rel.APIFormat()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EditRelease edit a release | ||||||
|  | func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) { | ||||||
|  | 	if ctx.Repo.AccessMode < models.AccessModeWrite { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	id := ctx.ParamsInt64(":id") | ||||||
|  | 	rel, err := models.GetReleaseByID(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "GetReleaseByID", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if rel.RepoID != ctx.Repo.Repository.ID { | ||||||
|  | 		ctx.Status(404) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(form.TagName) > 0 { | ||||||
|  | 		rel.TagName = form.TagName | ||||||
|  | 	} | ||||||
|  | 	if len(form.Target) > 0 { | ||||||
|  | 		rel.Target = form.Target | ||||||
|  | 	} | ||||||
|  | 	if len(form.Title) > 0 { | ||||||
|  | 		rel.Title = form.Title | ||||||
|  | 	} | ||||||
|  | 	if len(form.Note) > 0 { | ||||||
|  | 		rel.Note = form.Note | ||||||
|  | 	} | ||||||
|  | 	if form.IsDraft != nil { | ||||||
|  | 		rel.IsDraft = *form.IsDraft | ||||||
|  | 	} | ||||||
|  | 	if form.IsPrerelease != nil { | ||||||
|  | 		rel.IsPrerelease = *form.IsPrerelease | ||||||
|  | 	} | ||||||
|  | 	if err := models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil { | ||||||
|  | 		ctx.Error(500, "UpdateRelease", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rel, err = models.GetReleaseByID(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.Error(500, "GetReleaseByID", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := rel.LoadAttributes(); err != nil { | ||||||
|  | 		ctx.Error(500, "LoadAttributes", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.JSON(200, rel.APIFormat()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteRelease delete a release from a repository | ||||||
|  | func DeleteRelease(ctx *context.APIContext) { | ||||||
|  | 	if ctx.Repo.AccessMode < models.AccessModeWrite { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	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 | ||||||
|  | 	} | ||||||
|  | 	if err := models.DeleteReleaseByID(id, ctx.User); err != nil { | ||||||
|  | 		ctx.Error(500, "DeleteReleaseByID", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.Status(204) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								vendor/code.gitea.io/sdk/gitea/hook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/code.gitea.io/sdk/gitea/hook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -115,7 +115,6 @@ func (c *Client) DeleteOrgHook(org string, id int64) error { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| // DeleteRepoHook delete one hook from a repository, with hook id | // DeleteRepoHook delete one hook from a repository, with hook id | ||||||
| func (c *Client) DeleteRepoHook(user, repo string, id int64) error { | func (c *Client) DeleteRepoHook(user, repo string, id int64) error { | ||||||
| 	_, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) | 	_, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								vendor/code.gitea.io/sdk/gitea/releases.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vendor/code.gitea.io/sdk/gitea/releases.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | // Copyright 2016 The Gitea Authors. All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
|  | package gitea | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Release represents a repository release | ||||||
|  | type Release struct { | ||||||
|  | 	ID           int64     `json:"id"` | ||||||
|  | 	TagName      string    `json:"name"` | ||||||
|  | 	Target       string    `json:"target_commitish"` | ||||||
|  | 	Title        string    `json:"name"` | ||||||
|  | 	Note         string    `json:"body"` | ||||||
|  | 	URL          string    `json:"url"` | ||||||
|  | 	TarURL       string    `json:"tarball_url"` | ||||||
|  | 	ZipURL       string    `json:"zipball_url"` | ||||||
|  | 	IsDraft      bool      `json:"draft"` | ||||||
|  | 	IsPrerelease bool      `json:"prerelease"` | ||||||
|  | 	CreatedAt    time.Time `json:"created_at"` | ||||||
|  | 	PublishedAt  time.Time `json:"published_at"` | ||||||
|  | 	Publisher    *User     `json:"author"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ListReleases list releases of a repository | ||||||
|  | func (c *Client) ListReleases(user, repo string) ([]*Release, error) { | ||||||
|  | 	releases := make([]*Release, 0, 10) | ||||||
|  | 	err := c.getParsedResponse("GET", | ||||||
|  | 		fmt.Sprintf("/repos/%s/%s/releases", user, repo), | ||||||
|  | 		nil, nil, &releases) | ||||||
|  | 	return releases, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetRelease get a release of a repository | ||||||
|  | func (c *Client) GetRelease(user, repo string, id int64) (*Release, error) { | ||||||
|  | 	r := new(Release) | ||||||
|  | 	err := c.getParsedResponse("GET", | ||||||
|  | 		fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), | ||||||
|  | 		nil, nil, &r) | ||||||
|  | 	return r, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreateReleaseOption options when creating a release | ||||||
|  | type CreateReleaseOption struct { | ||||||
|  | 	TagName      string `json:"tag_name" binding:"Required"` | ||||||
|  | 	Target       string `json:"target_commitish"` | ||||||
|  | 	Title        string `json:"name"` | ||||||
|  | 	Note         string `json:"body"` | ||||||
|  | 	IsDraft      bool   `json:"draft"` | ||||||
|  | 	IsPrerelease bool   `json:"prerelease"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreateRelease create a release | ||||||
|  | func (c *Client) CreateRelease(user, repo string, form CreateReleaseOption) (*Release, error) { | ||||||
|  | 	body, err := json.Marshal(form) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	r := new(Release) | ||||||
|  | 	err = c.getParsedResponse("POST", | ||||||
|  | 		fmt.Sprintf("/repos/%s/%s/releases", user, repo), | ||||||
|  | 		jsonHeader, bytes.NewReader(body), r) | ||||||
|  | 	return r, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EditReleaseOption options when editing a release | ||||||
|  | type EditReleaseOption struct { | ||||||
|  | 	TagName      string `json:"tag_name"` | ||||||
|  | 	Target       string `json:"target_commitish"` | ||||||
|  | 	Title        string `json:"name"` | ||||||
|  | 	Note         string `json:"body"` | ||||||
|  | 	IsDraft      *bool  `json:"draft"` | ||||||
|  | 	IsPrerelease *bool  `json:"prerelease"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EditRelease edit a release | ||||||
|  | func (c *Client) EditRelease(user, repo string, id int64, form EditReleaseOption) (*Release, error) { | ||||||
|  | 	body, err := json.Marshal(form) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	r := new(Release) | ||||||
|  | 	err = c.getParsedResponse("PATCH", | ||||||
|  | 		fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), | ||||||
|  | 		jsonHeader, bytes.NewReader(body), r) | ||||||
|  | 	return r, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteRelease delete a release from a repository | ||||||
|  | func (c *Client) DeleteRelease(user, repo string, id int64) error { | ||||||
|  | 	_, err := c.getResponse("DELETE", | ||||||
|  | 		fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), | ||||||
|  | 		nil, nil) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @@ -9,10 +9,10 @@ | |||||||
| 			"revisionTime": "2016-12-28T14:57:51Z" | 			"revisionTime": "2016-12-28T14:57:51Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "5J8ejjEp2moLyK1pD++Jzof8DFs=", | 			"checksumSHA1": "BKj0haFTDebzdC2nACpoGzp3s8A=", | ||||||
| 			"path": "code.gitea.io/sdk/gitea", | 			"path": "code.gitea.io/sdk/gitea", | ||||||
| 			"revision": "c0e081342a4b99d90371081b888765b91f05546f", | 			"revision": "2064cc397bc48b0a46f8324a97421a824b11882e", | ||||||
| 			"revisionTime": "2016-12-29T09:40:42Z" | 			"revisionTime": "2016-12-31T14:43:27Z" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "IyfS7Rbl6OgR83QR7TOfKdDCq+M=", | 			"checksumSHA1": "IyfS7Rbl6OgR83QR7TOfKdDCq+M=", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user