view logging works

This commit is contained in:
overflowerror 2021-01-07 21:12:56 +01:00
parent d6197a692a
commit 6e5ac1b55c
8 changed files with 385 additions and 8 deletions

View file

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210107200117 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE "view" (id BLOB NOT NULL, video_id BLOB NOT NULL, link_id BLOB NOT NULL, timestamp DATETIME NOT NULL --(DC2Type:datetime_immutable)
, validated DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_FEFDAB8E29C1004E ON "view" (video_id)');
$this->addSql('CREATE INDEX IDX_FEFDAB8EADA40271 ON "view" (link_id)');
$this->addSql('DROP INDEX UNIQ_8D93D6495E237E06');
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, password, name, roles FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, password VARCHAR(255) NOT NULL COLLATE BINARY, name VARCHAR(180) NOT NULL COLLATE BINARY, roles CLOB NOT NULL COLLATE BINARY --(DC2Type:json)
, PRIMARY KEY(id))');
$this->addSql('INSERT INTO user (id, password, name, roles) SELECT id, password, name, roles FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D6495E237E06 ON user (name)');
$this->addSql('DROP INDEX IDX_7CC7DA2C16678C77');
$this->addSql('CREATE TEMPORARY TABLE __temp__video AS SELECT id, uploader_id, uploaded, name, description, tags, state, length, transcoding_progress FROM video');
$this->addSql('DROP TABLE video');
$this->addSql('CREATE TABLE video (id BLOB NOT NULL, uploader_id BLOB NOT NULL, uploaded DATETIME NOT NULL --(DC2Type:datetime_immutable)
, name VARCHAR(255) NOT NULL COLLATE BINARY, description VARCHAR(1024) NOT NULL COLLATE BINARY, tags CLOB NOT NULL COLLATE BINARY --(DC2Type:array)
, state INTEGER NOT NULL, length DOUBLE PRECISION DEFAULT NULL, transcoding_progress INTEGER NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_7CC7DA2C16678C77 FOREIGN KEY (uploader_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO video (id, uploader_id, uploaded, name, description, tags, state, length, transcoding_progress) SELECT id, uploader_id, uploaded, name, description, tags, state, length, transcoding_progress FROM __temp__video');
$this->addSql('DROP TABLE __temp__video');
$this->addSql('CREATE INDEX IDX_7CC7DA2C16678C77 ON video (uploader_id)');
$this->addSql('DROP INDEX IDX_313BC42D61220EA6');
$this->addSql('DROP INDEX IDX_313BC42D29C1004E');
$this->addSql('CREATE TEMPORARY TABLE __temp__video_link AS SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM video_link');
$this->addSql('DROP TABLE video_link');
$this->addSql('CREATE TABLE video_link (id BLOB NOT NULL, video_id BLOB NOT NULL, creator_id BLOB NOT NULL, created DATETIME NOT NULL --(DC2Type:datetime_immutable)
, max_views INTEGER DEFAULT NULL, viewable_for INTEGER DEFAULT NULL, viewable_until DATETIME DEFAULT NULL, comment VARCHAR(1024) DEFAULT NULL COLLATE BINARY, PRIMARY KEY(id), CONSTRAINT FK_313BC42D29C1004E FOREIGN KEY (video_id) REFERENCES video (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_313BC42D61220EA6 FOREIGN KEY (creator_id) REFERENCES user (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
$this->addSql('INSERT INTO video_link (id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment) SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM __temp__video_link');
$this->addSql('DROP TABLE __temp__video_link');
$this->addSql('CREATE INDEX IDX_313BC42D61220EA6 ON video_link (creator_id)');
$this->addSql('CREATE INDEX IDX_313BC42D29C1004E ON video_link (video_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE "view"');
$this->addSql('DROP INDEX UNIQ_8D93D6495E237E06');
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, name, roles, password FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, name VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO user (id, name, roles, password) SELECT id, name, roles, password FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D6495E237E06 ON user (name)');
$this->addSql('DROP INDEX IDX_7CC7DA2C16678C77');
$this->addSql('CREATE TEMPORARY TABLE __temp__video AS SELECT id, uploader_id, uploaded, name, description, tags, state, length, transcoding_progress FROM video');
$this->addSql('DROP TABLE video');
$this->addSql('CREATE TABLE video (id BLOB NOT NULL, uploaded DATETIME NOT NULL --(DC2Type:datetime_immutable)
, name VARCHAR(255) NOT NULL, description VARCHAR(1024) NOT NULL, tags CLOB NOT NULL --(DC2Type:array)
, state INTEGER NOT NULL, length DOUBLE PRECISION DEFAULT NULL, uploader_id BLOB NOT NULL, transcoding_progress INTEGER DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO video (id, uploader_id, uploaded, name, description, tags, state, length, transcoding_progress) SELECT id, uploader_id, uploaded, name, description, tags, state, length, transcoding_progress FROM __temp__video');
$this->addSql('DROP TABLE __temp__video');
$this->addSql('CREATE INDEX IDX_7CC7DA2C16678C77 ON video (uploader_id)');
$this->addSql('DROP INDEX IDX_313BC42D29C1004E');
$this->addSql('DROP INDEX IDX_313BC42D61220EA6');
$this->addSql('CREATE TEMPORARY TABLE __temp__video_link AS SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM video_link');
$this->addSql('DROP TABLE video_link');
$this->addSql('CREATE TABLE video_link (id BLOB NOT NULL, created DATETIME NOT NULL --(DC2Type:datetime_immutable)
, max_views INTEGER DEFAULT NULL, viewable_for INTEGER DEFAULT NULL, viewable_until DATETIME DEFAULT NULL, comment VARCHAR(1024) DEFAULT NULL, video_id BLOB NOT NULL, creator_id BLOB NOT NULL, PRIMARY KEY(id))');
$this->addSql('INSERT INTO video_link (id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment) SELECT id, video_id, creator_id, created, max_views, viewable_for, viewable_until, comment FROM __temp__video_link');
$this->addSql('DROP TABLE __temp__video_link');
$this->addSql('CREATE INDEX IDX_313BC42D29C1004E ON video_link (video_id)');
$this->addSql('CREATE INDEX IDX_313BC42D61220EA6 ON video_link (creator_id)');
}
}

View file

@ -169,6 +169,7 @@ class DashboardController extends AbstractController
try { try {
$videoId = $this->uuidMapper->fromString($videoId); $videoId = $this->uuidMapper->fromString($videoId);
} catch (ConversionException $e) { } catch (ConversionException $e) {
return new Response($videoId);
return $this->redirectToRoute("app_links"); return $this->redirectToRoute("app_links");
} }

View file

@ -4,7 +4,9 @@ namespace App\Controller;
use App\Entity\User; use App\Entity\User;
use App\Entity\Video; use App\Entity\Video;
use App\Entity\VideoLink;
use App\Mapper\CustomUuidMapper; use App\Mapper\CustomUuidMapper;
use App\Service\LoggingService;
use App\Service\UserService; use App\Service\UserService;
use App\Service\VideoLinkService; use App\Service\VideoLinkService;
use App\Service\VideoService; use App\Service\VideoService;
@ -13,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
class WatchController extends AbstractController class WatchController extends AbstractController
@ -34,28 +37,26 @@ class WatchController extends AbstractController
private $userService; private $userService;
private $videoService; private $videoService;
private $videoLinkService; private $videoLinkService;
private $loggingService;
private $uuidMapper; private $uuidMapper;
public function __construct( public function __construct(
UserService $userService, UserService $userService,
VideoService $videoService, VideoService $videoService,
VideoLinkService $videoLinkService, VideoLinkService $videoLinkService,
LoggingService $loggingService,
CustomUuidMapper $uuidMapper CustomUuidMapper $uuidMapper
) )
{ {
$this->userService = $userService; $this->userService = $userService;
$this->videoService = $videoService; $this->videoService = $videoService;
$this->videoLinkService = $videoLinkService; $this->videoLinkService = $videoLinkService;
$this->loggingService = $loggingService;
$this->uuidMapper = $uuidMapper; $this->uuidMapper = $uuidMapper;
} }
private function isAllowed(?Video $video, ?User $user, $linkId): int private function isAllowed(?Video $video, ?User $user, VideoLink $link): int
{ {
if ($video->getUploader() == $user) {
return self::IS_OWNER;
}
$link = $this->videoLinkService->get($this->uuidMapper->fromString($linkId));
if (!$link) { if (!$link) {
return self::NOT_ALLOWED; return self::NOT_ALLOWED;
} }
@ -74,8 +75,18 @@ class WatchController extends AbstractController
try { try {
$video = $this->videoService->get($this->uuidMapper->fromString($videoId)); $video = $this->videoService->get($this->uuidMapper->fromString($videoId));
$user = $this->userService->getLoggedInUser(); $user = $this->userService->getLoggedInUser();
$link = null;
$allowed = $this->isAllowed($video, $user, $linkId); $allowed = self::NOT_ALLOWED;
if ($video->getUploader() == $user) {
$allowed = self::IS_OWNER;
}
if (!$allowed) {
$link = $this->videoLinkService->get($this->uuidMapper->fromString($linkId));
$allowed = $this->isAllowed($video, $user, $link);
}
} catch (ConversionException $e) { } catch (ConversionException $e) {
throw new AccessDeniedHttpException(); throw new AccessDeniedHttpException();
} }
@ -86,6 +97,7 @@ class WatchController extends AbstractController
return [ return [
"video" => $video, "video" => $video,
"link" => $link,
"user" => $user, "user" => $user,
"isOwner" => $allowed == self::IS_OWNER "isOwner" => $allowed == self::IS_OWNER
]; ];
@ -151,6 +163,30 @@ class WatchController extends AbstractController
return $response; return $response;
} }
/**
* @Route("/{linkId}/{videoId}/v/{viewId}", methods={"POST"}, name="app_watch_view")
*/
public function viewCounter($videoId, $linkId, $viewId): Response
{
$data = $this->checkRequestData($videoId, $linkId);
if ($data["isOwner"]) {
throw new BadRequestHttpException();
}
try {
$viewId = $this->uuidMapper->fromString($viewId);
} catch (ConversionException $e) {
throw new BadRequestHttpException();
}
if (!$this->loggingService->validateView($data["video"], $data["link"], $viewId)) {
throw new BadRequestHttpException();
}
return new Response("ok");
}
/** /**
* @Route("/{linkId}/{videoId}/", name="app_watch_page") * @Route("/{linkId}/{videoId}/", name="app_watch_page")
*/ */
@ -158,7 +194,15 @@ class WatchController extends AbstractController
{ {
$data = $this->checkRequestData($videoId, $linkId); $data = $this->checkRequestData($videoId, $linkId);
$viewToken = null;
if (!$data["isOwner"]) {
$viewToken = $this->uuidMapper->toString($this->loggingService->createView($data["video"], $data["link"]));
}
$data["video"]->setCustomId($videoId);
return $this->render("watch/watch.html.twig", [ return $this->render("watch/watch.html.twig", [
"viewToken" => $viewToken,
"thumbnail" => $this->generateUrl("app_watch_thumbnail", [ "thumbnail" => $this->generateUrl("app_watch_thumbnail", [
"linkId" => $linkId, "linkId" => $linkId,
"videoId" => $videoId "videoId" => $videoId
@ -167,6 +211,7 @@ class WatchController extends AbstractController
"linkId" => $linkId, "linkId" => $linkId,
"videoId" => $videoId "videoId" => $videoId
]), ]),
"linkId" => $linkId,
"video" => $data["video"], "video" => $data["video"],
]); ]);
} }

99
src/Entity/View.php Normal file
View file

@ -0,0 +1,99 @@
<?php
namespace App\Entity;
use App\Repository\ViewRepository;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Doctrine\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="`view`")
*/
class View
{
/**
* @ORM\Id
* @ORM\Column(type="uuid", unique=true)
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class=UuidGenerator::class)
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=Video::class)
* @ORM\JoinColumn(nullable=false)
*/
private $video;
/**
* @ORM\ManyToOne(targetEntity=VideoLink::class)
* @ORM\JoinColumn(nullable=false)
*/
private $link;
/**
* @ORM\Column(type="datetime_immutable")
*/
private $timestamp;
/**
* @ORM\Column(type="datetime_immutable", nullable=true)
*/
private $validated;
public function getId(): ?UuidInterface
{
return $this->id;
}
public function getVideo(): ?Video
{
return $this->video;
}
public function setVideo(?Video $video): self
{
$this->video = $video;
return $this;
}
public function getLink(): ?VideoLink
{
return $this->link;
}
public function setLink(?VideoLink $link): self
{
$this->link = $link;
return $this;
}
public function getTimestamp(): ?DateTimeImmutable
{
return $this->timestamp;
}
public function setTimestamp(): self
{
$this->timestamp = new DateTimeImmutable();
return $this;
}
public function getValidated(): ?DateTimeImmutable
{
return $this->validated;
}
public function setValidated(): self
{
$this->validated = new DateTimeImmutable();
return $this;
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace App\Repository;
use App\Entity\View;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method View|null find($id, $lockMode = null, $lockVersion = null)
* @method View|null findOneBy(array $criteria, array $orderBy = null)
* @method View[] findAll()
* @method View[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ViewRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, View::class);
}
// /**
// * @return View[] Returns an array of View objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('v')
->andWhere('v.exampleField = :val')
->setParameter('val', $value)
->orderBy('v.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?View
{
return $this->createQueryBuilder('v')
->andWhere('v.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
public function save(View $view)
{
$this->_em->persist($view);
$this->_em->flush();
}
public function update()
{
$this->_em->flush();
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace App\Service;
use App\Entity\Video;
use App\Entity\VideoLink;
use App\Entity\View;
use App\Repository\ViewRepository;
use Ramsey\Uuid\UuidInterface;
class LoggingService
{
private $viewRepository;
public function __construct(ViewRepository $viewRepository)
{
$this->viewRepository = $viewRepository;
}
public function createView(Video $video, VideoLink $link): UuidInterface
{
$view = new View();
$view->setVideo($video);
$view->setLink($link);
$view->setTimestamp();
$this->viewRepository->save($view);
return $view->getId();
}
public function validateView(Video $video, VideoLink $link, UuidInterface $viewId): bool
{
$view = $this->viewRepository->findOneById($viewId);
if (!$view) {
return false;
}
if ($view->getVideo() != $video) {
return false;
}
if ($view->getLink() != $link) {
return false;
}
if ($view->getValidated()) {
return false;
}
$view->setValidated();
$this->viewRepository->update();
return true;
}
}

View file

@ -65,7 +65,7 @@
</div> </div>
<div class="info"> <div class="info">
<div class="link"> <div class="link">
<a href="{{ path("app_new_link") }}?video={{ video.customId }}"> <a href="{{ path("app_new_link") }}?video={{ video.customId | url_encode }}">
<i class="fas fa-link"></i> <i class="fas fa-link"></i>
</a> </a>
</div> </div>

View file

@ -18,6 +18,31 @@
// enable quality selector // enable quality selector
videojs('video').hlsQualitySelector(); videojs('video').hlsQualitySelector();
</script> </script>
<script>
{% if viewToken %}
(function () {
let player = videojs('video');
//let target = {{ video.length > 120 ? 60 : video.length * 0.5 }};
let target = 10;
let current = 0;
setInterval(function () {
console.log(!player.paused() + ", " + current + ", " + target);
if (!player.paused()) {
current++;
if (current === target) {
ajaxPost("{{ path("app_watch_view", {
linkId: linkId,
videoId: video.customId,
viewId: viewToken
}) }}", null, function () {
});
}
}
}, 1000);
})()
{% endif %}
</script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}