code.gitea.io/gitea@v1.22.3/routers/api/packages/cargo/cargo.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package cargo
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/models/db"
    14  	packages_model "code.gitea.io/gitea/models/packages"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/optional"
    17  	packages_module "code.gitea.io/gitea/modules/packages"
    18  	cargo_module "code.gitea.io/gitea/modules/packages/cargo"
    19  	"code.gitea.io/gitea/modules/setting"
    20  	"code.gitea.io/gitea/modules/structs"
    21  	"code.gitea.io/gitea/modules/util"
    22  	"code.gitea.io/gitea/routers/api/packages/helper"
    23  	"code.gitea.io/gitea/services/context"
    24  	"code.gitea.io/gitea/services/convert"
    25  	packages_service "code.gitea.io/gitea/services/packages"
    26  	cargo_service "code.gitea.io/gitea/services/packages/cargo"
    27  )
    28  
    29  // https://doc.rust-lang.org/cargo/reference/registries.html#web-api
    30  type StatusResponse struct {
    31  	OK     bool            `json:"ok"`
    32  	Errors []StatusMessage `json:"errors,omitempty"`
    33  }
    34  
    35  type StatusMessage struct {
    36  	Message string `json:"detail"`
    37  }
    38  
    39  func apiError(ctx *context.Context, status int, obj any) {
    40  	helper.LogAndProcessError(ctx, status, obj, func(message string) {
    41  		ctx.JSON(status, StatusResponse{
    42  			OK: false,
    43  			Errors: []StatusMessage{
    44  				{
    45  					Message: message,
    46  				},
    47  			},
    48  		})
    49  	})
    50  }
    51  
    52  // https://rust-lang.github.io/rfcs/2789-sparse-index.html
    53  func RepositoryConfig(ctx *context.Context) {
    54  	ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic))
    55  }
    56  
    57  func EnumeratePackageVersions(ctx *context.Context) {
    58  	p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.Params("package"))
    59  	if err != nil {
    60  		if errors.Is(err, util.ErrNotExist) {
    61  			apiError(ctx, http.StatusNotFound, err)
    62  		} else {
    63  			apiError(ctx, http.StatusInternalServerError, err)
    64  		}
    65  		return
    66  	}
    67  
    68  	b, err := cargo_service.BuildPackageIndex(ctx, p)
    69  	if err != nil {
    70  		apiError(ctx, http.StatusInternalServerError, err)
    71  		return
    72  	}
    73  	if b == nil {
    74  		apiError(ctx, http.StatusNotFound, nil)
    75  		return
    76  	}
    77  
    78  	ctx.PlainTextBytes(http.StatusOK, b.Bytes())
    79  }
    80  
    81  type SearchResult struct {
    82  	Crates []*SearchResultCrate `json:"crates"`
    83  	Meta   SearchResultMeta     `json:"meta"`
    84  }
    85  
    86  type SearchResultCrate struct {
    87  	Name          string `json:"name"`
    88  	LatestVersion string `json:"max_version"`
    89  	Description   string `json:"description"`
    90  }
    91  
    92  type SearchResultMeta struct {
    93  	Total int64 `json:"total"`
    94  }
    95  
    96  // https://doc.rust-lang.org/cargo/reference/registries.html#search
    97  func SearchPackages(ctx *context.Context) {
    98  	page := ctx.FormInt("page")
    99  	if page < 1 {
   100  		page = 1
   101  	}
   102  	perPage := ctx.FormInt("per_page")
   103  	paginator := db.ListOptions{
   104  		Page:     page,
   105  		PageSize: convert.ToCorrectPageSize(perPage),
   106  	}
   107  
   108  	pvs, total, err := packages_model.SearchLatestVersions(
   109  		ctx,
   110  		&packages_model.PackageSearchOptions{
   111  			OwnerID:    ctx.Package.Owner.ID,
   112  			Type:       packages_model.TypeCargo,
   113  			Name:       packages_model.SearchValue{Value: ctx.FormTrim("q")},
   114  			IsInternal: optional.Some(false),
   115  			Paginator:  &paginator,
   116  		},
   117  	)
   118  	if err != nil {
   119  		apiError(ctx, http.StatusInternalServerError, err)
   120  		return
   121  	}
   122  
   123  	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
   124  	if err != nil {
   125  		apiError(ctx, http.StatusInternalServerError, err)
   126  		return
   127  	}
   128  
   129  	crates := make([]*SearchResultCrate, 0, len(pvs))
   130  	for _, pd := range pds {
   131  		crates = append(crates, &SearchResultCrate{
   132  			Name:          pd.Package.Name,
   133  			LatestVersion: pd.Version.Version,
   134  			Description:   pd.Metadata.(*cargo_module.Metadata).Description,
   135  		})
   136  	}
   137  
   138  	ctx.JSON(http.StatusOK, SearchResult{
   139  		Crates: crates,
   140  		Meta: SearchResultMeta{
   141  			Total: total,
   142  		},
   143  	})
   144  }
   145  
   146  type Owners struct {
   147  	Users []OwnerUser `json:"users"`
   148  }
   149  
   150  type OwnerUser struct {
   151  	ID    int64  `json:"id"`
   152  	Login string `json:"login"`
   153  	Name  string `json:"name"`
   154  }
   155  
   156  // https://doc.rust-lang.org/cargo/reference/registries.html#owners-list
   157  func ListOwners(ctx *context.Context) {
   158  	ctx.JSON(http.StatusOK, Owners{
   159  		Users: []OwnerUser{
   160  			{
   161  				ID:    ctx.Package.Owner.ID,
   162  				Login: ctx.Package.Owner.Name,
   163  				Name:  ctx.Package.Owner.DisplayName(),
   164  			},
   165  		},
   166  	})
   167  }
   168  
   169  // DownloadPackageFile serves the content of a package
   170  func DownloadPackageFile(ctx *context.Context) {
   171  	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
   172  		ctx,
   173  		&packages_service.PackageInfo{
   174  			Owner:       ctx.Package.Owner,
   175  			PackageType: packages_model.TypeCargo,
   176  			Name:        ctx.Params("package"),
   177  			Version:     ctx.Params("version"),
   178  		},
   179  		&packages_service.PackageFileInfo{
   180  			Filename: strings.ToLower(fmt.Sprintf("%s-%s.crate", ctx.Params("package"), ctx.Params("version"))),
   181  		},
   182  	)
   183  	if err != nil {
   184  		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
   185  			apiError(ctx, http.StatusNotFound, err)
   186  			return
   187  		}
   188  		apiError(ctx, http.StatusInternalServerError, err)
   189  		return
   190  	}
   191  
   192  	helper.ServePackageFile(ctx, s, u, pf)
   193  }
   194  
   195  // https://doc.rust-lang.org/cargo/reference/registries.html#publish
   196  func UploadPackage(ctx *context.Context) {
   197  	defer ctx.Req.Body.Close()
   198  
   199  	cp, err := cargo_module.ParsePackage(ctx.Req.Body)
   200  	if err != nil {
   201  		apiError(ctx, http.StatusBadRequest, err)
   202  		return
   203  	}
   204  
   205  	buf, err := packages_module.CreateHashedBufferFromReader(cp.Content)
   206  	if err != nil {
   207  		apiError(ctx, http.StatusInternalServerError, err)
   208  		return
   209  	}
   210  	defer buf.Close()
   211  
   212  	if buf.Size() != cp.ContentSize {
   213  		apiError(ctx, http.StatusBadRequest, "invalid content size")
   214  		return
   215  	}
   216  
   217  	pv, _, err := packages_service.CreatePackageAndAddFile(
   218  		ctx,
   219  		&packages_service.PackageCreationInfo{
   220  			PackageInfo: packages_service.PackageInfo{
   221  				Owner:       ctx.Package.Owner,
   222  				PackageType: packages_model.TypeCargo,
   223  				Name:        cp.Name,
   224  				Version:     cp.Version,
   225  			},
   226  			SemverCompatible: true,
   227  			Creator:          ctx.Doer,
   228  			Metadata:         cp.Metadata,
   229  			VersionProperties: map[string]string{
   230  				cargo_module.PropertyYanked: strconv.FormatBool(false),
   231  			},
   232  		},
   233  		&packages_service.PackageFileCreationInfo{
   234  			PackageFileInfo: packages_service.PackageFileInfo{
   235  				Filename: strings.ToLower(fmt.Sprintf("%s-%s.crate", cp.Name, cp.Version)),
   236  			},
   237  			Creator: ctx.Doer,
   238  			Data:    buf,
   239  			IsLead:  true,
   240  		},
   241  	)
   242  	if err != nil {
   243  		switch err {
   244  		case packages_model.ErrDuplicatePackageVersion:
   245  			apiError(ctx, http.StatusConflict, err)
   246  		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   247  			apiError(ctx, http.StatusForbidden, err)
   248  		default:
   249  			apiError(ctx, http.StatusInternalServerError, err)
   250  		}
   251  		return
   252  	}
   253  
   254  	if err := cargo_service.UpdatePackageIndexIfExists(ctx, ctx.Doer, ctx.Package.Owner, pv.PackageID); err != nil {
   255  		if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
   256  			log.Error("Rollback creation of package version: %v", err)
   257  		}
   258  
   259  		apiError(ctx, http.StatusInternalServerError, err)
   260  		return
   261  	}
   262  
   263  	ctx.JSON(http.StatusOK, StatusResponse{OK: true})
   264  }
   265  
   266  // https://doc.rust-lang.org/cargo/reference/registries.html#yank
   267  func YankPackage(ctx *context.Context) {
   268  	yankPackage(ctx, true)
   269  }
   270  
   271  // https://doc.rust-lang.org/cargo/reference/registries.html#unyank
   272  func UnyankPackage(ctx *context.Context) {
   273  	yankPackage(ctx, false)
   274  }
   275  
   276  func yankPackage(ctx *context.Context, yank bool) {
   277  	pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeCargo, ctx.Params("package"), ctx.Params("version"))
   278  	if err != nil {
   279  		if err == packages_model.ErrPackageNotExist {
   280  			apiError(ctx, http.StatusNotFound, err)
   281  			return
   282  		}
   283  		apiError(ctx, http.StatusInternalServerError, err)
   284  		return
   285  	}
   286  
   287  	pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, cargo_module.PropertyYanked)
   288  	if err != nil {
   289  		apiError(ctx, http.StatusInternalServerError, err)
   290  		return
   291  	}
   292  	if len(pps) == 0 {
   293  		apiError(ctx, http.StatusInternalServerError, "Property not found")
   294  		return
   295  	}
   296  
   297  	pp := pps[0]
   298  	pp.Value = strconv.FormatBool(yank)
   299  
   300  	if err := packages_model.UpdateProperty(ctx, pp); err != nil {
   301  		apiError(ctx, http.StatusInternalServerError, err)
   302  		return
   303  	}
   304  
   305  	if err := cargo_service.UpdatePackageIndexIfExists(ctx, ctx.Doer, ctx.Package.Owner, pv.PackageID); err != nil {
   306  		apiError(ctx, http.StatusInternalServerError, err)
   307  		return
   308  	}
   309  
   310  	ctx.JSON(http.StatusOK, StatusResponse{OK: true})
   311  }