Browse Source

Add notification API

Jordi Boggiano 13 years ago
parent
commit
505e0eb4ec

+ 50 - 2
src/Packagist/WebBundle/Controller/ApiController.php

@@ -15,6 +15,7 @@ namespace Packagist\WebBundle\Controller;
 use Composer\IO\NullIO;
 use Composer\Repository\VcsRepository;
 use Packagist\WebBundle\Package\Updater;
+use Packagist\WebBundle\Entity\Package;
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\Request;
@@ -40,14 +41,19 @@ class ApiController extends Controller
         $packages = $em->getRepository('Packagist\WebBundle\Entity\Package')
             ->getFullPackages();
 
-        $data = array();
+        $notifyUrl = $this->generateUrl('track_download', array('name' => 'VND/PKG'));
+
+        $data = array(
+            'notify' => str_replace('VND/PKG', '%package%', $notifyUrl),
+            'packages' => array(),
+        );
         foreach ($packages as $package) {
             $versions = array();
             foreach ($package->getVersions() as $version) {
                 $versions[$version->getVersion()] = $version->toArray();
                 $em->detach($version);
             }
-            $data[$package->getName()] = array('versions' => $versions);
+            $data['packages'][$package->getName()] = array($versions);
             $em->detach($package);
         }
         unset($versions, $package, $packages);
@@ -106,4 +112,46 @@ class ApiController extends Controller
 
         return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',)), 404);
     }
+
+    /**
+     * @Route("/downloads/{name}", name="track_download", requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+"}, defaults={"_format" = "json"})
+     * @Method({"POST"})
+     */
+    public function trackDownloadAction(Request $request, $name)
+    {
+        $result = $this->getDoctrine()->getConnection()->fetchAssoc(
+            'SELECT p.id, v.id vid
+            FROM package p
+            LEFT JOIN package_version v ON p.id = v.package_id
+            WHERE p.name = ?
+            AND v.normalizedVersion = ?
+            LIMIT 1',
+            array($name, $request->request->get('version_normalized'))
+        );
+
+        if (!$result) {
+            return new Response('{"status": "error", "message": "Package not found"}', 200);
+        }
+
+        $redis = $this->get('snc_redis.default');
+        $id = $result['id'];
+        $version = $result['vid'];
+
+        $throttleKey = 'dl:'.$id.':'.$request->getClientIp().':'.date('Ymd');
+        $requests = $redis->incr($throttleKey);
+        if (1 === $requests) {
+            $redis->expire($throttleKey, 86400);
+        }
+        if ($requests <= 10) {
+            $redis->incr('dl:'.$id.':'.date('Ymd'));
+            $redis->incr('dl:'.$id.':'.date('Ym'));
+            $redis->incr('dl:'.$id);
+
+            $redis->incr('dl:'.$id.'-'.$version.':'.date('Ymd'));
+            $redis->incr('dl:'.$id.'-'.$version.':'.date('Ym'));
+            $redis->incr('dl:'.$id.'-'.$version);
+        }
+
+        return new Response('{"status": "success"}', 201);
+    }
 }

+ 10 - 1
src/Packagist/WebBundle/Package/Dumper.php

@@ -14,6 +14,7 @@ namespace Packagist\WebBundle\Package;
 
 use Symfony\Component\Filesystem\Filesystem;
 use Symfony\Bridge\Doctrine\RegistryInterface;
+use Symfony\Component\Routing\RouterInterface;
 use Packagist\WebBundle\Entity\Version;
 
 /**
@@ -42,6 +43,11 @@ class Dumper
      */
     protected $buildDir;
 
+    /**
+     * @var RouterInterface
+     */
+    protected $router;
+
     /**
      * Data cache
      * @var array
@@ -55,10 +61,11 @@ class Dumper
      * @param string $webDir web root
      * @param string $cacheDir cache dir
      */
-    public function __construct(RegistryInterface $doctrine, Filesystem $filesystem, $webDir, $cacheDir)
+    public function __construct(RegistryInterface $doctrine, Filesystem $filesystem, RouterInterface $router, $webDir, $cacheDir)
     {
         $this->doctrine = $doctrine;
         $this->fs = $filesystem;
+        $this->router = $router;
         $this->webDir = realpath($webDir);
         $this->buildDir = $cacheDir . '/composer-packages-build';
     }
@@ -112,6 +119,8 @@ class Dumper
         if (!isset($this->files['packages.json']['packages'])) {
             $this->files['packages.json']['packages'] = array();
         }
+        $url = $this->router->generate('track_download', array('name' => 'VND/PKG'));
+        $this->files['packages.json']['notify'] = str_replace('VND/PKG', '%package%', $url);
 
         // dump files to build dir
         foreach ($modifiedFiles as $file => $dummy) {

+ 1 - 1
src/Packagist/WebBundle/Resources/config/services.yml

@@ -7,4 +7,4 @@ services:
 
     packagist.package_dumper:
         class: Packagist\WebBundle\Package\Dumper
-        arguments: [ @doctrine, @filesystem, %kernel.root_dir%/../web/, %kernel.cache_dir% ]
+        arguments: [ @doctrine, @filesystem, @router, %kernel.root_dir%/../web/, %kernel.cache_dir% ]