Browse Source

Merge remote-tracking branch 'simensen/github-post-receive-hook'

Jordi Boggiano 13 years ago
parent
commit
2314223750

+ 12 - 0
app/Resources/FOSUserBundle/views/Profile/show.html.twig

@@ -9,6 +9,18 @@
         <p><a href="{{ path('fos_user_change_password') }}">Change your password</a></p>
         <p><a href="{{ path('user_profile', {'name':app.user.username}) }}">View your public profile</a></p>
 
+        {% if app.user.apiToken %}
+            <h1>Your API Token</h1>
+            <p><pre><code>{{ app.user.apiToken }}</code></pre></p>
+            <p>
+            You can use your API token to interact with the Packagist API.
+            </p>
+            <p>Your <a href="http://help.github.com/post-receive-hooks/">GitHub Post-Receive URL</a> is:</p>
+            <p>
+            <pre><code>{{ url('github_postreceive', { 'username': app.user.username, 'apiToken': app.user.apiToken }) }}</code></pre>
+            </p>
+        {% endif %}
+
         <h1>Your packages</h1>
         {% if user.packages|length %}
             {{ macros.listPackages(user.packages) }}

+ 48 - 0
src/Packagist/WebBundle/Command/GenerateTokensCommand.php

@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class GenerateTokensCommand extends ContainerAwareCommand
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this
+            ->setName('packagist:tokens:generate')
+            ->setDescription('Generates all missing user tokens')
+        ;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $doctrine = $this->getContainer()->get('doctrine');
+        $userRepo = $doctrine->getRepository('PackagistWebBundle:User');
+        $users = $userRepo->findUsersMissingApiToken();
+        foreach ($users as $user) {
+            $user->regenerateApiToken();
+        }
+        $doctrine->getEntityManager()->flush();
+    }
+}

+ 3 - 218
src/Packagist/WebBundle/Command/UpdatePackagesCommand.php

@@ -13,38 +13,17 @@
 namespace Packagist\WebBundle\Command;
 
 use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
-use Symfony\Bridge\Doctrine\RegistryInterface;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Output\OutputInterface;
-use Symfony\Component\HttpKernel\KernelInterface;
-use Symfony\Component\Finder\Finder;
-use Packagist\WebBundle\Entity\Version;
-use Packagist\WebBundle\Entity\Tag;
-use Packagist\WebBundle\Entity\Author;
-use Composer\IO\NullIO;
-use Composer\Package\Version\VersionParser;
-use Composer\Repository\VcsRepository;
-use Composer\Package\PackageInterface;
-use Composer\Repository\RepositoryManager;
+use Packagist\WebBundle\Package\Updater;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
 class UpdatePackagesCommand extends ContainerAwareCommand
 {
-    protected $versionParser;
-
-    protected $supportedLinkTypes = array(
-        'require'   => 'RequireLink',
-        'conflict'  => 'ConflictLink',
-        'provide'   => 'ProvideLink',
-        'replace'   => 'ReplaceLink',
-        'recommend' => 'RecommendLink',
-        'suggest'   => 'SuggestLink',
-    );
-
     /**
      * {@inheritdoc}
      */
@@ -70,9 +49,6 @@ class UpdatePackagesCommand extends ContainerAwareCommand
         $package = $input->getArgument('package');
 
         $doctrine = $this->getContainer()->get('doctrine');
-        $logger = $this->getContainer()->get('logger');
-
-        $this->versionParser = new VersionParser;
 
         if ($package) {
             $packages = array($doctrine->getRepository('PackagistWebBundle:Package')->findOneByName($package));
@@ -82,209 +58,18 @@ class UpdatePackagesCommand extends ContainerAwareCommand
             $packages = $doctrine->getRepository('PackagistWebBundle:Package')->getStalePackages();
         }
 
+        $updater = new Updater($doctrine);
         $start = new \DateTime();
 
         foreach ($packages as $package) {
             if ($verbose) {
                 $output->writeln('Importing '.$package->getRepository());
             }
-
             try {
-                $repository = new VcsRepository(array('url' => $package->getRepository()), new NullIO());
-                if ($verbose) {
-                    $repository->setDebug(true);
-                }
-                $versions = $repository->getPackages();
-
-                usort($versions, function ($a, $b) {
-                    return version_compare($a->getVersion(), $b->getVersion());
-                });
-
-                // clear existing versions to force a clean reloading if --force is enabled
-                if ($force) {
-                    $versionRepo = $doctrine->getRepository('PackagistWebBundle:Version');
-                    foreach ($package->getVersions() as $version) {
-                        $versionRepo->remove($version);
-                    }
-
-                    $doctrine->getEntityManager()->flush();
-                    $doctrine->getEntityManager()->refresh($package);
-                }
-
-                foreach ($versions as $version) {
-                    if ($verbose) {
-                        $output->writeln('Storing '.$version->getPrettyVersion().' ('.$version->getVersion().')');
-                    }
-
-                    $this->updateInformation($output, $doctrine, $package, $version);
-                    $doctrine->getEntityManager()->flush();
-                }
-
-                // remove outdated -dev versions
-                foreach ($package->getVersions() as $version) {
-                    if ($version->getDevelopment() && $version->getUpdatedAt() < $start) {
-                        if ($verbose) {
-                            $output->writeln('Deleting stale version: '.$version->getVersion());
-                        }
-                        $doctrine->getRepository('PackagistWebBundle:Version')->remove($version);
-                    }
-                }
-
-                $package->setUpdatedAt(new \DateTime);
-                $package->setCrawledAt(new \DateTime);
-                $doctrine->getEntityManager()->flush();
+                $updater->update($package, $force, $start);
             } catch (\Exception $e) {
                 $output->writeln('<error>Exception: '.$e->getMessage().', skipping package '.$package->getName().'.</error>');
             }
         }
     }
