mirror of
https://codeberg.org/demostf/api.git
synced 2026-06-03 18:04:08 +02:00
upload framework
This commit is contained in:
parent
754b1ce108
commit
a1a9504f11
30 changed files with 1578 additions and 155 deletions
77
src/Controllers/AuthController.php
Normal file
77
src/Controllers/AuthController.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php namespace Demostf\API\Controllers;
|
||||
|
||||
use Ehesp\SteamLogin\SteamLogin;
|
||||
use Demostf\API\Providers\AuthProvider;
|
||||
use Demostf\API\Providers\UserProvider;
|
||||
|
||||
class AuthController extends BaseController {
|
||||
/**
|
||||
* @var UserProvider
|
||||
*/
|
||||
private $userProvider;
|
||||
|
||||
/**
|
||||
* @var AuthProvider
|
||||
*/
|
||||
private $authProvider;
|
||||
|
||||
/** @var string */
|
||||
private $host;
|
||||
|
||||
/**
|
||||
* AuthController constructor.
|
||||
*
|
||||
* @param UserProvider $userProvider
|
||||
* @param AuthProvider $authProvider
|
||||
* @param string $host
|
||||
*/
|
||||
public function __construct(UserProvider $userProvider, AuthProvider $authProvider, string $host) {
|
||||
$this->userProvider = $userProvider;
|
||||
$this->authProvider = $authProvider;
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
public function token() {
|
||||
echo $this->authProvider->generateToken();
|
||||
}
|
||||
|
||||
public function get($token) {
|
||||
$userData = $this->authProvider->getUser($token);
|
||||
\Flight::json([
|
||||
'token' => $token,
|
||||
'steamid' => $userData['steamid'],
|
||||
'name' => $userData['name'],
|
||||
'key' => $userData['key']
|
||||
]);
|
||||
}
|
||||
|
||||
public function login($token) {
|
||||
$_SESSION['return'] = $this->query('return', 'https://' . $this->host);
|
||||
$steam = new SteamLogin();
|
||||
$url = $steam->url($_ENV['APP_ROOT'] . '/auth/handle/' . urlencode($token));
|
||||
\Flight::redirect(str_replace('&', '&', $url)); // headers make no sense
|
||||
}
|
||||
|
||||
public function logout($token) {
|
||||
$this->authProvider->logout($token);
|
||||
\Flight::json([
|
||||
'token' => $token,
|
||||
'steamid' => null,
|
||||
'name' => null,
|
||||
'key' => null
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle($token) {
|
||||
$return = isset($_SESSION['return']) ? $_SESSION['return'] : 'http://demos.tf';
|
||||
unset($_SESSION['return']);
|
||||
$steam = new SteamLogin();
|
||||
$steamId = $steam->validate();
|
||||
if ($steamId) {
|
||||
$steamIdObject = new \SteamId($steamId);
|
||||
$key = $this->userProvider->store($steamIdObject);
|
||||
$this->authProvider->setUser($token, $steamIdObject, $key);
|
||||
}
|
||||
\Flight::redirect($return);
|
||||
}
|
||||
}
|
||||
18
src/Controllers/BaseController.php
Normal file
18
src/Controllers/BaseController.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php namespace Demostf\API\Controllers;
|
||||
|
||||
class BaseController {
|
||||
protected function query($name, $default) {
|
||||
$request = \Flight::request();
|
||||
return isset($request->query[$name]) ? $request->query[$name] : $default;
|
||||
}
|
||||
|
||||
protected function file($name) {
|
||||
$request = \Flight::request();
|
||||
return $request->files[$name];
|
||||
}
|
||||
|
||||
protected function post($name, $default = null) {
|
||||
$request = \Flight::request();
|
||||
return isset($request->data[$name]) ? $request->data[$name] : $default;
|
||||
}
|
||||
}
|
||||
81
src/Controllers/DemoController.php
Normal file
81
src/Controllers/DemoController.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php namespace Demostf\API\Controllers;
|
||||
|
||||
use Demostf\API\Providers\DemoProvider;
|
||||
use Demostf\API\Providers\MatchProvider;
|
||||
|
||||
class DemoController extends BaseController {
|
||||
|
||||
/**
|
||||
* @var \Providers\DemoProvider
|
||||
*/
|
||||
private $demoProvider;
|
||||
|
||||
public function __construct(DemoProvider $demoProvider) {
|
||||
$this->demoProvider = $demoProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*/
|
||||
public function get($id) {
|
||||
\Flight::json($this->demoProvider->get($id));
|
||||
}
|
||||
|
||||
protected function getFilter() {
|
||||
$map = $this->query('map', '');
|
||||
$players = $this->query('players', '');
|
||||
$type = $this->query('type', '');
|
||||
$filter = [];
|
||||
if ($map) {
|
||||
$filter['map'] = $map;
|
||||
}
|
||||
if ($players) {
|
||||
if (!is_array($players)) {
|
||||
$players = explode(',', $players);
|
||||
}
|
||||
$players = array_filter($players);
|
||||
$filter['players'] = $players;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'hl':
|
||||
$filter['playerCount'] = [17, 18, 19];
|
||||
break;
|
||||
case '6v6':
|
||||
$filter['playerCount'] = [11, 12, 13];
|
||||
break;
|
||||
case '4v4':
|
||||
$filter['playerCount'] = [7, 8, 9];
|
||||
break;
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
public function listDemos() {
|
||||
$page = $this->query('page', 1);
|
||||
\Flight::json($this->demoProvider->listDemos($page, $this->getFilter()));
|
||||
}
|
||||
|
||||
public function listProfile($steamid) {
|
||||
$page = $this->query('page', 1);
|
||||
$where = $this->getFilter();
|
||||
$where['players'][] = $steamid;
|
||||
\Flight::json($this->demoProvider->listProfile($page, $where));
|
||||
}
|
||||
|
||||
public function listUploads($steamid) {
|
||||
$page = $this->query('page', 1);
|
||||
\Flight::json($this->demoProvider->listUploads($steamid, $page, $this->getFilter()));
|
||||
}
|
||||
|
||||
public function chat($demoId) {
|
||||
\Flight::json($this->demoProvider->getChat($demoId));
|
||||
}
|
||||
|
||||
public function listMaps() {
|
||||
\Flight::json($this->demoProvider->listMaps());
|
||||
}
|
||||
|
||||
public function stats() {
|
||||
\Flight::json($this->demoProvider->getStats());
|
||||
}
|
||||
}
|
||||
89
src/Controllers/UploadController.php
Normal file
89
src/Controllers/UploadController.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php namespace Demostf\API\Controllers;
|
||||
|
||||
use Demostf\API\Demo\DemoStore;
|
||||
use Demostf\API\Demo\Parser;
|
||||
use Demostf\API\Providers\DemoProvider;
|
||||
use Demostf\API\Providers\UserProvider;
|
||||
|
||||
class UploadController extends BaseController {
|
||||
|
||||
/**
|
||||
* @var \Providers\DemoProvider
|
||||
*/
|
||||
private $demoProvider;
|
||||
|
||||
/**
|
||||
* @var UserProvider
|
||||
*/
|
||||
private $userProvider;
|
||||
|
||||
/**
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/** @var DemoStore */
|
||||
private $store;
|
||||
|
||||
public function __construct(DemoProvider $demoProvider, UserProvider $userProvider, Parser $parser, DemoStore $store) {
|
||||
$this->demoProvider = $demoProvider;
|
||||
$this->userProvider = $userProvider;
|
||||
$this->parser = $parser;
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
public function upload() {
|
||||
$key = $this->post('key');
|
||||
$red = $this->post('red', 'RED');
|
||||
$blu = $this->post('blu', 'BLU');
|
||||
$name = $this->post('name', 'Unnamed');
|
||||
$demo = $this->file('demo');
|
||||
$user = $this->userProvider->byKey($key);
|
||||
if (!$user) {
|
||||
return 'Invalid key';
|
||||
}
|
||||
if (!$red) {
|
||||
$red = 'RED';
|
||||
}
|
||||
if (!$blu) {
|
||||
$blu = 'BLU';
|
||||
}
|
||||
|
||||
$size = $demo['size'];
|
||||
if ($size < 1024) {
|
||||
return 'Demos needs to be at least 1KB is size';
|
||||
}
|
||||
|
||||
if ($size > 100 * 1024 * 1024) {
|
||||
return 'Demos cant be more than 100MB in size';
|
||||
}
|
||||
$tmpPath = $demo['tmp_name'];
|
||||
try {
|
||||
$info = $this->parser->parseHeader($tmpPath);
|
||||
} catch (\Exception $e) {
|
||||
return 'Not a valid demo';
|
||||
}
|
||||
|
||||
if ($info->getDuration() < (5 * 60)) {
|
||||
return 'Demos need to be at least 5m long';
|
||||
}
|
||||
|
||||
if ($info->getDuration() > (60 * 60)) {
|
||||
return 'Demos cant be longer than one hour';
|
||||
}
|
||||
|
||||
$hash = hash_file('md5', $tmpPath);
|
||||
|
||||
$existingDemo = $this->demoProvider->demoIdByHash($hash);
|
||||
if ($existingDemo) {
|
||||
if ($key) {
|
||||
return 'STV available at: https://demos.tf/' . $existingDemo;
|
||||
} else {
|
||||
\Flight::redirect('https://demos.tf/' . $existingDemo);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
$url = $this->store->store($tmpPath, $hash . '_' . $name);
|
||||
}
|
||||
}
|
||||
30
src/Controllers/UserController.php
Normal file
30
src/Controllers/UserController.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php namespace Demostf\API\Controllers;
|
||||
|
||||
use Ehesp\SteamLogin\SteamLogin;
|
||||
use Demostf\API\Providers\AuthProvider;
|
||||
use Demostf\API\Providers\UserProvider;
|
||||
|
||||
class UserController extends BaseController {
|
||||
/**
|
||||
* @var UserProvider
|
||||
*/
|
||||
private $userProvider;
|
||||
|
||||
/**
|
||||
* UserController constructor.
|
||||
*
|
||||
* @param UserProvider $userProvider
|
||||
*/
|
||||
public function __construct(UserProvider $userProvider) {
|
||||
$this->userProvider = $userProvider;
|
||||
}
|
||||
|
||||
public function get($steamid) {
|
||||
\Flight::json($this->userProvider->get($steamid));
|
||||
}
|
||||
|
||||
public function search() {
|
||||
$query = $this->query('query', '');
|
||||
\Flight::json($this->userProvider->search($query));
|
||||
}
|
||||
}
|
||||
26
src/Demo/DemoStore.php
Normal file
26
src/Demo/DemoStore.php
Normal 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;
|
||||
}
|
||||
}
|
||||
156
src/Demo/Header.php
Normal file
156
src/Demo/Header.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php namespace Demostf\API\Demo;
|
||||
|
||||
/**
|
||||
* HL2 demo metadata
|
||||
*/
|
||||
class Header {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $protocol;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $server;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $nick;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $map;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $game;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected $duration;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $ticks;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $frames;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $sigon;
|
||||
|
||||
/**
|
||||
* @param array $info
|
||||
*/
|
||||
public function __construct($info) {
|
||||
$this->type = $info['type'];
|
||||
$this->version = $info['version'];
|
||||
$this->protocol = $info['protocol'];
|
||||
$this->server = $info['server'];
|
||||
$this->nick = $info['nick'];
|
||||
$this->map = $info['map'];
|
||||
$this->game = $info['game'];
|
||||
$this->duration = $info['duration'];
|
||||
$this->ticks = $info['ticks'];
|
||||
$this->frames = $info['frames'];
|
||||
$this->sigon = $info['sigon'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getDuration() {
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFrames() {
|
||||
return $this->frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getGame() {
|
||||
return $this->game;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMap() {
|
||||
return $this->map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNick() {
|
||||
return $this->nick;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getProtocol() {
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServer() {
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSigon() {
|
||||
return $this->sigon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTicks() {
|
||||
return $this->ticks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersion() {
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
}
|
||||
133
src/Demo/Parser.php
Normal file
133
src/Demo/Parser.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php namespace Demostf\API\Demo;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class Parser {
|
||||
const ANALYSER_BASEURL = 'http://demoserver.azurewebsites.net';
|
||||
|
||||
/**
|
||||
* @param string $head string containing the demo header binary data
|
||||
* @return Header
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function parseString($head) {
|
||||
set_error_handler(array($this, 'errorHandler'));
|
||||
$info = unpack("A8type/Iversion/Iprotocol/A260server/A260nick/A260map/A260game/fduration/Iticks/Iframes/Isigon",
|
||||
$head);
|
||||
restore_error_handler();
|
||||
if ($info['type'] !== 'HL2DEMO') {
|
||||
throw new \Exception('Not an HL2 demo');
|
||||
}
|
||||
return new Header($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse demo info from a stream
|
||||
*
|
||||
* @param resource $stream
|
||||
* @return Header
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function parseStream($stream) {
|
||||
$head = fread($stream, 2048);
|
||||
return $this->parseString($head);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse demo info from a local file
|
||||
*
|
||||
* @param string $path
|
||||
* @return Header
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function parseHeader($path) {
|
||||
if (!is_readable($path)) {
|
||||
throw new \Exception('Unable to open demo: ' . $path);
|
||||
}
|
||||
$fh = fopen($path, 'rb');
|
||||
return $this->parseStream($fh);
|
||||
}
|
||||
|
||||
public function analyse(StoredDemo $storedDemo) {
|
||||
$endPoint = self::ANALYSER_BASEURL . '/url';
|
||||
$client = new Client();
|
||||
$response = $client->post($endPoint, [
|
||||
'body' => $storedDemo->getUrl()
|
||||
]);
|
||||
$data = $response->getBody();
|
||||
return $this->handleData($data);
|
||||
}
|
||||
|
||||
private function handleData($data) {
|
||||
if (!is_array($data)) {
|
||||
throw new \Exception('Error parsing demo');
|
||||
}
|
||||
$intervalPerTick = $data['intervalPerTick'];
|
||||
$red = 0;
|
||||
$blue = 0;
|
||||
$chat = [];
|
||||
$players = [];
|
||||
foreach ($data['rounds'] as $round) {
|
||||
if ($round['winner'] === 'red') {
|
||||
$red++;
|
||||
} else {
|
||||
$blue++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data['chat'] as $message) {
|
||||
if (isset($message['from'])) {
|
||||
$chat[] = [
|
||||
'time' => floor(($message['tick'] - $data['startTick']) * $intervalPerTick),
|
||||
'from' => $message['from'],
|
||||
'text' => $message['text']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data['users'] as $player) {
|
||||
$class = 0;
|
||||
$classSpawns = 0;
|
||||
foreach ($player['classes'] as $classId => $spawns) {
|
||||
if ($spawns > $classSpawns) {
|
||||
$classSpawns = $spawns;
|
||||
$class = $classId;
|
||||
}
|
||||
}
|
||||
if ($class and $player['steamId']) {//skip spectators
|
||||
$players[] = [
|
||||
'name' => $player['name'],
|
||||
'demo_user_id' => $player['userId'],
|
||||
'steam_id' => $player['steamId'],
|
||||
'team' => $player['team'],
|
||||
'class' => $this->getClassName($class)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'score' => [
|
||||
'red' => $red,
|
||||
'blue' => $blue
|
||||
],
|
||||
'chat' => $chat,
|
||||
'players' => $players,
|
||||
'kills' => $data['deaths']
|
||||
];
|
||||
}
|
||||
|
||||
private function getClassName($classId) {
|
||||
$classes = [
|
||||
1 => 'scout',
|
||||
2 => 'sniper',
|
||||
3 => 'soldier',
|
||||
4 => 'demoman',
|
||||
5 => 'medic',
|
||||
6 => 'heavyweapons',
|
||||
7 => 'pyro',
|
||||
8 => 'spy',
|
||||
9 => 'engineer'
|
||||
];
|
||||
return isset($classes[$classId]) ? $classes[$classId] : 'Unknown';
|
||||
}
|
||||
}
|
||||
38
src/Providers/AuthProvider.php
Normal file
38
src/Providers/AuthProvider.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php namespace Demostf\API\Providers;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use RandomLib\Generator;
|
||||
|
||||
class AuthProvider extends BaseProvider {
|
||||
/**
|
||||
* @var Generator
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
public function __construct(Connection $db, Generator $generator) {
|
||||
parent::__construct($db);
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
public function generateToken() {
|
||||
return $this->generator->generateString(32, Generator::CHAR_ALNUM);
|
||||
}
|
||||
|
||||
public function setUser($token, \SteamId $steamid, $key) {
|
||||
apc_store($token, [
|
||||
'name' => $steamid->getNickname(),
|
||||
'steamid' => $steamid->getSteamId64(),
|
||||
'key' => $key
|
||||
]);
|
||||
}
|
||||
|
||||
public function getUser($token) {
|
||||
$found = true;
|
||||
$result = apc_fetch($token, $found);
|
||||
return ($found) ? $result : ['name' => null, 'steamid' => null, 'key' => null];
|
||||
}
|
||||
|
||||
public function logout($token) {
|
||||
apc_delete($token);
|
||||
}
|
||||
}
|
||||
70
src/Providers/BaseProvider.php
Normal file
70
src/Providers/BaseProvider.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php namespace Demostf\API\Providers;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Platforms\MySqlPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use LessQL\Database;
|
||||
|
||||
class BaseProvider {
|
||||
/**
|
||||
* @var Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var \LessQL\Database
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
public function __construct(Connection $connection) {
|
||||
$this->connection = $connection;
|
||||
$this->db = new Database($connection->getWrappedConnection());
|
||||
$this->dbConfig();
|
||||
}
|
||||
|
||||
private function dbConfig() {
|
||||
$platform = $this->connection->getDatabasePlatform();
|
||||
if ($platform instanceof MySqlPlatform) {
|
||||
$this->db->setIdentifierDelimiter("`");
|
||||
} else {
|
||||
$this->db->setIdentifierDelimiter('"');
|
||||
}
|
||||
|
||||
$this->db->setRewrite(function ($table) {
|
||||
$rawNames = ['chat'];
|
||||
$aliases = [
|
||||
|
||||
];
|
||||
if (isset($aliases[$table])) {
|
||||
return $aliases[$table];
|
||||
} else if (array_search($table, $rawNames) === false) {
|
||||
return $table . 's';
|
||||
} else {
|
||||
return $table;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function query($sql, array $params = []) {
|
||||
$delimiter = $this->db->getIdentifierDelimiter();
|
||||
$platform = $this->connection->getDatabasePlatform();
|
||||
$sql = str_replace('`', $delimiter, $sql);
|
||||
|
||||
if ($platform instanceof PostgreSqlPlatform) {
|
||||
$sql = str_replace('FROM_UNIXTIME(', 'to_timestamp(', $sql);
|
||||
}
|
||||
|
||||
$query = $this->connection->prepare($sql);
|
||||
$query->execute($params);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
protected function getQueryBuilder() {
|
||||
return new QueryBuilder($this->connection);
|
||||
}
|
||||
}
|
||||
186
src/Providers/DemoProvider.php
Normal file
186
src/Providers/DemoProvider.php
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
<?php namespace Demostf\API\Providers;
|
||||
|
||||
use Demostf\API\Demo\Header;
|
||||
use Demostf\API\Demo\IDemoStore;
|
||||
use Demostf\API\Demo\StoredDemo;
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
class DemoProvider extends BaseProvider {
|
||||
const VERSION = 4;
|
||||
|
||||
|
||||
public function __construct(Connection $connection) {
|
||||
parent::__construct($connection);
|
||||
}
|
||||
|
||||
public function get($id) {
|
||||
$demo = $this->db->demo()->where('id', $id);
|
||||
|
||||
// sql magic
|
||||
$sql = 'WITH demokills AS (SELECT attacker_id, assister_id, victim_id FROM kills WHERE demo_id = ?)
|
||||
SELECT players.id, user_id, players.name, team, class, users.steamid, users.avatar,
|
||||
(SELECT COUNT(*) FROM demokills WHERE attacker_id=players.user_id) AS kills,
|
||||
(SELECT COUNT(*) FROM demokills WHERE assister_id=players.user_id) AS assists,
|
||||
(SELECT COUNT(*) FROM demokills WHERE victim_id=players.user_id) AS deaths
|
||||
FROM players
|
||||
INNER JOIN demos ON demos.id = players.demo_id
|
||||
INNER JOIN users ON players.user_id = users.id
|
||||
WHERE demo_id = ?
|
||||
';
|
||||
|
||||
$uploader = $demo->user()->via('uploader')->fetch();
|
||||
$demoData = $demo->fetch();
|
||||
$playerQuery = $this->query($sql, [$demoData['id'], $demoData['id']]);
|
||||
$players = $playerQuery->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$formattedDemo = $this->formatDemo($demoData);
|
||||
$formattedDemo['players'] = $players;
|
||||
$formattedDemo['uploader'] = [
|
||||
'id' => $uploader['id'],
|
||||
'steamid' => $uploader['steamid'],
|
||||
'name' => $uploader['name']
|
||||
];
|
||||
return $formattedDemo;
|
||||
}
|
||||
|
||||
public function listUploads($steamid, $page, $where = []) {
|
||||
$user = $this->db->user()->where('steamid', $steamid);
|
||||
$where['uploader'] = $user->fetch()->id;
|
||||
return $this->listDemos($page, $where);
|
||||
}
|
||||
|
||||
public function listProfile($page, $where = []) {
|
||||
$users = $this->db->user()->where('steamid', $where['players']);
|
||||
unset($where['players']);
|
||||
$userIds = [];
|
||||
foreach ($users as $user) {
|
||||
$userIds[] = $user['id'];
|
||||
}
|
||||
$in = implode(', ', array_fill(0, count($userIds), '?'));
|
||||
|
||||
$sql = 'SELECT demos.id FROM demos INNER JOIN players ON players.demo_id = demos.id
|
||||
WHERE players.user_id IN (' . $in . ') GROUP BY demos.id HAVING COUNT(user_id) = ? ORDER BY demos.id DESC LIMIT 50 OFFSET ' . ((int)$page - 1) * 50;
|
||||
|
||||
$params = $userIds;
|
||||
$params[] = count($userIds);
|
||||
|
||||
$result = $this->query($sql, $params);
|
||||
$demoIds = $result->fetchAll(\PDO::FETCH_COLUMN);
|
||||
|
||||
$demos = $this->db->demo()->where('id', $demoIds)
|
||||
->where($where)
|
||||
->orderBy('id', 'DESC');
|
||||
return $this->formatList($demos);
|
||||
}
|
||||
|
||||
public function listDemos($page, $where = []) {
|
||||
if (isset($where['players']) and is_array($where['players']) and count($where['players']) > 0) {
|
||||
return $this->listProfile($page, $where);
|
||||
}
|
||||
|
||||
$offset = ($page - 1) * 50;
|
||||
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->select('d.*')
|
||||
->from('demos', 'd')
|
||||
->leftJoin('d', 'upload_blacklist', 'b', $query->expr()->eq('uploader_id', 'uploader'))
|
||||
->where($query->expr()->isNull('b.id'));
|
||||
if (isset($where['map'])) {
|
||||
$query->where($query->expr()->eq('map', $query->createNamedParameter($where['map'])));
|
||||
}
|
||||
if (isset($where['playerCount'])) {
|
||||
$query->where($query->expr()->in('playerCount', $query->createNamedParameter($where['playerCount'], Connection::PARAM_INT_ARRAY)));
|
||||
}
|
||||
$query->orderBy('d.id', 'DESC')
|
||||
->setMaxResults(50)
|
||||
->setFirstResult($offset);
|
||||
|
||||
$demos = $query->execute()->fetchAll();
|
||||
return $this->formatList($demos);
|
||||
}
|
||||
|
||||
public function listMaps() {
|
||||
$sql = 'SELECT DISTINCT(map), COUNT(map) AS count from demos GROUP BY map ORDER BY count DESC';
|
||||
$result = $this->query($sql);
|
||||
return $result->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
public function getChat($demoId) {
|
||||
$chat = $this->db->chat()->where('demo_id', $demoId);
|
||||
$result = [];
|
||||
foreach ($chat as $message) {
|
||||
$result[] = [
|
||||
'message' => $message['text'],
|
||||
'user' => $message['from'],
|
||||
'time' => $message['time']
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function formatList($demos) {
|
||||
$result = [];
|
||||
foreach ($demos as $demo) {
|
||||
$result[] = $this->formatDemo($demo);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function formatDemo($demoData) {
|
||||
return [
|
||||
'id' => $demoData['id'],
|
||||
'url' => $demoData['url'],
|
||||
'name' => $demoData['name'],
|
||||
'server' => $demoData['server'],
|
||||
'duration' => $demoData['duration'],
|
||||
'nick' => $demoData['nick'],
|
||||
'map' => $demoData['map'],
|
||||
'time' => strtotime($demoData['created_at']),
|
||||
'red' => $demoData['red'],
|
||||
'blue' => $demoData['blu'],
|
||||
'redScore' => $demoData['scoreRed'],
|
||||
'blueScore' => $demoData['scoreBlue'],
|
||||
'playerCount' => $demoData['playerCount'],
|
||||
'uploader' => $demoData['uploader']
|
||||
];
|
||||
}
|
||||
|
||||
private function formatTeam($teamInfo) {
|
||||
if ($teamInfo === null) {
|
||||
return $teamInfo;
|
||||
}
|
||||
return [
|
||||
'id' => $teamInfo['id'],
|
||||
'profileId' => $teamInfo['profile_id'],
|
||||
'name' => $teamInfo['name'],
|
||||
'tag' => $teamInfo['tag'],
|
||||
'avatar' => $teamInfo['avatar'],
|
||||
'steam' => $teamInfo['steam'],
|
||||
'league' => $teamInfo['league'],
|
||||
'division' => $teamInfo['division']
|
||||
];
|
||||
}
|
||||
|
||||
public function getStats() {
|
||||
$demoCount = $this->db->demo()->count();
|
||||
$playerCount = $this->db->user()->count();
|
||||
|
||||
$sql = 'SELECT count(user_id) FROM players GROUP BY user_id';
|
||||
$result = $this->query($sql);
|
||||
|
||||
return [
|
||||
'demos' => $demoCount,
|
||||
'players' => $playerCount,
|
||||
'uploaders' => $result->fetchColumn()
|
||||
];
|
||||
}
|
||||
|
||||
public function demoIdByHash($hash) {
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->select('hash')
|
||||
->from('demos')
|
||||
->where($query->expr()->eq('hash', $query->createNamedParameter($hash)));
|
||||
|
||||
return $query->execute()->fetchColumn();
|
||||
}
|
||||
}
|
||||
16
src/Providers/UploadProvider.php
Normal file
16
src/Providers/UploadProvider.php
Normal 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;
|
||||
}
|
||||
}
|
||||
88
src/Providers/UserProvider.php
Normal file
88
src/Providers/UserProvider.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php namespace Demostf\API\Providers;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use RandomLib\Generator;
|
||||
|
||||
class UserProvider extends BaseProvider {
|
||||
/**
|
||||
* @var Generator
|
||||
*/
|
||||
private $generator;
|
||||
|
||||
public function __construct(Connection $db, Generator $generator) {
|
||||
parent::__construct($db);
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
public function store(\SteamId $steamId) {
|
||||
$sql = 'INSERT INTO users(steamid, name, avatar, token)
|
||||
SELECT ?, ?, ?, ? WHERE NOT EXISTS(SELECT id FROM users WHERE steamid = ?)';
|
||||
$this->query($sql, [
|
||||
$steamId->getSteamId64(),
|
||||
$steamId->getNickname(),
|
||||
$steamId->getMediumAvatarUrl(),
|
||||
$this->generator->generateString(64),
|
||||
$steamId->getSteamId64()
|
||||
]);
|
||||
|
||||
$user = $this->db->user()->where('steamid', $steamId->getSteamId64());
|
||||
return $user->fetch()->token;
|
||||
}
|
||||
|
||||
public function get($steamid) {
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->select(['id', 'steamid', 'name', 'avatar', 'token'])
|
||||
->from('users')
|
||||
->where($query->expr()->eq('steamid', $query->createNamedParameter($steamid)));
|
||||
|
||||
return $query->execute()->fetch();
|
||||
}
|
||||
|
||||
public function search($query) {
|
||||
$sql = 'SELECT user_id, players.name, count(demo_id) AS count, steamid,
|
||||
1-(players.name <-> ?) AS sim FROM players
|
||||
INNER JOIN users ON users.id = players.user_id
|
||||
WHERE players.name % ? OR players.name ~* ?
|
||||
GROUP BY players.name, user_id, steamid
|
||||
ORDER BY count DESC
|
||||
LIMIT 100';
|
||||
$result = $this->query($sql, [$query, $query, $query]);
|
||||
$players = $result->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
usort($players, function ($b, $a) {
|
||||
$countWeight = 1;
|
||||
$simWeight = 5;
|
||||
$diff = ($a['sim'] * $simWeight + $a['count'] * $countWeight) - ($b['sim'] * $simWeight + $b['count'] * $countWeight);
|
||||
if ($diff === 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ($diff < 0) ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
$result = [];
|
||||
foreach ($players as $player) {
|
||||
$id = $player['user_id'];
|
||||
if (!isset($result[$id])) {
|
||||
$result[$id] = [
|
||||
'id' => $id,
|
||||
'name' => $player['name'],
|
||||
'steamid' => $player['steamid']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$players = array_values($result);
|
||||
|
||||
return $players;
|
||||
}
|
||||
|
||||
public function byKey($key) {
|
||||
$query = $this->getQueryBuilder();
|
||||
$query->select(['id', 'steamid', 'name', 'avatar'])
|
||||
->from('users')
|
||||
->where($query->expr()->eq('token', $query->createNamedParameter($key)));
|
||||
|
||||
return $query->execute()->fetch();
|
||||
}
|
||||
}
|
||||
64
src/app.php
Normal file
64
src/app.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php namespace Demostf\API;
|
||||
|
||||
use Flight;
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
|
||||
$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';
|
||||
}
|
||||
$db = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);
|
||||
$host = getenv('BASE_HOST');
|
||||
|
||||
$demoProvider = new Providers\DemoProvider($db);
|
||||
|
||||
$factory = new \RandomLib\Factory;
|
||||
$generator = $factory->getMediumStrengthGenerator();
|
||||
$authProvider = new Providers\AuthProvider($db, $generator);
|
||||
$userProvider = new Providers\UserProvider($db, $generator);
|
||||
$demoController = new Controllers\DemoController($demoProvider);
|
||||
$authController = new Controllers\AuthController($userProvider, $authProvider, $host);
|
||||
$userController = new Controllers\UserController($userProvider);
|
||||
|
||||
Flight::route('/*', function () {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
return true;
|
||||
});
|
||||
|
||||
Flight::route('/auth/*', function () {
|
||||
session_start();
|
||||
return true;
|
||||
});
|
||||
|
||||
Flight::route('/', function () {
|
||||
echo 'hello world!';
|
||||
});
|
||||
|
||||
Flight::route('/maps', [$demoController, 'listMaps']);
|
||||
Flight::route('/stats', [$demoController, 'stats']);
|
||||
|
||||
Flight::route('/demos', [$demoController, 'listDemos']);
|
||||
Flight::route('/demos/@id', [$demoController, 'get']);
|
||||
Flight::route('/demos/@id/chat', [$demoController, 'chat']);
|
||||
Flight::route('/profiles/@steamid', [$demoController, 'listProfile']);
|
||||
Flight::route('/uploads/@steamid', [$demoController, 'listUploads']);
|
||||
|
||||
Flight::route('/users/search', [$userController, 'search']);
|
||||
Flight::route('/users/@steamid', [$userController, 'get']);
|
||||
|
||||
Flight::route('/auth/token', [$authController, 'token']);
|
||||
Flight::route('/auth/get/@token', [$authController, 'get']);
|
||||
Flight::route('/auth/handle/@token', [$authController, 'handle']);
|
||||
Flight::route('/auth/login/@token', [$authController, 'login']);
|
||||
Flight::route('/auth/logout/@token', [$authController, 'logout']);
|
||||
|
||||
Flight::start();
|
||||
6
src/init.php
Normal file
6
src/init.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
$autoloader = require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
if (!getenv('DB_TYPE')) {
|
||||
Dotenv::load(__DIR__ . '/../');
|
||||
}
|
||||
3
src/public/index.php
Normal file
3
src/public/index.php
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
require '../app.php';
|
||||
3
src/public/upload.php
Normal file
3
src/public/upload.php
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . '/../upload.php';
|
||||
26
src/upload.php
Normal file
26
src/upload.php
Normal 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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue