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

prepare for private demos

This commit is contained in:
Robin Appelman 2025-04-01 22:49:04 +02:00
commit 4f07dbbf34
13 changed files with 131 additions and 25 deletions

View file

@ -109,7 +109,7 @@
testScript = let testScript = let
initSql = pkgs.fetchurl { initSql = pkgs.fetchurl {
url = "https://github.com/demostf/db/raw/refs/heads/master/schema.sql"; url = "https://github.com/demostf/db/raw/refs/heads/master/schema.sql";
hash = "sha256-AwXN9mh9CRk6HWdvyUR+YdBkpmExNIDOIeDMz6XqjEQ="; hash = "sha256-tdMYDxlvpuQRxHglX46sCldxzsh1cDxkch2lGWnFH8E=";
}; };
in '' in ''
machine.succeed("mkdir /demos && chmod 0777 /demos"); machine.succeed("mkdir /demos && chmod 0777 /demos");

View file

@ -33,7 +33,7 @@
api = pkgs.demostf-api-dev; api = pkgs.demostf-api-dev;
initSql = pkgs.fetchurl { initSql = pkgs.fetchurl {
url = "https://github.com/demostf/db/raw/refs/heads/master/schema.sql"; url = "https://github.com/demostf/db/raw/refs/heads/master/schema.sql";
hash = "sha256-AwXN9mh9CRk6HWdvyUR+YdBkpmExNIDOIeDMz6XqjEQ="; hash = "sha256-tdMYDxlvpuQRxHglX46sCldxzsh1cDxkch2lGWnFH8E=";
}; };
in '' in ''
machine.succeed("mkdir /demos && chmod 0777 /demos"); machine.succeed("mkdir /demos && chmod 0777 /demos");

View file

@ -28,7 +28,8 @@ class UploadController extends BaseController {
return; return;
} }
$demoFile = $demo['tmp_name']; $demoFile = $demo['tmp_name'];
$private = $this->post('private', '0') === '1';
echo $this->uploadProvider->upload($key, $red, $blu, $name, $demoFile); echo $this->uploadProvider->upload($key, $red, $blu, $name, $demoFile, $private);
} }
} }

View file

