Andrew 3 hari lalu
induk
melakukan
55c503da7c

+ 5 - 0
.idea/codeStyles/codeStyleConfig.xml

@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+  </state>
+</component>

+ 12 - 0
.idea/dataSources.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="@localhost" uuid="6c4c5d86-94e7-4a4a-b455-d0dfc867049d">
+      <driver-ref>mariadb</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
+      <jdbc-url>jdbc:mariadb://localhost:3306</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>

+ 6 - 0
.idea/sqldialects.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="SqlDialectMappings">
+    <file url="file://$PROJECT_DIR$/app/Controllers/VideoController.php" dialect="GenericSQL" />
+  </component>
+</project>

+ 3 - 1
app/Controllers/AccountController.php

@@ -15,7 +15,7 @@ use app\Types\DatabaseObjects\Link;
 use app\Types\DatabaseObjects\Session;
 use app\Types\LinkEnum;
 use Exception;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 use Mimey\MimeTypes;
 use Pecee\SimpleRouter\SimpleRouter;
 
@@ -78,6 +78,7 @@ class AccountController implements IRouteController
             "pictureHash" => $account->picture_hash,
             "followers" => $followers,
             "following" => $following,
+            "myself" => $id == get_token_id(request()),
             "links" => $links
         ]);
     }
@@ -97,6 +98,7 @@ class AccountController implements IRouteController
         $data = Hajeebtok::$Database->Query("SELECT * FROM videos WHERE author_id = :author_id", ["author_id" => $account->id]);
         if (empty($data)) throw new VideoNotFoundException(0, 404);
 
+        // todo: make it return in feed format!!!!
         CORSHelper();
         return api_json($data);
     }

+ 1 - 1
app/Controllers/ErrorController.php

@@ -97,7 +97,7 @@ class ErrorController implements IRouteController
         CORSHelper();
         return api_json([
             "error" => $code,
-            "message" => self::$codeLookup[$code],
+            "message" => self::$codeLookup[$code] ?? 500,
         ]);
     }
 

+ 197 - 121
app/Controllers/VideoController.php

@@ -4,9 +4,15 @@ namespace app\Controllers;
 
 use app\Exceptions\AccountNotFoundException;
 use app\Exceptions\CommentNotFoundException;
+use app\Exceptions\InvalidRequestException;
+use app\Exceptions\InvalidVideoException;
+use app\Exceptions\UnauthenticatedException;
 use app\Exceptions\VideoNotFoundException;
 use app\Hajeebtok;
 use app\Types\DatabaseObjects\View;
+use Cassandra\Exception\UnauthorizedException;
+use Exception;
+use app\Exceptions\SecurityFaultException;
 use Pecee\SimpleRouter\SimpleRouter;
 use Mimey\MimeTypes;
 use app\Interfaces\IRouteController;
@@ -19,35 +25,35 @@ class VideoController implements IRouteController
 {
 	public static function getVideo(string $id): string
 	{
-        $signed_in = signed_in(request());
-
-        $video_information = new Video($id);
-        $video_information->Load();
-        $mime_types = new MimeTypes();
-
-        if($signed_in) { // Signed in
-            $video_path = APP_ROOT . "/usercontent/videos/$id/video.mp4";
-        } else { // not signed in
-            $rand = rand(1, 48);
-            $video_path = APP_ROOT . "/usercontent/fake_videos/$rand/video.mp4";
-        }
-
-        CORSHelper();
-        if (file_exists($video_path)) {
-            $video_contents = file_get_contents($video_path);
-            $video_size = filesize($video_path);
-
-            $mime = $mime_types->getMimeType(pathinfo($video_path, PATHINFO_EXTENSION));
-
-            $response = response();
-            $response->header("Content-Type: $mime");
-            $response->header("Content-Length: $video_size");
-            $response->header("Cache-Control: max-age=86400, public");
-
-            return $video_contents;
-        } else {
-            throw new VideoNotFoundException($id, 404);
-        }
+		$signed_in = signed_in(request());
+
+		$video_information = new Video($id);
+		$video_information->Load();
+		$mime_types = new MimeTypes();
+
+		if ($signed_in) { // Signed in
+			$video_path = APP_ROOT . "/usercontent/videos/$id/video.mp4";
+		} else { // not signed in
+			$rand = rand(1, 48);
+			$video_path = APP_ROOT . "/usercontent/fake_videos/$rand/video.mp4";
+		}
+
+		CORSHelper();
+		if (file_exists($video_path)) {
+			$video_contents = file_get_contents($video_path);
+			$video_size = filesize($video_path);
+
+			$mime = $mime_types->getMimeType(pathinfo($video_path, PATHINFO_EXTENSION));
+
+			$response = response();
+			$response->header("Content-Type: $mime");
+			$response->header("Content-Length: $video_size");
+			$response->header("Cache-Control: max-age=86400, public");
+
+			return $video_contents;
+		} else {
+			throw new VideoNotFoundException($id, 404);
+		}
 	}
 
 	public static function getInfo(string $id): string
@@ -57,9 +63,9 @@ class VideoController implements IRouteController
 		$author_information = new Account($video_information->author_id);
 		$author_information->Load();
 
-        CORSHelper();
+		CORSHelper();
 		return api_json([
-            "id" => $video_information->id,
+			"id" => $video_information->id,
 			"title" => $video_information->title,
 			"description" => $video_information->description,
 			"likes" => $video_information->likes,
@@ -74,103 +80,173 @@ class VideoController implements IRouteController
 		]);
 	}
 
-    public static function search(): string {
-        $query = input("query");
-        $video = new Video(title: $query);
-        $videos = $video->LoadMany();
-
-        // idk if this is the greatest thing to do but jetbrains phpfart ai recommended it and it looks fine to me /shurg
-        $accounts = Hajeebtok::$Database->Query("SELECT * FROM accounts WHERE id IN (SELECT author_id FROM videos WHERE title LIKE :title)", ["title" => "%$query%"]);
-        if(empty($accounts)) throw new AccountNotFoundException(0, 404);
-
-        $data = [];
-        foreach($videos as $vid) {
-            $data[] = [
-                "id" => $vid["id"],
-                "title" => $vid["title"],
-                "description" => $vid["description"],
-                "likes" => $vid["likes"],
-                "dislikes" => $vid["dislikes"],
-                "comments" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM comments WHERE video_id = :id", ["id" => $vid["id"]]),
-                "shares" =>  Hajeebtok::$Database->Single("SELECT COUNT(*) FROM messages INNER JOIN videos ON messages.video_id = videos.id WHERE videos.id = :id", ["id" => $vid["id"]]),
-                "author" => [
-                    "id" => $vid["author_id"],
-                    "pictureHash" => $accounts[$vid["author_id"] - 1]["picture_hash"], // kinda scuffed fix
-                    "username" => $accounts[$vid["author_id"] - 1]["username"]
-                ],
-            ];
-        }
-
-        CORSHelper();
-        return api_json($data);
-    }
-
-    public static function getThumbnail(string $id): string
-    {
-        $signed_in = signed_in(request());
-        $video_information = new Video($id);
-        $video_information->Load();
-
-        if($signed_in) { // Signed in
-            $seconds = 2;
-            $video_path = APP_ROOT . "/usercontent/videos/$id/video.mp4";
-            $frame_path = APP_ROOT . "/usercontent/videos/$id/thumbnail.png";
-        } else { // not signed in
-            $rand = rand(1, 47);
-            $seconds = 6;
-            $video_path = APP_ROOT . "/usercontent/fake_videos/$rand/video.mp4";
-            $frame_path = APP_ROOT . "/usercontent/fake_videos/$rand/thumbnail.png";
-        }
-
-        $mime_types = new MimeTypes();
-        if(!file_exists($video_path)) throw new VideoNotFoundException($id, 404);
-        if(!file_exists($frame_path)) {
-            Logger::Debug("Generating thumbnail for video $id");
-
-            $ffmpeg = FFMpeg\FFMpeg::create();
-            $video = $ffmpeg->open($video_path);
-            $frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds($seconds));
-            $frame->save($frame_path);
-        }
-
-        CORSHelper();
-        $mime = $mime_types->getMimeType(pathinfo($frame_path, PATHINFO_EXTENSION));
-        $response = response();
-        $response->header("Content-Type: $mime");
-        $response->header("Cache-Control: max-age=86400, public");
-
-        return file_get_contents($frame_path);
-    }
-
-    public static function getFeed(): string
-    {
-        $signed_in = signed_in(request());
-        if(!$signed_in) throw new AccountNotFoundException(0, 404);
-        $id = get_token_id(request());
-        $view = new View(account_id: $id);
-        $view->LoadMany();
-
-
-
-        CORSHelper();
-        return api_json([]);
-    }
+	public static function search(): string
+	{
+		$query = input("query");
+		$video = new Video(title: $query);
+		$videos = $video->LoadMany();
+
+		// idk if this is the greatest thing to do but jetbrains phpfart ai recommended it and it looks fine to me /shurg
+		$accounts = Hajeebtok::$Database->Query("SELECT * FROM accounts WHERE id IN (SELECT author_id FROM videos WHERE title LIKE :title)", ["title" => "%$query%"]);
+		if (empty($accounts)) throw new AccountNotFoundException(0, 404);
+
+		$data = [];
+		foreach ($videos as $vid) {
+			$data[] = [
+				"id" => $vid["id"],
+				"title" => $vid["title"],
+				"description" => $vid["description"],
+				"likes" => $vid["likes"],
+				"dislikes" => $vid["dislikes"],
+				"comments" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM comments WHERE video_id = :id", ["id" => $vid["id"]]),
+				"shares" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM messages INNER JOIN videos ON messages.video_id = videos.id WHERE videos.id = :id", ["id" => $vid["id"]]),
+				"author" => [
+					"id" => $vid["author_id"],
+					"pictureHash" => $accounts[$vid["author_id"] - 1]["picture_hash"], // kinda scuffed fix
+					"username" => $accounts[$vid["author_id"] - 1]["username"]
+				],
+			];
+		}
+
+		CORSHelper();
+		return api_json($data);
+	}
+
+	public static function getThumbnail(string $id): string
+	{
+		$signed_in = signed_in(request());
+		$video_information = new Video($id);
+		$video_information->Load();
+
+		if ($signed_in) { // Signed in
+			$seconds = 2;
+			$video_path = APP_ROOT . "/usercontent/videos/$id/video.mp4";
+			$frame_path = APP_ROOT . "/usercontent/videos/$id/thumbnail.png";
+		} else { // not signed in
+			$rand = rand(1, 47);
+			$seconds = 6;
+			$video_path = APP_ROOT . "/usercontent/fake_videos/$rand/video.mp4";
+			$frame_path = APP_ROOT . "/usercontent/fake_videos/$rand/thumbnail.png";
+		}
+
+		$mime_types = new MimeTypes();
+		if (!file_exists($video_path)) throw new VideoNotFoundException($id, 404);
+		if (!file_exists($frame_path)) {
+			Logger::Debug("Generating thumbnail for video $id");
+
+			$ffmpeg = FFMpeg\FFMpeg::create();
+			$video = $ffmpeg->open($video_path);
+			$frame = $video->frame(FFMpeg\Coordinate\TimeCode::fromSeconds($seconds));
+			$frame->save($frame_path);
+		}
+
+		CORSHelper();
+		$mime = $mime_types->getMimeType(pathinfo($frame_path, PATHINFO_EXTENSION));
+		$response = response();
+		$response->header("Content-Type: $mime");
+		$response->header("Cache-Control: max-age=86400, public");
+
+		return file_get_contents($frame_path);
+	}
+
+	public static function getFeed(): string
+	{
+		// todo: implement offset & limit
+		$signed_in = signed_in(request());
+		if (!$signed_in) throw new AccountNotFoundException(0, 404);
+		$id = get_token_id(request());
+
+		$feed = [];
+
+		$videos = Hajeebtok::$Database->Query("SELECT * FROM videos WHERE id NOT IN (SELECT video_id FROM views WHERE account_id = :account_id AND video_id > :offset) ORDER BY id DESC", ["account_id" => $id,  "offset" => $offset]);
+		foreach ($videos as $video) {
+			$account = new Account($video["author_id"]);
+			$account->Load();
+
+			$feed[] = [
+				"id" => $video["id"],
+				"title" => $video["title"],
+				"description" => $video["description"],
+				"likes" => $video["likes"],
+				"dislikes" => $video["dislikes"],
+				"comments" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM comments WHERE video_id = :id", ["id" => $video["id"]]),
+				"shares" => Hajeebtok::$Database->Single("SELECT COUNT(*) FROM messages INNER JOIN videos ON messages.video_id = videos.id WHERE videos.id = :id", ["id" => $video["id"]]),
+				"author" => [
+					"id" => $video["author_id"],
+					"pictureHash" => $account->picture_hash,
+					"username" => $account->username,
+				],
+			];
+		}
+
+		CORSHelper();
+		return api_json($feed);
+	}
+
+	public static function uploadVideo(): string
+	{
+		if (!signed_in(request())) throw new UnauthenticatedException(0, 401);
+
+		$video_file = input()->file("video");
+		$title = input("title");
+		$description = input("description");
+		$author_id = get_token_id(request());
+		if (empty($video_file) || empty($title) || empty($author_id)) throw new InvalidRequestException(0, 400);
+
+		if (!file_exists($video_file->getTmpName()))
+			throw new InvalidRequestException(400);
+
+		// save database object
+		$video = new Video(title: $title, description: $description, author_id: $author_id);
+		$video->Save();
+
+		// Save file
+		$video_folder = APP_ROOT . "/usercontent/videos/" . $video->id;
+
+		// check video file type
+		$mimey = new MimeTypes();
+		$mime_type = $mimey->getMimeType($video_file->getExtension());
+		if ($mime_type != "video/mp4" && $mime_type != "video/ogg" && $mime_type != "video/webm" && $mime_type != "video/x-matroska") {
+			$video->Delete();
+			throw new InvalidVideoException($video->id, 415); // media type unsupported
+		}
+
+		// move video
+		mkdir($video_folder);
+		Logger::Debug($video_file->getFilename() . " -> " . $video_folder . "/video.mp4");
+		$video_file->move($video_folder . "/video.mp4");
+
+		CORSHelper();
+		return api_json([
+			"id" => $video->id,
+			"title" => $video->title,
+			"description" => $video->description,
+			"likes" => $video->likes,
+			"dislikes" => $video->dislikes,
+			"comments" => 0,
+			"shares" => 0,
+			"author" => [
+				"id" => $author_id,
+			]
+		]);
+	}
 
 	public static function RegisterRoutes(): void
 	{
 		SimpleRouter::group([
 			"prefix" => "/video",
 		], function () {
-			SimpleRouter::get("/{id}", [VideoController::class, "getVideo"]);
-            SimpleRouter::get("/{id}/info", [VideoController::class, "getInfo"]);
-            SimpleRouter::get("/{id}/thumbnail", [VideoController::class, "getThumbnail"]);
+			SimpleRouter::get("/{id}/get", [VideoController::class, "getVideo"]);
+			SimpleRouter::get("/{id}/info", [VideoController::class, "getInfo"]);
+			SimpleRouter::get("/{id}/thumbnail", [VideoController::class, "getThumbnail"]);
 			SimpleRouter::post("/upload", [VideoController::class, "uploadVideo"]);
-            SimpleRouter::post("/search", [VideoController::class, "search"]);
-            SimpleRouter::options("/{id}", "CORSHelper");
-            SimpleRouter::options("/{id}/info", "CORSHelper");
-            SimpleRouter::options("/{id}/thumbnail", "CORSHelper");
-            SimpleRouter::options("/upload", "CORSHelper");
-            SimpleRouter::options("/search", "CORSHelper");
+			SimpleRouter::post("/search", [VideoController::class, "search"]);
+			SimpleRouter::get("/feed", [VideoController::class, "getFeed"]);
+			SimpleRouter::options("/{id}/get", "CORSHelper");
+			SimpleRouter::options("/{id}/info", "CORSHelper");
+			SimpleRouter::options("/{id}/thumbnail", "CORSHelper");
+			SimpleRouter::options("/upload", "CORSHelper");
+			SimpleRouter::options("/search", "CORSHelper");
 		});
 	}
 }