-
-    private function updateInformation(OutputInterface $output, RegistryInterface $doctrine, $package, PackageInterface $data)
-    {
-        $em = $doctrine->getEntityManager();
-        $version = new Version();
-
-        $version->setName($package->getName());
-        $version->setNormalizedVersion(preg_replace('{-dev$}i', '', $data->getVersion()));
-
-        // check if we have that version yet
-        foreach ($package->getVersions() as $existingVersion) {
-            if ($existingVersion->equals($version)) {
-                // avoid updating newer versions, in case two branches have the same version in their composer.json
-                if ($existingVersion->getReleasedAt() > $data->getReleaseDate()) {
-                    return;
-                }
-                if ($existingVersion->getDevelopment()) {
-                    $version = $existingVersion;
-                    break;
-                }
-                return;
-            }
-        }
-
-        $version->setVersion($data->getPrettyVersion());
-        $version->setDevelopment(substr($data->getVersion(), -4) === '-dev');
-
-        $em->persist($version);
-
-        $version->setDescription($data->getDescription());
-        $package->setDescription($data->getDescription());
-        $version->setHomepage($data->getHomepage());
-        $version->setLicense($data->getLicense() ?: array());
-
-        $version->setPackage($package);
-        $version->setUpdatedAt(new \DateTime);
-        $version->setReleasedAt($data->getReleaseDate());
-
-        if ($data->getSourceType()) {
-            $source['type'] = $data->getSourceType();
-            $source['url'] = $data->getSourceUrl();
-            $source['reference'] = $data->getSourceReference();
-            $version->setSource($source);
-        }
-
-        if ($data->getDistType()) {
-            $dist['type'] = $data->getDistType();
-            $dist['url'] = $data->getDistUrl();
-            $dist['reference'] = $data->getDistReference();
-            $dist['shasum'] = $data->getDistSha1Checksum();
-            $version->setDist($dist);
-        }
-
-        if ($data->getType()) {
-            $version->setType($data->getType());
-            if ($data->getType() && $data->getType() !== $package->getType()) {
-                $package->setType($data->getType());
-            }
-        }
-
-        $version->setTargetDir($data->getTargetDir());
-        $version->setAutoload($data->getAutoload());
-        $version->setExtra($data->getExtra());
-        $version->setBinaries($data->getBinaries());
-
-        $version->getTags()->clear();
-        if ($data->getKeywords()) {
-            foreach ($data->getKeywords() as $keyword) {
-                $version->addTag(Tag::getByName($em, $keyword, true));
-            }
-        }
-
-        $version->getAuthors()->clear();
-        if ($data->getAuthors()) {
-            foreach ($data->getAuthors() as $authorData) {
-                $author = null;
-                // skip authors with no information
-                if (empty($authorData['email']) && empty($authorData['name'])) {
-                    continue;
-                }
-
-                if (!empty($authorData['email'])) {
-                    $author = $doctrine->getRepository('PackagistWebBundle:Author')->findOneByEmail($authorData['email']);
-                }
-
-                if (!$author && !empty($authorData['homepage'])) {
-                    $author = $doctrine->getRepository('PackagistWebBundle:Author')->findOneBy(array(
-                        'name' => $authorData['name'],
-                        'homepage' => $authorData['homepage']
-                    ));
-                }
-
-                if (!$author && !empty($authorData['name'])) {
-                    $author = $doctrine->getRepository('PackagistWebBundle:Author')->findOneByNameAndPackage($authorData['name'], $package);
-                }
-
-                if (!$author) {
-                    $author = new Author();
-                    $em->persist($author);
-                }
-
-                foreach (array('email', 'name', 'homepage') as $field) {
-                    if (isset($authorData[$field])) {
-                        $author->{'set'.$field}($authorData[$field]);
-                    }
-                }
-
-                $author->setUpdatedAt(new \DateTime);
-                if (!$version->getAuthors()->contains($author)) {
-                    $version->addAuthor($author);
-                }
-                if (!$author->getVersions()->contains($version)) {
-                    $author->addVersion($version);
-                }
-            }
-        }
-
-        foreach ($this->supportedLinkTypes as $linkType => $linkEntity) {
-            $links = array();
-            foreach ($data->{'get'.$linkType.'s'}() as $link) {
-                $links[$link->getTarget()] = $link->getPrettyConstraint();
-            }
-
-            foreach ($version->{'get'.$linkType}() as $link) {
-                // clear links that have changed/disappeared (for updates)
-                if (!isset($links[$link->getPackageName()]) || $links[$link->getPackageName()] !== $link->getPackageVersion()) {
-                    $version->{'get'.$linkType}()->removeElement($link);
-                    $em->remove($link);
-                } else {
-                    // clear those that are already set
-                    unset($links[$link->getPackageName()]);
-                }
-            }
-
-            foreach ($links as $linkPackageName => $linkPackageVersion) {
-                $class = 'Packagist\WebBundle\Entity\\'.$linkEntity;
-                $link = new $class;
-                $link->setPackageName($linkPackageName);
-                $link->setPackageVersion($linkPackageVersion);
-                $version->{'add'.$linkType.'Link'}($link);
-                $link->setVersion($version);
-                $em->persist($link);
-            }
-        }
-
-        if (!$package->getVersions()->contains($version)) {
-            $package->addVersions($version);
-        }
-    }
 }

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

