diff --git a/apis/project.go b/apis/project.go index 2c344ad..469fa41 100644 --- a/apis/project.go +++ b/apis/project.go @@ -1,296 +1,296 @@ // SPDX-FileCopyrightText: 2017-2020 Harald Sitter // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL package apis import ( "net/http" "strings" "invent.kde.org/sysadmin/projects-api.git/models" "github.com/gin-gonic/gin" ) type projectService interface { Get(path string) (*models.Project, error) GetByIdentifier(id string) (*models.Project, error) Find(filter *models.ProjectFilter) ([]string, error) List(path string) ([]string, error) Identifiers() ([]string, error) } type projectResource struct { service projectService } // ServeProjectResource creates a new API resource. func ServeProjectResource(rg *gin.RouterGroup, service projectService) { r := &projectResource{service} rg.GET("/repo/*path", r.repo) rg.GET("/project/*path", r.get) rg.GET("/identifier/*id", r.getByIdentifier) rg.GET("/find", r.find) rg.GET("/projects", r.projects) rg.GET("/projects/*path", r.projects) rg.GET("/identifiers", r.identifiers) } /** * @apiDefine ProjectSuccessExample * @apiSuccessExample {json} Success-Response: * { * "i18n": { * "stable": "none", * "stableKF5": "krita/3.1", * "trunk": "none", * "trunkKF5": "master", * "component": "krita" * }, * "repo": "krita", * "path": "calligra/krita", * "identifier": "krita" * } */ /** * @api {get} /project/:path Get by Project Path * * @apiVersion 1.0.0 * @apiGroup Project * @apiName project * * @apiDescription Gets the metadata of the project identified by path. * * @apiUse ProjectSuccessExample * * @apiError Forbidden Path may not be accessed. */ func (r *projectResource) get(c *gin.Context) { path := strings.TrimLeft(c.Param("path"), "/") if strings.Contains(path, "/..") || strings.Contains(path, "../") { c.AbortWithStatus(http.StatusForbidden) return } response, err := r.service.Get(path) if err != nil { panic(err) } if response == nil { c.AbortWithStatus(http.StatusNotFound) return } c.JSON(http.StatusOK, response) } /** * @api {get} /identifier/:id Get by Identifier * * @apiVersion 1.0.0 * @apiGroup Project - * @apiName project + * @apiName identifier * * @apiDescription Gets the metadata of the project identified by identifier. * * @apiUse ProjectSuccessExample * * @apiError NotFound identifier is not known to be associated with any project */ func (r *projectResource) getByIdentifier(c *gin.Context) { id := strings.TrimLeft(c.Param("id"), "/") response, err := r.service.GetByIdentifier(id) if err != nil { panic(err) } if response == nil { c.AbortWithStatus(http.StatusNotFound) return } c.JSON(http.StatusOK, response) } /** * @apiDeprecated use the /project endpoint. Since the gitlab migration the * repo path and the project path are the same. * @api {get} /repo/:path Get by Repository * @apiParam {String} path Repository path as seen in git clone kde:path/is/this. * * @apiVersion 1.0.0 * @apiGroup Project * @apiName repo * * @apiDescription Gets the metadata of the project identified by a query. * * @apiUse ProjectSuccessExample * * @apiError NotFound Couldn't find a project associated with this repo. * @apiError MultipleChoices Couldn't find a unique project for this repo. * You'll need to pick a project and get it via the /project/ * endpoint * @apiErrorExample {json} MultipleChoices-Response: * [ * "books/kf5book", * "books/kf5book-duplcate" * ] */ func (r *projectResource) repo(c *gin.Context) { path := strings.TrimLeft(c.Param("path"), "/") matches, err := r.service.Find(&models.ProjectFilter{RepoPath: path}) if err != nil { panic(err) } if len(matches) < 1 { c.AbortWithStatus(http.StatusNotFound) return } if len(matches) > 1 { c.JSON(http.StatusMultipleChoices, matches) return } response, err := r.service.Get(matches[0]) if err != nil { panic(err) } c.JSON(http.StatusOK, response) } /** * @api {get} /find Find * @apiParam {String} repo repo attribute of the project * to find. * @apiParam {Bool} active Whether to find only projects marked active (inactive * projects can be skipped by code that wants to iterate worthwhile code * projects only e.g. sysadmin repos are usually inactive). * This defaults to false, giving you all repos. * @apiParam {Bool} inactive Whether to find only inactive projects (see active) * This defaults to false, giving you all repos. * @apiParam {String} basename Basename of the project to find. Since the gitlab * migration this filter doesn't have much purpose since the basenames are * not unique. Do not assume that you'll get a unique result when using this * filter! This used to be called `id`. * * @apiVersion 1.0.0 * @apiGroup Project * @apiName find * * @apiDescription Finds matching projects by a combination of filter params or * none to list all projects. * * @apiError NotFound Nothing matched the filter criteria * * @apiSuccessExample {json} Success-Response: * [ * "books", * "books/kf5book", * "calligra", * ... * ] */ func (r *projectResource) find(c *gin.Context) { var filter models.ProjectFilter c.BindQuery(&filter) // backwards compat handling: id is now called basename if len(filter.ID) != 0 { if len(filter.Basename) != 0 { c.String(http.StatusConflict, "Query must not contain `basename` and its legacy variant `id` at the same time.\n") c.AbortWithStatus(http.StatusConflict) return } filter.Basename = filter.ID } matches, err := r.service.Find(&filter) if len(matches) == 0 || err != nil { c.AbortWithStatus(http.StatusNotFound) return } c.JSON(http.StatusOK, matches) } /** * @api {get} /projects/:prefix List Paths * @apiParam {String} prefix Prefix path of the project. This will usually be * the module/components the project is sorted under. * * @apiVersion 1.0.0 * @apiGroup Project * @apiName projects * * @apiDescription Lists all *projects* underneath the prefix. If the prefix is * a project itself it will be included in the list. If the prefix is not a * project only its "children" will be returned. * * @apiSuccessExample {json} Project (/projects/calligra/krita): * [ * "calligra/krita" * ] * * @apiSuccessExample {json} Project with Children (/projects/papa): * [ * "aba", * "aba/bubbale1", * "aba/bubbale2" * ] * * @apiSuccessExample {json} Component (/projects/frameworks) * [ * "frameworks/solid", * "frameworks/ki18n", * "..." * ] */ func (r *projectResource) projects(c *gin.Context) { path := strings.TrimLeft(c.Param("path"), "/") matches, err := r.service.List(path) if len(matches) == 0 || err != nil { c.AbortWithStatus(http.StatusNotFound) return } c.JSON(http.StatusOK, matches) } /** * @api {get} /identifiers List Identifiers * * @apiVersion 1.0.0 * @apiGroup Project - * @apiName projects + * @apiName identifiers * * @apiDescription Lists all *identifiers*. Since the migration to gitlab * identifiers are equal to the i18n component. They act as unique * identifier string for a given project. * * @apiSuccessExample {json} Project (/identifiers): * [ * "krita", * "solid", * "maui-dialer" * "..." * ] */ func (r *projectResource) identifiers(c *gin.Context) { matches, err := r.service.Identifiers() if len(matches) == 0 || err != nil { c.AbortWithStatus(http.StatusNotFound) return } c.JSON(http.StatusOK, matches) }