@@ -145,6 +145,10 @@ pub fn package_router() -> Router<Body, ApiError> {
145145 "/:package/versions/:version" ,
146146 util:: auth ( version_update_handler) ,
147147 )
148+ . delete (
149+ "/:package/versions/:version" ,
150+ util:: auth ( version_delete_handler) ,
151+ )
148152 . post (
149153 "/:package/versions/:version/provenance" ,
150154 util:: auth ( version_provenance_statements_handler) ,
@@ -942,8 +946,98 @@ pub async fn version_update_handler(
942946 gzip_encoded : false ,
943947 } ,
944948 )
945- . await
946- . unwrap ( ) ;
949+ . await ?;
950+
951+ let npm_version_manifest_path =
952+ crate :: gcs_paths:: npm_version_manifest_path ( & scope, & package) ;
953+ let npm_version_manifest =
954+ generate_npm_version_manifest ( db, npm_url, & scope, & package) . await ?;
955+ let content = serde_json:: to_vec_pretty ( & npm_version_manifest) ?;
956+ buckets
957+ . npm_bucket
958+ . upload (
959+ npm_version_manifest_path. into ( ) ,
960+ UploadTaskBody :: Bytes ( content. into ( ) ) ,
961+ GcsUploadOptions {
962+ content_type : Some ( "application/json" . into ( ) ) ,
963+ cache_control : Some ( CACHE_CONTROL_DO_NOT_CACHE . into ( ) ) ,
964+ gzip_encoded : false ,
965+ } ,
966+ )
967+ . await ?;
968+
969+ Ok (
970+ Response :: builder ( )
971+ . status ( StatusCode :: NO_CONTENT )
972+ . body ( Body :: empty ( ) )
973+ . unwrap ( ) ,
974+ )
975+ }
976+
977+ #[ instrument(
978+ name = "DELETE /api/scopes/:scope/packages/:package/versions/:version" ,
979+ skip( req) ,
980+ err,
981+ fields( scope, package, version)
982+ ) ]
983+ pub async fn version_delete_handler (
984+ req : Request < Body > ,
985+ ) -> ApiResult < Response < Body > > {
986+ let scope = req. param_scope ( ) ?;
987+ let package = req. param_package ( ) ?;
988+ let version = req. param_version ( ) ?;
989+ Span :: current ( ) . record ( "scope" , field:: display ( & scope) ) ;
990+ Span :: current ( ) . record ( "package" , field:: display ( & package) ) ;
991+ Span :: current ( ) . record ( "version" , field:: display ( & version) ) ;
992+
993+ let db = req. data :: < Database > ( ) . unwrap ( ) ;
994+ let buckets = req. data :: < Buckets > ( ) . unwrap ( ) . clone ( ) ;
995+ let npm_url = & req. data :: < NpmUrl > ( ) . unwrap ( ) . 0 ;
996+
997+ let iam = req. iam ( ) ;
998+ iam. check_admin_access ( ) ?;
999+
1000+ let count = db
1001+ . count_package_dependents (
1002+ crate :: db:: DependencyKind :: Jsr ,
1003+ & format ! ( "@{}/{}" , scope, package) ,
1004+ )
1005+ . await ?;
1006+
1007+ if count > 0 {
1008+ return Err ( ApiError :: DeleteVersionHasDependents ) ;
1009+ }
1010+
1011+ db. delete_package_version ( & scope, & package, & version)
1012+ . await ?;
1013+
1014+ let path = crate :: gcs_paths:: docs_v1_path ( & scope, & package, & version) ;
1015+ buckets. docs_bucket . delete_file ( path. into ( ) ) . await ?;
1016+
1017+ let path = crate :: gcs_paths:: version_metadata ( & scope, & package, & version) ;
1018+ buckets. modules_bucket . delete_file ( path. into ( ) ) . await ?;
1019+
1020+ let path =
1021+ crate :: gcs_paths:: file_path_root_directory ( & scope, & package, & version) ;
1022+ buckets. modules_bucket . delete_directory ( path. into ( ) ) . await ?;
1023+
1024+ let package_metadata_path =
1025+ crate :: gcs_paths:: package_metadata ( & scope, & package) ;
1026+ let package_metadata = PackageMetadata :: create ( db, & scope, & package) . await ?;
1027+
1028+ let content = serde_json:: to_vec_pretty ( & package_metadata) ?;
1029+ buckets
1030+ . modules_bucket
1031+ . upload (
1032+ package_metadata_path. into ( ) ,
1033+ UploadTaskBody :: Bytes ( content. into ( ) ) ,
1034+ GcsUploadOptions {
1035+ content_type : Some ( "application/json" . into ( ) ) ,
1036+ cache_control : Some ( CACHE_CONTROL_DO_NOT_CACHE . into ( ) ) ,
1037+ gzip_encoded : false ,
1038+ } ,
1039+ )
1040+ . await ?;
9471041
9481042 let npm_version_manifest_path =
9491043 crate :: gcs_paths:: npm_version_manifest_path ( & scope, & package) ;
@@ -4093,4 +4187,78 @@ ggHohNAjhbzDaY2iBW/m3NC5dehGUP4T2GBo/cwGhg==
40934187 assert_eq ! ( tasks. len( ) , 1 ) ;
40944188 assert_eq ! ( tasks[ 0 ] . id, task2. id) ;
40954189 }
4190+
4191+ #[ tokio:: test]
4192+ async fn delete_version ( ) {
4193+ let mut t = TestSetup :: new ( ) . await ;
4194+ let staff_token = t. staff_user . token . clone ( ) ;
4195+
4196+ // unpublished package
4197+ let mut resp = t
4198+ . http ( )
4199+ . get ( "/api/scopes/scope/packages/foo/versions/0.0.1/dependencies/graph" )
4200+ . call ( )
4201+ . await
4202+ . unwrap ( ) ;
4203+ resp
4204+ . expect_err_code ( StatusCode :: NOT_FOUND , "packageVersionNotFound" )
4205+ . await ;
4206+
4207+ let task = process_tarball_setup ( & t, create_mock_tarball ( "ok" ) ) . await ;
4208+ assert_eq ! ( task. status, PublishingTaskStatus :: Success , "{:?}" , task) ;
4209+
4210+ // Now publish a package that has a few deps
4211+ let package_name = PackageName :: try_from ( "bar" ) . unwrap ( ) ;
4212+ let version = Version :: try_from ( "1.2.3" ) . unwrap ( ) ;
4213+ let task = process_tarball_setup2 (
4214+ & t,
4215+ create_mock_tarball ( "depends_on_ok" ) ,
4216+ & package_name,
4217+ & version,
4218+ false ,
4219+ )
4220+ . await ;
4221+ assert_eq ! ( task. status, PublishingTaskStatus :: Success , "{:?}" , task) ;
4222+
4223+ let mut resp = t
4224+ . http ( )
4225+ . delete ( "/api/scopes/scope/packages/foo/versions/0.0.1" )
4226+ . token ( Some ( & staff_token) )
4227+ . call ( )
4228+ . await
4229+ . unwrap ( ) ;
4230+ resp
4231+ . expect_err_code ( StatusCode :: BAD_REQUEST , "deleteVersionHasDependents" )
4232+ . await ;
4233+
4234+ let mut resp = t
4235+ . http ( )
4236+ . delete ( "/api/scopes/scope/packages/bar/versions/1.2.3" )
4237+ . token ( Some ( & staff_token) )
4238+ . call ( )
4239+ . await
4240+ . unwrap ( ) ;
4241+ resp. expect_ok_no_content ( ) . await ;
4242+
4243+ let mut resp = t
4244+ . http ( )
4245+ . delete ( "/api/scopes/scope/packages/foo/versions/0.0.1" )
4246+ . token ( Some ( & staff_token) )
4247+ . call ( )
4248+ . await
4249+ . unwrap ( ) ;
4250+ resp. expect_ok_no_content ( ) . await ;
4251+
4252+ let package_name = PackageName :: try_from ( "foo" ) . unwrap ( ) ;
4253+ let version = Version :: try_from ( "0.0.1" ) . unwrap ( ) ;
4254+ let task = process_tarball_setup2 (
4255+ & t,
4256+ create_mock_tarball ( "ok" ) ,
4257+ & package_name,
4258+ & version,
4259+ false ,
4260+ )
4261+ . await ;
4262+ assert_eq ! ( task. status, PublishingTaskStatus :: Failure , "{:?}" , task) ;
4263+ }
40964264}
0 commit comments