@@ -12,10 +12,13 @@
 
 namespace Packagist\WebBundle\Controller;
 
+use Packagist\WebBundle\Package\Updater;
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -24,7 +27,7 @@ class ApiController extends Controller
 {
     /**
      * @Template()
-     * @Route("/packages.json", name="packages")
+     * @Route("/packages.json", name="packages", defaults={"_format" = "json"})
      */
     public function packagesAction()
     {
@@ -37,8 +40,51 @@ class ApiController extends Controller
             $data[$package->getName()] = $package->toArray();
         }
 
-        $response = new Response(json_encode($data), 200, array('Content-Type' => 'application/json'));
+        $response = new Response(json_encode($data), 200);
         $response->setSharedMaxAge(60);
         return $response;
     }
+
+    /**
+     * @Route("/api/github", name="github_postreceive", defaults={"_format" = "json"})
+     * @Method({"POST"})
+     */
+    public function githubPostReceive(Request $request)
+    {
+        $payload = json_decode($request->request->get('payload'), true);
+        if (!$payload || !isset($payload['repository']['url'])) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Missing or invalid payload',)), 406);
+        }
+
+        $username = $request->query->get('username');
+        $apiToken = $request->query->get('apiToken');
+
+        $doctrine = $this->get('doctrine');
+        $user = $doctrine
+            ->getRepository('Packagist\WebBundle\Entity\User')
+            ->findOneBy(array('username' => $username, 'apiToken' => $apiToken));
+
+        if (!$user) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Invalid credentials',)), 403);
+        }
+
+        if (! preg_match('~(github.com/[\w_\-\.]+/[\w_\-\.]+)$~', $payload['repository']['url'], $matches)) {
+            return new Response(json_encode(array('status' => 'error', 'message' => 'Could not parse payload repository URL',)), 406);
+        }
+
+        $payloadRepositoryChunk = $matches[1];
+
+        foreach ($user->getPackages() as $package) {
+            if (false !== strpos($package->getRepository(), $payloadRepositoryChunk)) {
+                // We found the package that was referenced.
+
+                $updater = new Updater($doctrine);
+                $updater->update($package);
+
+                return new Response('{ "status": "success" }', 202);
+            }
+        }
+
+        return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',)), 404);
+    }
 }

