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