+ 16 - 0
app/Exceptions/InvalidVideoException.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace app\Exceptions;
+
+use app\Logger;
+use \Exception;
+use \Throwable;
+
+class InvalidVideoException extends Exception
+{
+	public function __construct(int $id, int $code = 0, ?Throwable $previous = null)
+	{
+		Logger::Error("Invalid video exception. ($id)");
+		parent::__construct("Invalid video exception. ($id)", $code, $previous);
+	}
+}

+ 4 - 2
app/Exceptions/SecurityFaultException.php

@@ -11,7 +11,7 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-namespace Hajeebtok\Types\Exceptions;
+namespace app\Exceptions;
 
 use Exception;
 use JetBrains\PhpStorm\Pure;
@@ -41,6 +41,7 @@ class SecurityFaultException extends Exception
 		$otherDetails = $devmode ? $otherDetails : "Redacted";
 
 		http_response_code(403);
+		CORSHelper();
 		die(<<<HTML
 <!doctype html>
 <html lang="en">
@@ -48,7 +49,7 @@ class SecurityFaultException extends Exception
 <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
                          <meta http-equiv="X-UA-Compatible" content="ie=edge">
-             <title>Shuzanne</title>
+             <title>Hajeebtok</title>
 </head>
 <body style="font-family: sans-serif; background-color: #eee; color: #333; padding: 20px;">
 <div style="border: 8px solid #f00; padding: 1em; background-color: #ffff00;">
@@ -56,6 +57,7 @@ class SecurityFaultException extends Exception
 	<p style="margin: 1em 0;">Your request has been terminated, and the incident has been logged. Please contact the site administrator if you believe this was in error.</p>
 	<summary>
 		<details>
+			<p>Message: $message</p>
 			<summary>Request details</summary>
 			<p>Incident ID: $incidentId</p>
 			<pre style="white-space: pre-wrap;">$details</pre>

+ 1 - 1
app/Types/DatabaseObjects/Account.php

@@ -6,7 +6,7 @@ use app\Exceptions\AccountNotFoundException;
 use app\Interfaces\IDatabaseObject;
 use app\Hajeebtok;
 use app\Logger;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class Account implements IDatabaseObject
 {

+ 1 - 1
app/Types/DatabaseObjects/Comment.php

@@ -6,7 +6,7 @@ use app\Exceptions\CommentNotFoundException;
 use app\Interfaces\IDatabaseObject;
 use app\Hajeebtok;
 use app\Logger;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class Comment implements IDatabaseObject
 {

+ 1 - 1
app/Types/DatabaseObjects/Follow.php

@@ -6,7 +6,7 @@ use app\Exceptions\FollowNotFoundException;
 use app\Interfaces\IDatabaseObject;
 use app\Hajeebtok;
 use app\Logger;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class Follow implements IDatabaseObject
 {

+ 1 - 1
app/Types/DatabaseObjects/Link.php

@@ -7,7 +7,7 @@ use app\Interfaces\IDatabaseObject;
 use app\Logger;
 use app\Types\LinkEnum;
 use app\Exceptions\LinkNotFoundException;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class Link implements IDatabaseObject
 {

+ 1 - 1
app/Types/DatabaseObjects/Message.php

@@ -6,7 +6,7 @@ use app\Exceptions\MessageNotFoundException;
 use app\Interfaces\IDatabaseObject;
 use app\Hajeebtok;
 use app\Logger;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class Message implements IDatabaseObject
 {

+ 1 - 1
app/Types/DatabaseObjects/Session.php

@@ -6,7 +6,7 @@ use app\Exceptions\SessionNotFoundException;
 use app\Interfaces\IDatabaseObject;
 use app\Hajeebtok;
 use app\Logger;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 use Random\RandomException;
 
 class Session implements IDatabaseObject

+ 9 - 3
app/Types/DatabaseObjects/Video.php

@@ -3,11 +3,12 @@
 namespace app\Types\DatabaseObjects;
 
 use app\Exceptions\AccountNotFoundException;
+use app\Exceptions\UnauthenticatedException;
 use app\Exceptions\VideoNotFoundException;
 use app\Interfaces\IDatabaseObject;
 use app\Hajeebtok;
 use app\Logger;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class Video implements IDatabaseObject
 {
@@ -48,6 +49,11 @@ class Video implements IDatabaseObject
 	 * Saves the object to the database.
 	 */
 	public function Save() {
+        $this->likes = 0;
+        $this->dislikes = 0;
+        if(empty($this->author_id)) throw new UnauthenticatedException(0, 401);
+        if(empty($this->description)) $this->description = "";
+
 		Hajeebtok::$Database->Query("INSERT INTO videos (title, description, author_id, likes, dislikes) VALUES (:title, :description, :author_id, :likes, :dislikes)", [
 			"title" => $this->title,
 			"description" => $this->description,
@@ -55,8 +61,8 @@ class Video implements IDatabaseObject
 			"likes" => $this->likes,
 			"dislikes" => $this->dislikes
 		]);
-		$id = Hajeebtok::$Database->LastInsertId();
-		Logger::Debug("Saved video id ($id).");
+		$this->id = Hajeebtok::$Database->LastInsertId();
+		Logger::Debug("Saved video id ($this->id).");
 	}
 
 	/**

+ 4 - 4
app/Types/DatabaseObjects/View.php

@@ -8,7 +8,7 @@ use app\Interfaces\IDatabaseObject;
 use app\Logger;
 use app\Types\LinkEnum;
 use app\Exceptions\LinkNotFoundException;
-use Hajeebtok\Types\Exceptions\SecurityFaultException;
+use app\Exceptions\SecurityFaultException;
 
 class View implements IDatabaseObject
 {
@@ -67,13 +67,13 @@ class View implements IDatabaseObject
 	}
 
     public function LoadMany(): array {
-
+        Logger::Debug("{$this->account_id}");
         if(!empty($this->account_id)) {
             $array =  ["account_id" => $this->account_id];
-            $query = "SELECT * FROM links WHERE account_id = :account_id";
+            $query = "SELECT * FROM views WHERE account_id = :account_id";
         } else if(!empty($this->video_id)) {
             $array =  ["video_id" => $this->video_id];
-            $query = "SELECT * FROM links WHERE video_id = :video_id";
+            $query = "SELECT * FROM views WHERE video_id = :video_id";
         } else {
             throw new ViewNotFoundException(0, 404);
         }