+ 36 - 1
src/Packagist/WebBundle/Entity/User.php

@@ -17,7 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
 use Doctrine\Common\Collections\ArrayCollection;
 
 /**
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="Packagist\WebBundle\Entity\UserRepository")
  * @ORM\Table(name="fos_user")
  */
 class User extends BaseUser
@@ -44,11 +44,18 @@ class User extends BaseUser
      */
     private $createdAt;
 
+    /**
+     * @ORM\Column(type="string", length=20, nullable=true)
+     * @var string
+     */
+    private $apiToken;
+
     public function __construct()
     {
         $this->packages = new ArrayCollection();
         $this->authors = new ArrayCollection();
         $this->createdAt = new \DateTime();
+        $this->regenerateApiToken();
         parent::__construct();
     }
 
@@ -119,4 +126,32 @@ class User extends BaseUser
     {
         return $this->createdAt;
     }
+    
+    /**
+     * Set apiToken
+     *
+     * @param string $apiToken
+     */
+    public function setApiToken($apiToken)
+    {
+        $this->apiToken = $apiToken;
+    }
+
+    /**
+     * Get apiToken
+     * 
+     * @return string
+     */
+    public function getApiToken()
+    {
+        return $this->apiToken;
+    }
+
+    /**
+     * Regenerate the apiToken
+     */
+    public function regenerateApiToken()
+    {
+        $this->apiToken = substr($this->generateToken(), 0, 20);
+    }
 }

+ 28 - 0
src/Packagist/WebBundle/Entity/UserRepository.php

@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Entity;
+
+use Doctrine\ORM\EntityRepository;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class UserRepository extends EntityRepository
+{
+    public function findUsersMissingApiToken()
+    {
+        $qb = $this->createQueryBuilder('u')
+            ->where('u.apiToken IS NULL');
+        return $qb->getQuery()->getResult();
+    }
+}

+ 257 - 0
src/Packagist/WebBundle/Package/Updater.php

