Moved post upload to API

This commit is contained in:
Marcin Kurczewski 2014-05-03 14:20:48 +02:00
parent f383a5ed21
commit 6ae4cea8bb
17 changed files with 433 additions and 47 deletions

View file

@ -70,6 +70,9 @@ $postValidation =
'score' => '-1|0|1', 'score' => '-1|0|1',
]; ];
\Chibi\Router::register(['PostController', 'uploadView'], 'GET', '/posts/upload', $postValidation);
\Chibi\Router::register(['PostController', 'uploadAction'], 'POST', '/posts/upload', $postValidation);
\Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}', $postValidation); \Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}', $postValidation);
\Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}/{query}', $postValidation); \Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}/{query}', $postValidation);
\Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}/{query}/{page}', $postValidation); \Chibi\Router::register(['PostController', 'listView'], 'GET', '/{source}/{query}/{page}', $postValidation);
@ -92,7 +95,6 @@ $postValidation =
foreach (['GET', 'POST'] as $method) foreach (['GET', 'POST'] as $method)
{ {
\Chibi\Router::register(['PostController', 'uploadAction'], $method, '/posts/upload', $postValidation);
\Chibi\Router::register(['PostController', 'viewAction'], $method, '/post/{id}', $postValidation); \Chibi\Router::register(['PostController', 'viewAction'], $method, '/post/{id}', $postValidation);
\Chibi\Router::register(['PostController', 'retrieveAction'], $method, '/post/{name}/retrieve', $postValidation); \Chibi\Router::register(['PostController', 'retrieveAction'], $method, '/post/{name}/retrieve', $postValidation);
\Chibi\Router::register(['PostController', 'thumbAction'], $method, '/post/{name}/thumb', $postValidation); \Chibi\Router::register(['PostController', 'thumbAction'], $method, '/post/{name}/thumb', $postValidation);

View file

@ -166,6 +166,7 @@ $(function()
{ {
handleInputs(files, function(postDom, file) handleInputs(files, function(postDom, file)
{ {
postDom.data('url', '');
postDom.data('file', file); postDom.data('file', file);
$('.file-name strong', postDom).text(file.name); $('.file-name strong', postDom).text(file.name);
@ -198,6 +199,7 @@ $(function()
handleInputs(urls, function(postDom, url) handleInputs(urls, function(postDom, url)
{ {
postDom.data('url', url); postDom.data('url', url);
postDom.data('file', '');
postDom.find('[name=source]').val(url); postDom.find('[name=source]').val(url);
if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/)) if (matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/))
{ {

View file

@ -40,4 +40,12 @@ class Api
}); });
return $statuses; return $statuses;
} }
public static function serializeFile($filePath, $fileName)
{
$x = new StdClass;
$x->filePath = $filePath;
$x->fileName = $fileName;
return $x;
}
} }

View file

@ -0,0 +1,8 @@
<?php
class ApiMissingArgumentException extends SimpleException
{
public function __construct($argumentName)
{
parent::__construct('Expected argument "' . $argumentName . '" was not specified');
}
}

View file

@ -84,54 +84,35 @@ class PostController
]); ]);
} }
public function uploadView()
{
}
public function uploadAction() public function uploadAction()
{ {
$context = getContext(); $jobArgs =
Access::assert(Privilege::UploadPost); [
if (getConfig()->registration->needEmailForUploading) AddPostJob::ANONYMOUS => InputHelper::get('anonymous'),
Access::assertEmailConfirmation(); EditPostSafetyJob::SAFETY => InputHelper::get('safety'),
EditPostTagsJob::TAG_NAMES => InputHelper::get('tags'),
EditPostSourceJob::SOURCE => InputHelper::get('source'),
];
if (!InputHelper::get('submit')) if (!empty(InputHelper::get('url')))
return;
\Chibi\Database::transaction(function() use ($context)
{ {
$post = PostModel::spawn(); $jobArgs[EditPostUrlJob::CONTENT_URL] = InputHelper::get('url');
LogHelper::bufferChanges(); }
elseif (!empty($_FILES['file']['name']))
{
$file = $_FILES['file'];
TransferHelper::handleUploadErrors($file);
//basic stuff $jobArgs[EditPostContentJob::POST_CONTENT] = Api::serializeFile(
$anonymous = InputHelper::get('anonymous'); $file['tmp_name'],
if (Auth::isLoggedIn() and !$anonymous) $file['name']);
$post->setUploader(Auth::getCurrentUser()); }
//store the post to get the ID in the logs Api::run(new AddPostJob(), $jobArgs);
PostModel::forgeId($post);
//do the edits
$this->doEdit($post, true);
//this basically means that user didn't specify file nor url
if (empty($post->type))
throw new SimpleException('No post type detected; upload faled');
//clean edit log
LogHelper::setBuffer([]);
//log
$fmt = ($anonymous and !getConfig()->misc->logAnonymousUploads)
? '{anon}'
: '{user}';
$fmt .= ' added {post} (tags: {tags}, safety: {safety}, source: {source})';
LogHelper::log($fmt, [
'post' => TextHelper::reprPost($post),
'tags' => TextHelper::reprTags($post->getTags()),
'safety' => PostSafety::toString($post->safety),
'source' => $post->source]);
//finish
LogHelper::flush();
PostModel::save($post);
});
} }
public function editAction($id) public function editAction($id)

