1
0
Fork 0
mirror of https://codeberg.org/demostf/api.git synced 2026-06-03 18:04:08 +02:00

upload framework

This commit is contained in:
Robin Appelman 2017-03-20 21:51:03 +01:00
commit a1a9504f11
30 changed files with 1578 additions and 155 deletions

View file

@ -1 +1,3 @@
.env .env
tests
vendor

View file

@ -1,42 +0,0 @@
<?php namespace Demo;
use MicrosoftAzure\Storage\Blob\Internal\IBlob;
class AzureStore implements IDemoStore {
/**
* @var IBlob
*/
private $blobStorage;
/**
* @param IBlob $blobStorage
*/
public function __construct(IBlob $blobStorage) {
$this->blobStorage = $blobStorage;
}
/**
* @param resource $stream
* @param string $name
* @return StoredDemo
*/
public function store($stream, $name) {
$name = preg_replace("/[^A-Za-z0-9\\.\\-]/", '', $name);
if (substr($name, -4) !== '.dem') {
$name .= '.dem';
}
$id = uniqid() . $name;
$this->upload($stream, $id);
$url = 'https://demostf.blob.core.windows.net/demos/' . $id;
return new StoredDemo('azure', $id, $url);
}
/**
* @param resource $stream
* @param string $id
* @return string mixed
*/
private function upload($stream, $id) {
$this->blobStorage->createBlockBlob('demos', $id, $stream);
}
}

View file

@ -1,10 +0,0 @@
<?php namespace Demo;
interface IDemoStore {
/**
* @param resource $stream
* @param string $name
* @return StoredDemo
*/
public function store($stream, $name);
}

View file

@ -1,50 +0,0 @@
<?php namespace Demo;
class StoredDemo {
/**
* @var string
*/
private $backend;
/**
* @var string
*/
private $path;
/**
* @var string
*/
private $url;
/**
* @param string $backend
* @param string $path
* @param string $url
*/
public function __construct($backend, $path, $url) {
$this->backend = $backend;
$this->path = $path;
$this->url = $url;
}
/**
* @return string
*/
public function getBackend() {
return $this->backend;
}
/**
* @return string
*/
public function getPath() {
return $this->path;
}
/**
* @return string
*/
public function getUrl() {
return $this->url;
}
}

View file

@ -2,7 +2,7 @@ FROM yavin/alpine-php-fpm:7.0
RUN apk add --no-cache php7-pdo_pgsql RUN apk add --no-cache php7-pdo_pgsql
COPY . /app/ COPY src /app/
RUN wget https://getcomposer.org/composer.phar \ RUN wget https://getcomposer.org/composer.phar \
&& php composer.phar -d=/app install --no-interaction \ && php composer.phar -d=/app install --no-interaction \

View file

@ -1,3 +1,11 @@
.PHONY: docker .PHONY: docker
docker: docker:
docker build -t demostf/api . docker build -t demostf/api .
.PHONY: testdb
testdb:
docker run -d --name api-test -p 5433:5432 -e POSTGRES_PASSWORD=test demostf/db
.PHONY: test
test:
cd tests; DB_PORT=5433 DB_TYPE=pgsql DB_HOST=localhost DB_USERNAME=postgres DB_USERNAME=postgres DB_PASSWORD=test DB_DATABASE=postgres phpunit

View file

@ -14,6 +14,13 @@
"autoload": { "autoload": {
"files": [ "files": [
"vendor/koraktor/steam-condenser/lib/steam-condenser.php" "vendor/koraktor/steam-condenser/lib/steam-condenser.php"
] ],
"psr-4": {
"Demostf\\API\\": "src/",
"Demostf\\API\\Test\\": "tests/"
}
},
"require-dev": {
"phpunit/phpunit": "^6.0"
} }
} }