@@ -0,0 +1,257 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Package;
+
+use Composer\Package\PackageInterface;
+use Composer\Repository\VcsRepository;
+use Composer\IO\NullIO;
+use Packagist\WebBundle\Entity\Author;
+use Packagist\WebBundle\Entity\Package;
+use Packagist\WebBundle\Entity\Tag;
+use Packagist\WebBundle\Entity\Version;
+use Symfony\Bridge\Doctrine\RegistryInterface;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class Updater
+{
+    /**
+     * Doctrine
+     * @var RegistryInterface
+     */
+    protected $doctrine;
+
+    /**
+     * Supported link types
+     * @var array
+     */
+    protected $supportedLinkTypes = array(
+        'require'   => 'RequireLink',
+        'conflict'  => 'ConflictLink',
+        'provide'   => 'ProvideLink',
+        'replace'   => 'ReplaceLink',
+        'recommend' => 'RecommendLink',
+        'suggest'   => 'SuggestLink',
+    );
+
+    /**
+     * Constructor
+     * 
+     * @param RegistryInterface $doctrine
+     */
+    public function __construct(RegistryInterface $doctrine)
+    {
+        $this->doctrine = $doctrine;
+    }
+
+    /**
+     * Update a project
+     *
+     * @param PackageInterface $package
+     * @param boolean $clearExistingVersions
+     * @param DateTime $start
+     */
+    public function update(Package $package, $clearExistingVersions = false, \DateTime $start = null)
+    {
+        if (null === $start) {
+            $start = new \DateTime();
+        }
+
+        $repository = new VcsRepository(array('url' => $package->getRepository()), new NullIO());
+        $versions = $repository->getPackages();
+        $em = $this->doctrine->getEntityManager();
+        
+        usort($versions, function ($a, $b) {
+            return version_compare($a->getVersion(), $b->getVersion());
+        });
+
+        $versionRepository = $this->doctrine->getRepository('PackagistWebBundle:Version');
+        
+        if ($clearExistingVersions) {
+            foreach ($package->getVersions() as $version) {
+                $versionRepository->remove($version);
+            }
+        
+            $em->flush();
+            $em->refresh($package);
+        }
+
+       foreach ($versions as $version) {
+            $this->updateInformation($package, $version);
+            $em->flush();
+        }
+        
+        // remove outdated -dev versions
+        foreach ($package->getVersions() as $version) {
+            if ($version->getDevelopment() && $version->getUpdatedAt() < $start) {
+                $versionRepository->remove($version);
+            }
+        }
+        
+        $package->setUpdatedAt(new \DateTime);
+        $package->setCrawledAt(new \DateTime);
+        $em->flush();
+    }
+
+    private function updateInformation(Package $package, PackageInterface $data)
+    {
+        $em = $this->doctrine->getEntityManager();
+        $version = new Version();
+    
+        $version->setName($package->getName());
+        $version->setNormalizedVersion(preg_replace('{-dev$}i', '', $data->getVersion()));
+    
+        // check if we have that version yet
+        foreach ($package->getVersions() as $existingVersion) {
+            if ($existingVersion->equals($version)) {
+                // avoid updating newer versions, in case two branches have the same version in their composer.json
+                if ($existingVersion->getReleasedAt() > $data->getReleaseDate()) {
+                    return;
+                }
+                if ($existingVersion->getDevelopment()) {
+                    $version = $existingVersion;
+                    break;
+                }
+                return;
+            }
+        }
+    
+        $version->setVersion($data->getPrettyVersion());
+        $version->setDevelopment(substr($data->getVersion(), -4) === '-dev');
+    
+        $em->persist($version);
+    
+        $version->setDescription($data->getDescription());
+        $package->setDescription($data->getDescription());
+        $version->setHomepage($data->getHomepage());
+        $version->setLicense($data->getLicense() ?: array());
+    
+        $version->setPackage($package);
+        $version->setUpdatedAt(new \DateTime);
+        $version->setReleasedAt($data->getReleaseDate());
+    
+        if ($data->getSourceType()) {
+            $source['type'] = $data->getSourceType();
+            $source['url'] = $data->getSourceUrl();
+            $source['reference'] = $data->getSourceReference();
+            $version->setSource($source);
+        }
+    
+        if ($data->getDistType()) {
+            $dist['type'] = $data->getDistType();
+            $dist['url'] = $data->getDistUrl();
+            $dist['reference'] = $data->getDistReference();
+            $dist['shasum'] = $data->getDistSha1Checksum();
+            $version->setDist($dist);
+        }
+    
+        if ($data->getType()) {
+            $version->setType($data->getType());
+            if ($data->getType() && $data->getType() !== $package->getType()) {
+                $package->setType($data->getType());
+            }
+        }
+    
+        $version->setTargetDir($data->getTargetDir());
+        $version->setAutoload($data->getAutoload());
+        $version->setExtra($data->getExtra());
+        $version->setBinaries($data->getBinaries());
+    
+        $version->getTags()->clear();
+        if ($data->getKeywords()) {
+            foreach ($data->getKeywords() as $keyword) {
+                $version->addTag(Tag::getByName($em, $keyword, true));
+            }
+        }
+        
+        $authorRepository = $this->doctrine->getRepository('PackagistWebBundle:Author');
+    
+        $version->getAuthors()->clear();
+        if ($data->getAuthors()) {
+            foreach ($data->getAuthors() as $authorData) {
+                $author = null;
+                // skip authors with no information
+                if (empty($authorData['email']) && empty($authorData['name'])) {
+                    continue;
+                }
+    
+                if (!empty($authorData['email'])) {
+                    $author = $authorRepository->findOneByEmail($authorData['email']);
+                }
+    
+                if (!$author && !empty($authorData['homepage'])) {
+                    $author = $authorRepository->findOneBy(array(
+                        'name' => $authorData['name'],
+                        'homepage' => $authorData['homepage']
+                    ));
+                }
+    
+                if (!$author && !empty($authorData['name'])) {
+                    $author = $authorRepository->findOneByNameAndPackage($authorData['name'], $package);
+                }
+    
+                if (!$author) {
+                    $author = new Author();
+                    $em->persist($author);
+                }
+    
+                foreach (array('email', 'name', 'homepage') as $field) {
+                    if (isset($authorData[$field])) {
+                        $author->{'set'.$field}($authorData[$field]);
+                    }
+                }
+    
+                $author->setUpdatedAt(new \DateTime);
+                if (!$version->getAuthors()->contains($author)) {
+                    $version->addAuthor($author);
+                }
+                if (!$author->getVersions()->contains($version)) {
+                    $author->addVersion($version);
+                }
+            }
+        }
+    
+        foreach ($this->supportedLinkTypes as $linkType => $linkEntity) {
+            $links = array();
+            foreach ($data->{'get'.$linkType.'s'}() as $link) {
+                $links[$link->getTarget()] = $link->getPrettyConstraint();
+            }
+    
+            foreach ($version->{'get'.$linkType}() as $link) {
+                // clear links that have changed/disappeared (for updates)
+                if (!isset($links[$link->getPackageName()]) || $links[$link->getPackageName()] !== $link->getPackageVersion()) {
+                    $version->{'get'.$linkType}()->removeElement($link);
+                    $em->remove($link);
+                } else {
+                    // clear those that are already set
+                    unset($links[$link->getPackageName()]);
+                }
+            }
+    
+            foreach ($links as $linkPackageName => $linkPackageVersion) {
+                $class = 'Packagist\WebBundle\Entity\\'.$linkEntity;
+                $link = new $class;
+                $link->setPackageName($linkPackageName);
+                $link->setPackageVersion($linkPackageVersion);
+                $version->{'add'.$linkType.'Link'}($link);
+                $link->setVersion($version);
+                $em->persist($link);
+            }
+        }
+    
+        if (!$package->getVersions()->contains($version)) {
+            $package->addVersions($version);
+        }
+    }
+}