@ -10,13 +10,15 @@ class Upload {
private string $blue; private string $blue;
private int $uploaderId; private int $uploaderId;
private string $hash; private string $hash;
private bool $private;
public function __construct(string $name, string $red, string $blue, int $uploaderId, string $hash) { public function __construct(string $name, string $red, string $blue, int $uploaderId, string $hash, bool $private) {
$this->name = $name; $this->name = $name;
$this->red = $red; $this->red = $red;
$this->blue = $blue; $this->blue = $blue;
$this->uploaderId = $uploaderId; $this->uploaderId = $uploaderId;
$this->hash = $hash; $this->hash = $hash;
$this->private = $private;
} }
public function getName(): string { public function getName(): string {
@ -38,4 +40,8 @@ class Upload {
public function getHash(): string { public function getHash(): string {
return $this->hash; return $this->hash;
} }
public function isPrivate(): bool {
return $this->private;
}
} }

View file

@ -30,6 +30,9 @@ class Demo implements JsonSerializable {
private string $hash; private string $hash;
private string $backend; private string $backend;
private string $path; private string $path;
private bool $showPrivateData = false;
private ?\DateTimeImmutable $privateUntil;
public function __construct( public function __construct(
int $id, int $id,
@ -48,7 +51,8 @@ class Demo implements JsonSerializable {
int $uploader, int $uploader,
string $hash, string $hash,
string $backend, string $backend,
string $path string $path,
?\DateTimeImmutable $privateUntil,
) { ) {
$this->id = $id; $this->id = $id;
$this->url = $url; $this->url = $url;
@ -69,6 +73,7 @@ class Demo implements JsonSerializable {
$this->path = $path; $this->path = $path;
$this->players = null; $this->players = null;
$this->uploaderUser = null; $this->uploaderUser = null;
$this->privateUntil = $privateUntil;
} }
public function getId(): int { public function getId(): int {
@ -154,11 +159,13 @@ class Demo implements JsonSerializable {
* 'hash': string, * 'hash': string,
* 'backend': string, * 'backend': string,
* 'path': string, * 'path': string,
* 'private_until': ?string,
* } $row * } $row
* *
* @return Demo * @return Demo
*/ */
public static function fromRow(array $row): self { public static function fromRow(array $row): self {
$private = $row['private_until'];
return new self( return new self(
(int) $row['id'], (int) $row['id'],
$row['url'], $row['url'],
@ -176,7 +183,8 @@ class Demo implements JsonSerializable {
(int) $row['uploader'], (int) $row['uploader'],
$row['hash'], $row['hash'],
$row['backend'], $row['backend'],
$row['path'] $row['path'],
$private ? new \DateTimeImmutable($private) : null,
); );
} }
@ -206,6 +214,10 @@ class Demo implements JsonSerializable {
return $this->path; return $this->path;
} }
public function getPrivateUntil(): ?\DateTimeImmutable {
return $this->privateUntil;
}
/** /**
* @return array{ * @return array{
* 'id': int, * 'id': int,
@ -226,12 +238,15 @@ class Demo implements JsonSerializable {
* 'backend': string, * 'backend': string,
* 'path': string, * 'path': string,
* 'players': ?DemoPlayer * 'players': ?DemoPlayer
* 'private_until': ?string,
* } * }
*/ */
public function jsonSerialize(): array { public function jsonSerialize(): array {
$now = new \DateTimeImmutable();
$isPublic = $this->showPrivateData || ($this->getPrivateUntil() ? $this->getPrivateUntil() <= $now : true);
$data = [ $data = [
'id' => $this->getId(), 'id' => $this->getId(),
'url' => $this->getUrl(), 'url' => $isPublic ? $this->getUrl() : '',
'name' => $this->getName(), 'name' => $this->getName(),
'server' => $this->getServer(), 'server' => $this->getServer(),
'duration' => $this->getDuration(), 'duration' => $this->getDuration(),
@ -245,8 +260,9 @@ class Demo implements JsonSerializable {
'playerCount' => $this->getPlayerCount(), 'playerCount' => $this->getPlayerCount(),
'uploader' => $this->uploaderUser ? $this->getUploaderUser()->jsonSerialize() : $this->getUploader(), 'uploader' => $this->uploaderUser ? $this->getUploaderUser()->jsonSerialize() : $this->getUploader(),
'hash' => $this->getHash(), 'hash' => $this->getHash(),
'backend' => $this->getBackend(), 'backend' => $isPublic ? $this->getBackend() : '',
'path' => $this->getPath(), 'path' => $isPublic ? $this->getPath() : '',
'private_until' => $this->getPrivateUntil()?->format(\DateTimeImmutable::ATOM),
]; ];
if (\is_array($this->players)) { if (\is_array($this->players)) {
$data['players'] = $this->getPlayers(); $data['players'] = $this->getPlayers();
@ -254,4 +270,8 @@ class Demo implements JsonSerializable {
return $data; return $data;
} }
function showPrivateData(bool $show): void {
$this->showPrivateData = $show;
}
} }

View file

@ -38,6 +38,9 @@ class DemoSaver {
public function saveDemo(ParsedDemo $demo, Header $header, StoredDemo $storedDemo, Upload $upload): int { public function saveDemo(ParsedDemo $demo, Header $header, StoredDemo $storedDemo, Upload $upload): int {
$this->connection->beginTransaction(); $this->connection->beginTransaction();
$now = new \DateTimeImmutable();
$week = \DateInterval::createFromDateString('7 days');
$privateUntil = $upload->isPrivate() ? $now->add($week) : null;
$demoId = $this->demoProvider->storeDemo(new Demo( $demoId = $this->demoProvider->storeDemo(new Demo(
0, 0,
@ -56,7 +59,8 @@ class DemoSaver {
$upload->getUploaderId(), $upload->getUploaderId(),
$upload->getHash(), $upload->getHash(),
$storedDemo->getBackend(), $storedDemo->getBackend(),
$storedDemo->getPath() $storedDemo->getPath(),
$privateUntil,
), $storedDemo->getBackend(), $storedDemo->getPath()); ), $storedDemo->getBackend(), $storedDemo->getPath());
$kills = []; $kills = [];

View file

@ -182,6 +182,7 @@ class DemoListProvider extends BaseProvider {
* 'hash': string, * 'hash': string,
* 'backend': string, * 'backend': string,
* 'path': string, * 'path': string,
* 'private_until': ?string,
* }[] $rows * }[] $rows
* *
* @return Demo[] * @return Demo[]

View file

@ -96,6 +96,7 @@ class DemoProvider extends BaseProvider {
'nick' => $query->createNamedParameter($demo->getNick()), 'nick' => $query->createNamedParameter($demo->getNick()),
'"playerCount"' => $query->createNamedParameter($demo->getPlayerCount(), PDO::PARAM_INT), '"playerCount"' => $query->createNamedParameter($demo->getPlayerCount(), PDO::PARAM_INT),
'hash' => $query->createNamedParameter($demo->getHash()), 'hash' => $query->createNamedParameter($demo->getHash()),
'private_until' => $query->createNamedParameter($demo->getPrivateUntil()?->format(DATE_ATOM)),
]) ])
->executeStatement(); ->executeStatement();

View file

@ -46,7 +46,7 @@ class UploadProvider extends BaseProvider {
$this->uploadKey = $uploadKey; $this->uploadKey = $uploadKey;
} }
public function upload(string $key, string $red, string $blu, string $name, string $demoFile): string { public function upload(string $key, string $red, string $blu, string $name, string $demoFile, bool $private): string {
$nameParts = explode('/', $name); $nameParts = explode('/', $name);
$name = array_pop($nameParts); $name = array_pop($nameParts);
$name = str_replace('%', '_', $name); $name = str_replace('%', '_', $name);
@ -86,7 +86,7 @@ class UploadProvider extends BaseProvider {
try { try {
$storedDemo = $this->store->store($demoFile, $hash . '_' . $name); $storedDemo = $this->store->store($demoFile, $hash . '_' . $name);
$upload = new Upload($name, $red, $blu, $user->getId(), $hash); $upload = new Upload($name, $red, $blu, $user->getId(), $hash, $private);
$id = $this->demoSaver->saveDemo($parsed, $header, $storedDemo, $upload); $id = $this->demoSaver->saveDemo($parsed, $header, $storedDemo, $upload);
} catch (\Exception $e) { } catch (\Exception $e) {

View file

@ -37,7 +37,8 @@ class DemoSaverTest extends TestCase {
'DER', 'DER',
'ULB', 'ULB',
$userProvider->getUserId('2345678', 'user2'), $userProvider->getUserId('2345678', 'user2'),
'securehash' 'securehash',
false,
); );
$header = new Header( $header = new Header(

View file

@ -51,7 +51,8 @@ class DemoListProviderTest extends TestCase {
$uploaderId, $uploaderId,
'hash', 'hash',
'backend', 'backend',
'path' 'path',
null,
); );
} }

View file

@ -57,7 +57,8 @@ class DemoProviderTest extends TestCase {
$uploader->getId(), $uploader->getId(),
'hash', 'hash',
'dummy', 'dummy',
'path' 'path',
null,
); );
$demo->setUploaderUser($uploader); $demo->setUploaderUser($uploader);
@ -114,8 +115,7 @@ class DemoProviderTest extends TestCase {
'hash', 'hash',
'backend', 'backend',
'path', 'path',
'dummy', null,
'path'
); );
$id = $this->provider->storeDemo($demo, 'dummy', 'path'); $id = $this->provider->storeDemo($demo, 'dummy', 'path');
@ -179,7 +179,8 @@ class DemoProviderTest extends TestCase {
$uploader->getId(), $uploader->getId(),
'hash', 'hash',
'dummy', 'dummy',
'path' 'path',
null,
); );
$id = $this->provider->storeDemo($demo, 'dummy', 'path'); $id = $this->provider->storeDemo($demo, 'dummy', 'path');
@ -195,4 +196,61 @@ class DemoProviderTest extends TestCase {
$storedDemo2 = $this->provider->get($id2); $storedDemo2 = $this->provider->get($id2);
$this->assertEquals('http://example.com', $storedDemo2->getUrl()); $this->assertEquals('http://example.com', $storedDemo2->getUrl());
} }
public function privateDateProvider() {
return [
['2 days', false],
['-2 days', true],
];
}
/**
* @dataProvider privateDateProvider
*/
public function testPrivateDemo(string $until, bool $visible) {
$now = new \DateTimeImmutable();
$until = \DateInterval::createFromDateString($until);
$uploaderSteamId = $this->getSteamId('12345', 'test');
$this->userProvider->store($uploaderSteamId, 'test');
$uploader = $this->userProvider->get($uploaderSteamId->getSteamId64());
$demo = new Demo(
0,
'http://example.com',
'name',
'server',
12,
'nick',
'map',
new \DateTime(),
'RED',
'BLUE',
1,
2,
18,
$uploader->getId(),
'hash',
'dummy',
'path',
$now->add($until),
);
$id = $this->provider->storeDemo($demo, 'dummy', 'path');
$this->provider->setDemoUrl($id, 'foobackend', 'http://foo.example.com', 'bar');
$storedDemo = $this->provider->get($id);
$json = $storedDemo->jsonSerialize();
if ($visible) {
$this->assertEquals('http://foo.example.com', $json['url']);
} else {
$this->assertEquals('', $json['url']);
}
$storedDemo->showPrivateData(true);
$json = $storedDemo->jsonSerialize();
$this->assertEquals('http://foo.example.com', $json['url']);
}
} }

View file

@ -214,7 +214,7 @@ class UploadProviderTest extends TestCase {
public function testUploadInvalidKey() { public function testUploadInvalidKey() {
$this->expectException(InvalidKeyException::class); $this->expectException(InvalidKeyException::class);
$this->uploadProvider->upload('dummy', 'RED', 'BLU', 'dummy', 'dummy'); $this->uploadProvider->upload('dummy', 'RED', 'BLU', 'dummy', 'dummy', false);
} }
public function testUploadNonDemo() { public function testUploadNonDemo() {
@ -226,7 +226,7 @@ class UploadProviderTest extends TestCase {
$steamId = $this->getSteamId('123', 'a'); $steamId = $this->getSteamId('123', 'a');
$token = $this->userProvider->store($steamId, 'a'); $token = $this->userProvider->store($steamId, 'a');
$this->uploadProvider->upload($token, 'RED', 'BLU', 'dummy', $this->tmpDir . '/foo.dem'); $this->uploadProvider->upload($token, 'RED', 'BLU', 'dummy', $this->tmpDir . '/foo.dem', false);
} }
public function testUploadExisting() { public function testUploadExisting() {
@ -253,7 +253,8 @@ class UploadProviderTest extends TestCase {
1, 1,
$hash, $hash,
'b', 'b',
'p' 'p',
null,
), ),
'test', 'test',
'test' 'test'
@ -264,7 +265,7 @@ class UploadProviderTest extends TestCase {
$this->assertEquals( $this->assertEquals(
'STV available at: http://example.com/' . $id, 'STV available at: http://example.com/' . $id,
$this->uploadProvider->upload($token, 'RED', 'BLU', 'dummy', $this->tmpDir . '/foo.dem') $this->uploadProvider->upload($token, 'RED', 'BLU', 'dummy', $this->tmpDir . '/foo.dem', false)
); );
} }
@ -298,14 +299,15 @@ class UploadProviderTest extends TestCase {
public function uploadProvider(): array { public function uploadProvider(): array {
return [ return [
[__DIR__ . '/../data/product.dem', __DIR__ . '/../data/product-raw.json', 'koth_product_rc8', 0, 3], [__DIR__ . '/../data/product.dem', __DIR__ . '/../data/product-raw.json', 'koth_product_rc8', 0, 3, false],
[__DIR__ . '/../data/product.dem', __DIR__ . '/../data/product-raw.json', 'koth_product_rc8', 0, 3, true],
]; ];
} }
/** /**
* @dataProvider uploadProvider * @dataProvider uploadProvider
*/ */
public function testUpload(string $demo, string $parsed, string $map, int $blue, int $red) { public function testUpload(string $demo, string $parsed, string $map, int $blue, int $red, bool $private) {
copy($demo, $this->tmpDir . '/foo.dem'); copy($demo, $this->tmpDir . '/foo.dem');
copy($parsed, $this->tmpDir . '/foo-raw.json'); copy($parsed, $this->tmpDir . '/foo-raw.json');
@ -314,7 +316,7 @@ class UploadProviderTest extends TestCase {
$this->preloadNames(); $this->preloadNames();
$result = $this->uploadProvider->upload($token, 'RED', 'BLU', 'foodemo', $this->tmpDir . '/foo.dem'); $result = $this->uploadProvider->upload($token, 'RED', 'BLU', 'foodemo', $this->tmpDir . '/foo.dem', $private);
$this->assertStringStartsWith('STV available at: http://example.com/', $result); $this->assertStringStartsWith('STV available at: http://example.com/', $result);
$demoId = (int) substr($result, \strlen('STV available at: http://example.com/')); $demoId = (int) substr($result, \strlen('STV available at: http://example.com/'));
@ -326,5 +328,16 @@ class UploadProviderTest extends TestCase {
$this->assertEquals($map, $demo->getMap()); $this->assertEquals($map, $demo->getMap());
$this->assertEquals($blue, $demo->getBlueScore()); $this->assertEquals($blue, $demo->getBlueScore());
$this->assertEquals($red, $demo->getRedScore()); $this->assertEquals($red, $demo->getRedScore());
$json = $demo->jsonSerialize();
if ($private) {
$this->assertEquals('', $json['url']);
$this->assertEquals('', $json['backend']);
$this->assertEquals('', $json['path']);
} else {
$this->assertEquals($demo->getUrl(), $json['url']);
$this->assertEquals($demo->getBackend(), $json['backend']);
$this->assertEquals($demo->getPath(), $json['path']);
}
} }
} }