1309
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
<?php namespace Controllers; <?php namespace Demostf\API\Controllers;
use Ehesp\SteamLogin\SteamLogin; use Ehesp\SteamLogin\SteamLogin;
use Providers\AuthProvider; use Demostf\API\Providers\AuthProvider;
use Providers\UserProvider; use Demostf\API\Providers\UserProvider;
class AuthController extends BaseController { class AuthController extends BaseController {
/** /**
@ -15,15 +15,20 @@ class AuthController extends BaseController {
*/ */
private $authProvider; private $authProvider;
/** @var string */
private $host;
/** /**
* AuthController constructor. * AuthController constructor.
* *
* @param UserProvider $userProvider * @param UserProvider $userProvider
* @param AuthProvider $authProvider * @param AuthProvider $authProvider
* @param string $host
*/ */
public function __construct(UserProvider $userProvider, AuthProvider $authProvider) { public function __construct(UserProvider $userProvider, AuthProvider $authProvider, string $host) {
$this->userProvider = $userProvider; $this->userProvider = $userProvider;
$this->authProvider = $authProvider; $this->authProvider = $authProvider;
$this->host = $host;
} }
public function token() { public function token() {
@ -41,7 +46,7 @@ class AuthController extends BaseController {
} }
public function login($token) { public function login($token) {
$_SESSION['return'] = $this->query('return', 'http://demos.tf'); $_SESSION['return'] = $this->query('return', 'https://' . $this->host);
$steam = new SteamLogin(); $steam = new SteamLogin();
$url = $steam->url($_ENV['APP_ROOT'] . '/auth/handle/' . urlencode($token)); $url = $steam->url($_ENV['APP_ROOT'] . '/auth/handle/' . urlencode($token));
\Flight::redirect(str_replace('&amp;', '&', $url)); // headers make no sense \Flight::redirect(str_replace('&amp;', '&', $url)); // headers make no sense

View file

@ -1,4 +1,4 @@
<?php namespace Controllers; <?php namespace Demostf\API\Controllers;
class BaseController { class BaseController {
protected function query($name, $default) { protected function query($name, $default) {

View file

@ -1,7 +1,7 @@
<?php namespace Controllers; <?php namespace Demostf\API\Controllers;
use Providers\DemoProvider; use Demostf\API\Providers\DemoProvider;
use Providers\MatchProvider; use Demostf\API\Providers\MatchProvider;
class DemoController extends BaseController { class DemoController extends BaseController {

View file

@ -1,8 +1,9 @@
<?php namespace Controllers; <?php namespace Demostf\API\Controllers;
use Demo\Parser; use Demostf\API\Demo\DemoStore;
use Providers\DemoProvider; use Demostf\API\Demo\Parser;
use Providers\UserProvider; use Demostf\API\Providers\DemoProvider;
use Demostf\API\Providers\UserProvider;
class UploadController extends BaseController { class UploadController extends BaseController {
@ -21,10 +22,14 @@ class UploadController extends BaseController {
*/ */
private $parser; private $parser;
public function __construct(DemoProvider $demoProvider, UserProvider $userProvider, Parser $parser) { /** @var DemoStore */
private $store;
public function __construct(DemoProvider $demoProvider, UserProvider $userProvider, Parser $parser, DemoStore $store) {
$this->demoProvider = $demoProvider; $this->demoProvider = $demoProvider;
$this->userProvider = $userProvider; $this->userProvider = $userProvider;
$this->parser = $parser; $this->parser = $parser;
$this->store = $store;
} }
public function upload() { public function upload() {
@ -52,8 +57,9 @@ class UploadController extends BaseController {
if ($size > 100 * 1024 * 1024) { if ($size > 100 * 1024 * 1024) {
return 'Demos cant be more than 100MB in size'; return 'Demos cant be more than 100MB in size';
} }
$tmpPath = $demo['tmp_name'];
try { try {
$info = $this->parser->parseFile($demo['tmp_name']); $info = $this->parser->parseHeader($tmpPath);
} catch (\Exception $e) { } catch (\Exception $e) {
return 'Not a valid demo'; return 'Not a valid demo';
} }
@ -66,8 +72,7 @@ class UploadController extends BaseController {
return 'Demos cant be longer than one hour'; return 'Demos cant be longer than one hour';
} }
$tmpPath = $demo->getPathname(); $hash = hash_file('md5', $tmpPath);
$hash = hash_file('md5', $demo['tmp_name']);
$existingDemo = $this->demoProvider->demoIdByHash($hash); $existingDemo = $this->demoProvider->demoIdByHash($hash);
if ($existingDemo) { if ($existingDemo) {
@ -79,7 +84,6 @@ class UploadController extends BaseController {
} }
} }
$handle = fopen($tmpPath, 'rb'); $url = $this->store->store($tmpPath, $hash . '_' . $name);
$storedDemo = $this->demoProvider->storeDemo($handle, $name);
} }
} }

View file

@ -1,8 +1,8 @@
<?php namespace Controllers; <?php namespace Demostf\API\Controllers;
use Ehesp\SteamLogin\SteamLogin; use Ehesp\SteamLogin\SteamLogin;
use Providers\AuthProvider; use Demostf\API\Providers\AuthProvider;
use Providers\UserProvider; use Demostf\API\Providers\UserProvider;
class UserController extends BaseController { class UserController extends BaseController {
/** /**

26
src/Demo/DemoStore.php Normal file
View file

@ -0,0 +1,26 @@
<?php namespace Demostf\API\Demo;
class DemoStore {
/** @var string */
private $root;
/** @var string */
private $webroot;
public function __construct(string $root, string $webroot) {
$this->root = $root;
$this->webroot = $webroot;
}
public function store(string $sourcePath, string $name): string {
rename($sourcePath, $this->generatePath($name));
return $this->getUrl($name);
}
private function generatePath(string $name): string {
return $this->root . '/' . substr($name, 0, 2) . '/' . substr($name, 2, 4) . '/' . $name;
}
private function getUrl(string $name): string {
return 'https://' . $this->webroot . '/' . substr($name, 0, 2) . '/' . substr($name, 2, 4) . '/' . $name;
}
}

View file

@ -1,4 +1,4 @@
<?php namespace Demo; <?php namespace Demostf\API\Demo;
/** /**
* HL2 demo metadata * HL2 demo metadata

View file

@ -1,4 +1,4 @@
<?php namespace Demo; <?php namespace Demostf\API\Demo;
use GuzzleHttp\Client; use GuzzleHttp\Client;
@ -40,7 +40,7 @@ class Parser {
* @return Header * @return Header
* @throws \Exception * @throws \Exception
*/ */
public function parseFile($path) { public function parseHeader($path) {
if (!is_readable($path)) { if (!is_readable($path)) {
throw new \Exception('Unable to open demo: ' . $path); throw new \Exception('Unable to open demo: ' . $path);
} }

View file

@ -1,4 +1,4 @@
<?php namespace Providers; <?php namespace Demostf\API\Providers;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use RandomLib\Generator; use RandomLib\Generator;

View file

@ -1,4 +1,4 @@
<?php namespace Providers; <?php namespace Demostf\API\Providers;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Platforms\MySqlPlatform;

View file

@ -1,8 +1,8 @@
<?php namespace Providers; <?php namespace Demostf\API\Providers;
use Demo\Header; use Demostf\API\Demo\Header;
use Demo\IDemoStore; use Demostf\API\Demo\IDemoStore;
use Demo\StoredDemo; use Demostf\API\Demo\StoredDemo;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
class DemoProvider extends BaseProvider { class DemoProvider extends BaseProvider {

View file

@ -0,0 +1,16 @@
<?php namespace Demostf\API\Providers;
use Doctrine\DBAL\Connection;
use RandomLib\Generator;
class UploadProvider extends BaseProvider {
/**
* @var Generator
*/
private $generator;
public function __construct(Connection $db, Generator $generator) {
parent::__construct($db);
$this->generator = $generator;
}
}

View file

@ -1,4 +1,4 @@
<?php namespace Providers; <?php namespace Demostf\API\Providers;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use RandomLib\Generator; use RandomLib\Generator;
@ -31,8 +31,8 @@ class UserProvider extends BaseProvider {
public function get($steamid) { public function get($steamid) {
$query = $this->getQueryBuilder(); $query = $this->getQueryBuilder();
$query->select(['id', 'steamid', 'name', 'avatar']) $query->select(['id', 'steamid', 'name', 'avatar', 'token'])
->from('user') ->from('users')
->where($query->expr()->eq('steamid', $query->createNamedParameter($steamid))); ->where($query->expr()->eq('steamid', $query->createNamedParameter($steamid)));
return $query->execute()->fetch(); return $query->execute()->fetch();
@ -80,7 +80,7 @@ class UserProvider extends BaseProvider {
public function byKey($key) { public function byKey($key) {
$query = $this->getQueryBuilder(); $query = $this->getQueryBuilder();
$query->select(['id', 'steamid', 'name', 'avatar']) $query->select(['id', 'steamid', 'name', 'avatar'])
->from('user') ->from('users')
->where($query->expr()->eq('token', $query->createNamedParameter($key))); ->where($query->expr()->eq('token', $query->createNamedParameter($key)));
return $query->execute()->fetch(); return $query->execute()->fetch();

View file

@ -1,35 +1,33 @@
<?php <?php namespace Demostf\API;
$autoloader = require __DIR__ . '/vendor/autoload.php'; use Flight;
$autoloader->setPsr4('Providers\\', __DIR__ . '/Providers');
$autoloader->setPsr4('Demo\\', __DIR__ . '/Demo'); require_once __DIR__ . '/init.php';
$autoloader->setPsr4('Controllers\\', __DIR__ . '/Controllers');
if (!getenv('DB_TYPE')) {
Dotenv::load(__DIR__);
}
$connectionParams = array( $connectionParams = array(
'dbname' => getenv('DB_DATABASE'), 'dbname' => getenv('DB_DATABASE'),
'user' => getenv('DB_USERNAME'), 'user' => getenv('DB_USERNAME'),
'password' => getenv('DB_PASSWORD'), 'password' => getenv('DB_PASSWORD'),
'host' => getenv('DB_HOST'), 'host' => getenv('DB_HOST'),
'port' => getenv('DB_PORT'),
'driver' => getenv('DB_TYPE'), 'driver' => getenv('DB_TYPE'),
); );
if ($connectionParams['driver'] === 'pgsql') { if ($connectionParams['driver'] === 'pgsql') {
$connectionParams['driver'] = 'pdo_pgsql'; $connectionParams['driver'] = 'pdo_pgsql';
} }
$db = \Doctrine\DBAL\DriverManager::getConnection($connectionParams); $db = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
$host = getenv('BASE_HOST');
$demoProvider = new \Providers\DemoProvider($db); $demoProvider = new Providers\DemoProvider($db);
$factory = new \RandomLib\Factory; $factory = new \RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator(); $generator = $factory->getMediumStrengthGenerator();
$authProvider = new \Providers\AuthProvider($db, $generator); $authProvider = new Providers\AuthProvider($db, $generator);
$userProvider = new \Providers\UserProvider($db, $generator); $userProvider = new Providers\UserProvider($db, $generator);
$demoController = new \Controllers\DemoController($demoProvider); $demoController = new Controllers\DemoController($demoProvider);
$authController = new \Controllers\AuthController($userProvider, $authProvider); $authController = new Controllers\AuthController($userProvider, $authProvider, $host);
$userController = new \Controllers\UserController($userProvider); $userController = new Controllers\UserController($userProvider);
Flight::route('/*', function () { Flight::route('/*', function () {
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Origin: *');

6
src/init.php Normal file
View file

@ -0,0 +1,6 @@
<?php
$autoloader = require __DIR__ . '/../vendor/autoload.php';
if (!getenv('DB_TYPE')) {
Dotenv::load(__DIR__ . '/../');
}

3
src/public/upload.php Normal file
View file

@ -0,0 +1,3 @@
<?php
require __DIR__ . '/../upload.php';

26
src/upload.php Normal file
View file

@ -0,0 +1,26 @@
<?php namespace Demostf\API;
require_once __DIR__ . '/init.php';
$connectionParams = array(
'dbname' => getenv('DB_DATABASE'),
'user' => getenv('DB_USERNAME'),
'password' => getenv('DB_PASSWORD'),
'host' => getenv('DB_HOST'),
'driver' => getenv('DB_TYPE'),
);
if ($connectionParams['driver'] === 'pgsql') {
$connectionParams['driver'] = 'pdo_pgsql';
}
$db = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
$host = getenv('BASE_HOST');
$storeRoot = getenv('DEMO_ROOT');
$demoProvider = new Providers\DemoProvider($db);
$factory = new \RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$userProvider = new Providers\UserProvider($db, $generator);
$store = new Demo\DemoStore($storeRoot, 'static.' . $host);
$uploadController = new Controllers\UploadController($demoProvider, $userProvider, new Demo\Parser(), $store);
$uploadController->upload();

View file

@ -0,0 +1,52 @@
<?php namespace Demostf\API\Test\Providers;
use Demostf\API\Providers\UserProvider;
use Demostf\API\Test\TestCase;
class UserProviderTest extends TestCase {
/** @var UserProvider */
private $provider;
/** @var \SteamId */
private $steamId;
public function setUp() {
parent::setUp();
$this->steamId = new \SteamId('76561198024494988');
$this->provider = new UserProvider($this->getDatabaseConnection(), $this->getRandomGenerator());
}
public function testGetNonExisting() {
$this->assertFalse($this->provider->get('76561198024494988'));
}
public function testStoreRetrieve() {
$this->provider->store($this->steamId);
$user = $this->provider->get('76561198024494988');
$this->assertEquals($this->steamId->getNickname(), $user['name']);
$this->assertEquals($this->steamId->getSteamId64(), '76561198024494988');
}
public function testDoubleInsert() {
$this->provider->store($this->steamId);
$this->provider->store($this->steamId);
}
public function testByKey() {
$this->provider->store($this->steamId);
$user = $this->provider->get('76561198024494988');
$byKey = $this->provider->byKey($user['token']);
$this->assertEquals('76561198024494988', $byKey['steamid']);
}
public function testSearch() {
$result = $this->provider->search('__NOT__FOUND__');
$this->assertCount(0, $result);
}
}

55
tests/TestCase.php Normal file
View file

@ -0,0 +1,55 @@
<?php namespace Demostf\API\Test;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
abstract class TestCase extends \PHPUnit\Framework\TestCase {
/** @var Connection */
private $database;
protected function getDatabaseConnection() {
if (!$this->database instanceof Connection) {
$connectionParams = array(
'dbname' => getenv('DB_DATABASE'),
'user' => getenv('DB_USERNAME'),
'password' => getenv('DB_PASSWORD'),
'host' => getenv('DB_HOST'),
'port' => getenv('DB_PORT'),
'driver' => getenv('DB_TYPE'),
);
if ($connectionParams['driver'] === 'pgsql') {
$connectionParams['driver'] = 'pdo_pgsql';
}
$this->database = DriverManager::getConnection($connectionParams);
}
return $this->database;
}
public function setUp() {
parent::setUp();
}
public function tearDown() {
$this->clearDatabase();
parent::tearDown();
}
private function clearDatabase() {
if ($this->database instanceof Connection) {
$tables = $this->database->getSchemaManager()->listTables();
foreach ($tables as $table) {
$this->truncateTable($table->getName());
}
}
}
private function truncateTable(string $tableName) {
$sql = sprintf('TRUNCATE TABLE %s;', $tableName);
$this->getDatabaseConnection()->query($sql);
}
protected function getRandomGenerator() {
$factory = new \RandomLib\Factory;
return $factory->getMediumStrengthGenerator();
}
}

1
tests/bootstrap.php Normal file
View file

@ -0,0 +1 @@
<?php require_once __DIR__ . '/../src/init.php';

11
tests/phpunit.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="bootstrap.php">
<testsuite name='API'>
<directory suffix='Test.php'>./</directory>
</testsuite>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>
</phpunit>