+ 23 - 23
src/Packagist/WebBundle/Tests/Controller/AboutControllerTest.php

@@ -1,24 +1,24 @@
-<?php
-
-namespace Packagist\WebBundle\Tests\Controller;
-
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
-class AboutControllerTest extends WebTestCase
-{
-    public function testPackagist()
-    {
-        $client = self::createClient();
-
-        $crawler = $client->request('GET', '/about');
-        $this->assertEquals('What is Packagist?', $crawler->filter('.box h1')->first()->text());
-    }
-    
-    public function testComposer()
-    {
-        $client = self::createClient();
-
-        $crawler = $client->request('GET', '/about-composer');
-        $this->assertEquals('What is Composer?', $crawler->filter('.box h1')->first()->text());
-    }
+<?php
+
+namespace Packagist\WebBundle\Tests\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+
+class AboutControllerTest extends WebTestCase
+{
+    public function testPackagist()
+    {
+        $client = self::createClient();
+
+        $crawler = $client->request('GET', '/about');
+        $this->assertEquals('What is Packagist?', $crawler->filter('.box h1')->first()->text());
+    }
+    
+    public function testComposer()
+    {
+        $client = self::createClient();
+
+        $crawler = $client->request('GET', '/about-composer');
+        $this->assertEquals('What is Composer?', $crawler->filter('.box h1')->first()->text());
+    }
 }

+ 29 - 15
src/Packagist/WebBundle/Tests/Controller/ApiControllerTest.php

@@ -1,16 +1,30 @@
-<?php
-
-namespace Packagist\WebBundle\Tests\Controller;
-
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
-class ApiControllerTest extends WebTestCase
-{
-    public function testPackages()
-    {
-        $client = self::createClient();
-
-        $client->request('GET', '/packages.json');
-        $this->assertTrue(count(json_decode($client->getResponse()->getContent())) > 0);
-    }
+<?php
+
+namespace Packagist\WebBundle\Tests\Controller;
+
+use Packagist\WebBundle\Entity\User;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+
+class ApiControllerTest extends WebTestCase
+{
+    public function testPackages()
+    {
+        $client = self::createClient();
+
+        $client->request('GET', '/packages.json');
+        $this->assertTrue(count(json_decode($client->getResponse()->getContent())) > 0);
+    }
+
+    public function testGithubFailsCorrectly()
+    {
+        $client = self::createClient();
+        
+        $client->request('GET', '/api/github');
+        $this->assertEquals(405, $client->getResponse()->getStatusCode(), 'GET method should not be allowed for GitHub Post-Receive URL');
+
+        $payload = json_encode(array('repository' => array('url' => 'git://github.com/composer/composer',)));
+        $client->request('POST', '/api/github?username=INVALID_USER&apiToken=INVALID_TOKEN', array('payload' => $payload,));
+        $this->assertEquals(403, $client->getResponse()->getStatusCode(), 'POST method should return 403 "Forbidden" if invalid username and API Token are sent');
+    }
 }

+ 34 - 34
src/Packagist/WebBundle/Tests/Controller/WebControllerTest.php

@@ -1,35 +1,35 @@
-<?php
-
-namespace Packagist\WebBundle\Tests\Controller;
-
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
-class WebControllerTest extends WebTestCase
-{
-    public function testHomepage()
-    {
-        $client = self::createClient();
-
-        $crawler = $client->request('GET', '/');
-        $this->assertEquals('Getting Started', $crawler->filter('.getting-started h1')->text());
-    }
-    
-    public function testPackages()
-    {
-        $client = self::createClient();
-        //we expect at least one package
-        $crawler = $client->request('GET', '/packages/');
-        $this->assertTrue($crawler->filter('.packages li')->count() > 0);
-    }
-    
-    public function testPackage()
-    {
-        $client = self::createClient();
-        //we expect package to be clickable and showing at least 'package' div
-        $crawler = $client->request('GET', '/packages/');
-        $link = $crawler->filter('.packages li h1 a')->first()->attr('href');
-        
-        $crawler = $client->request('GET', $link);
-        $this->assertTrue($crawler->filter('.package')->count() > 0);
-    }
+<?php
+
+namespace Packagist\WebBundle\Tests\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+
+class WebControllerTest extends WebTestCase
+{
+    public function testHomepage()
+    {
+        $client = self::createClient();
+
+        $crawler = $client->request('GET', '/');
+        $this->assertEquals('Getting Started', $crawler->filter('.getting-started h1')->text());
+    }
+    
+    public function testPackages()
+    {
+        $client = self::createClient();
+        //we expect at least one package
+        $crawler = $client->request('GET', '/packages/');
+        $this->assertTrue($crawler->filter('.packages li')->count() > 0);
+    }
+    
+    public function testPackage()
+    {
+        $client = self::createClient();
+        //we expect package to be clickable and showing at least 'package' div
+        $crawler = $client->request('GET', '/packages/');
+        $link = $crawler->filter('.packages li h1 a')->first()->attr('href');
+        
+        $crawler = $client->request('GET', $link);
+        $this->assertTrue($crawler->filter('.package')->count() > 0);
+    }
 }