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 }