View file

@ -4,6 +4,7 @@ abstract class AbstractJob
const COMMENT_ID = 'comment-id'; const COMMENT_ID = 'comment-id';
const POST_ID = 'post-id'; const POST_ID = 'post-id';
const TAG_NAME = 'tag-name'; const TAG_NAME = 'tag-name';
const TAG_NAMES = 'tags';
const TEXT = 'text'; const TEXT = 'text';
const PAGE_NUMBER = 'page-number'; const PAGE_NUMBER = 'page-number';
const QUERY = 'query'; const QUERY = 'query';
@ -24,12 +25,22 @@ abstract class AbstractJob
public function getArgument($key) public function getArgument($key)
{ {
if (!isset($this->arguments[$key])) if (!$this->hasArgument($key))
throw new SimpleException('Expected argument "' . $key . '" was not specified'); throw new ApiMissingArgumentException($key);
return $this->arguments[$key]; return $this->arguments[$key];
} }
public function getArguments()
{
return $this->arguments;
}
public function hasArgument($key)
{
return isset($this->arguments[$key]);
}
public function setArguments($arguments) public function setArguments($arguments)
{ {
$this->arguments = $arguments; $this->arguments = $arguments;

89
src/Jobs/AddPostJob.php Normal file
View file

@ -0,0 +1,89 @@
<?php
class AddPostJob extends AbstractJob
{
const ANONYMOUS = 'anonymous';
public function execute()
{
$post = PostModel::spawn();
LogHelper::bufferChanges();
//basic stuff
$anonymous = $this->getArgument(self::ANONYMOUS);
if (Auth::isLoggedIn() and !$anonymous)
$post->setUploader(Auth::getCurrentUser());
//store the post to get the ID in the logs
PostModel::forgeId($post);
//do the edits
//warning: each handler runs uses the same privileges as post editing
$subJobs =
[
new EditPostSafetyJob(),
new EditPostTagsJob(),
new EditPostSourceJob(),
new EditPostRelationsJob(),
new EditPostContentJob(),
new EditPostUrlJob(),
];
foreach ($subJobs as $subJob)
{
$args = $this->getArguments();
$args[self::POST_ID] = $post->id;
try
{
Api::run($subJob, $args);
}
catch (ApiMissingArgumentException $e)
{
}
}
//load the post after edits
$post = PostModel::findById($post->id);
// basically means that user didn't specify file nor url
//todo:
//- move this to PostEntity::isValid()
//- create IValidatable interface
//- enforce entity validity upon calling save() in models
if (empty($post->type))
throw new SimpleException('No post type detected; upload faled');
//clean edit log
LogHelper::setBuffer([]);
//log
LogHelper::log('{user} added {post} (tags: {tags}, safety: {safety}, source: {source})', [
'user' => ($anonymous and !getConfig()->misc->logAnonymousUploads)
? TextHelper::reprUser(UserModel::getAnonymousName())
: TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'tags' => TextHelper::reprTags($post->getTags()),
'safety' => PostSafety::toString($post->safety),
'source' => $post->source]);
//finish
LogHelper::flush();
PostModel::save($post);
return $post;
}
public function requiresPrivilege()
{
return Privilege::UploadPost;
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return getConfig()->registration->needEmailForUploading;
}
}

View file

@ -0,0 +1,38 @@
<?php
class EditPostContentJob extends AbstractPostEditJob
{
const CONTENT = 'content';
public function execute()
{
$file = $this->getArgument(self::CONTENT);
$this->post->setContentFromPath($file->filePath, $file->fileName);
PostModel::save($this->post);
LogHelper::log('{user} changed contents of {post}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($this->post)]);
return $this->post;
}
public function requiresPrivilege()
{
return
[
Privilege::EditPostFile,
Access::getIdentity($this->post->getUploader())
];
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return false;
}
}

View file

@ -0,0 +1,54 @@
<?php
class EditPostRelationsJob extends AbstractPostEditJob
{
const RELATED_POST_IDS = 'related-post-ids';
public function execute()
{
$post = $this->post;
$relations = $this->getArgument(self::RELATED_POST_IDS);
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->getRelations());
$post->setRelationsFromText($relations);
$newRelatedIds = array_map(function($post) { return $post->id; }, $post->getRelations());
PostModel::save($post);
foreach (array_diff($oldRelatedIds, $newRelatedIds) as $post2id)
{
LogHelper::log('{user} removed relation between {post} and {post2}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'post2' => TextHelper::reprPost($post2id)]);
}
foreach (array_diff($newRelatedIds, $oldRelatedIds) as $post2id)
{
LogHelper::log('{user} added relation between {post} and {post2}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'post2' => TextHelper::reprPost($post2id)]);
}
return $post;
}
public function requiresPrivilege()
{
return
[
Privilege::EditPostRelations,
Access::getIdentity($this->post->getUploader())
];
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return false;
}
}

View file

@ -0,0 +1,45 @@
<?php
class EditPostSafetyJob extends AbstractPostEditJob
{
const SAFETY = 'safety';
public function execute()
{
$post = $this->post;
$newSafety = $this->getArgument(self::SAFETY);
$oldSafety = $post->safety;
$post->setSafety($newSafety);
PostModel::save($post);
if ($oldSafety != $newSafety)
{
LogHelper::log('{user} changed safety of {post} to {safety}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'safety' => PostSafety::toString($post->safety)]);
}
return $post;
}
public function requiresPrivilege()
{
return
[
Privilege::EditPostSafety,
Access::getIdentity($this->post->getUploader())
];
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return false;
}
}

View file

@ -0,0 +1,45 @@
<?php
class EditPostSourceJob extends AbstractPostEditJob
{
const SOURCE = 'source';
public function execute()
{
$post = $this->post;
$newSource = $this->getArgument(self::SOURCE);
$oldSource = $post->source;
$post->setSource($newSource);
PostModel::save($post);
if ($oldSource != $newSource)
{
LogHelper::log('{user} changed source of {post} to {source}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'source' => $post->source]);
}
return $post;
}
public function requiresPrivilege()
{
return
[
Privilege::EditPostSource,
Access::getIdentity($this->post->getUploader())
];
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return false;
}
}

View file

@ -0,0 +1,52 @@
<?php
class EditPostTagsJob extends AbstractPostEditJob
{
public function execute()
{
$post = $this->post;
$tags = $this->getArgument(self::TAG_NAMES);
$oldTags = array_map(function($tag) { return $tag->name; }, $post->getTags());
$post->setTagsFromText($tags);
$newTags = array_map(function($tag) { return $tag->name; }, $post->getTags());
PostModel::save($post);
foreach (array_diff($oldTags, $newTags) as $tag)
{
LogHelper::log('{user} untagged {post} with {tag}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'tag' => TextHelper::reprTag($tag)]);
}
foreach (array_diff($newTags, $oldTags) as $tag)
{
LogHelper::log('{user} tagged {post} with {tag}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post),
'tag' => TextHelper::reprTag($tag)]);
}
return $post;
}
public function requiresPrivilege()
{
return
[
Privilege::EditPostTags,
Access::getIdentity($this->post->getUploader())
];
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return false;
}
}

View file

@ -0,0 +1,40 @@
<?php
class EditPostUrlJob extends AbstractPostEditJob
{
const CONTENT_URL = 'url';
public function execute()
{
$post = $this->post;
$url = $this->getArgument(self::CONTENT_URL);
$post->setContentFromUrl($url);
PostModel::save($post);
LogHelper::log('{user} changed contents of {post}', [
'user' => TextHelper::reprUser(Auth::getCurrentUser()),
'post' => TextHelper::reprPost($post)]);
return $post;
}
public function requiresPrivilege()
{
return
[
Privilege::EditPostFile,
Access::getIdentity($this->post->getUploader())
];
}
public function requiresAuthentication()
{
return false;
}
public function requiresConfirmedEmail()
{
return false;
}
}

View file

@ -120,6 +120,14 @@ abstract class AbstractCrudModel implements IModel
{ {
$stmt = new Sql\InsertStatement(); $stmt = new Sql\InsertStatement();
$stmt->setTable($table); $stmt->setTable($table);
foreach ($entity as $key => $val)
{
$key = TextCaseConverter::convert($key,
TextCaseConverter::LOWER_CAMEL_CASE,
TextCaseConverter::SNAKE_CASE);
$stmt->setColumn($key, new Sql\Binding($val));
}
Database::exec($stmt); Database::exec($stmt);
$entity->id = Database::lastInsertId(); $entity->id = Database::lastInsertId();
} }

View file

@ -19,6 +19,10 @@ class PostModel extends AbstractCrudModel
public static function spawn() public static function spawn()
{ {
$post = new PostEntity; $post = new PostEntity;
$post->score = 0;
$post->favCount = 0;
$post->commentCount = 0;
$post->safety = PostSafety::Safe;
$post->hidden = false; $post->hidden = false;
$post->uploadDate = time(); $post->uploadDate = time();
do do

View file

@ -30,7 +30,6 @@ class TokenModel extends AbstractCrudModel
$stmt->setColumn($key, new Sql\Binding($val)); $stmt->setColumn($key, new Sql\Binding($val));
Database::exec($stmt); Database::exec($stmt);
}); });
} }

View file

@ -30,7 +30,7 @@
{ {
$registerNavItem( $registerNavItem(
'Upload', 'Upload',
\Chibi\Router::linkTo(['PostController', 'uploadAction']), \Chibi\Router::linkTo(['PostController', 'uploadView']),
$activeController == 'post' and $activeAction == 'upload'); $activeController == 'post' and $activeAction == 'upload');
} }