Added post relations
This commit is contained in:
parent
22b30c3e43
commit
5dc85b7dee
20 changed files with 213 additions and 18 deletions
2
TODO
2
TODO
|
@ -6,11 +6,9 @@ everything related to posts:
|
|||
- fav
|
||||
- score (see notes about scoring)
|
||||
- editing
|
||||
- relations
|
||||
- ability to loop video posts
|
||||
- post edit history (think
|
||||
http://konachan.com/history?search=post%3A188614)
|
||||
- relations
|
||||
- previous and next post (difficult)
|
||||
- extract Pager from PagedCollectionPresenter
|
||||
- rename PagedCollectionPresenter to PagerPresenter
|
||||
|
|
|
@ -48,7 +48,8 @@ changePostSafety = powerUser, moderator, administrator
|
|||
changePostSource = regularUser, powerUser, moderator, administrator
|
||||
changePostTags = regularUser, powerUser, moderator, administrator
|
||||
changePostContent = regularUser, powerUser, moderator, administrator
|
||||
changePostThumbnail = regularUser, powerUser, moderator, administrator
|
||||
changePostThumbnail = powerUser, moderator, administrator
|
||||
changePostRelations = regularUser, powerUser, moderator, administrator
|
||||
|
||||
listTags = anonymous, regularUser, powerUser, moderator, administrator
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
|
|||
changePostTags: 'changePostTags',
|
||||
changePostContent: 'changePostContent',
|
||||
changePostThumbnail: 'changePostThumbnail',
|
||||
changePostRelations: 'changePostRelations',
|
||||
|
||||
listTags: 'listTags',
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@ App.Presenters.PostPresenter = function(
|
|||
editPrivileges.canChangeTags = auth.hasPrivilege(auth.privileges.changePostTags);
|
||||
editPrivileges.canChangeContent = auth.hasPrivilege(auth.privileges.changePostContent);
|
||||
editPrivileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail);
|
||||
editPrivileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations);
|
||||
|
||||
promise.waitAll(
|
||||
util.promiseTemplate('post'),
|
||||
|
@ -190,6 +191,10 @@ App.Presenters.PostPresenter = function(
|
|||
formData.tags = tagInput.getTags().join(' ');
|
||||
}
|
||||
|
||||
if (editPrivileges.canChangeRelations) {
|
||||
formData.relations = $form.find('[name=relations]').val();
|
||||
}
|
||||
|
||||
if (post.tags.length === 0) {
|
||||
showEditError('No tags set.');
|
||||
return;
|
||||
|
|
|
@ -39,6 +39,15 @@
|
|||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (privileges.canChangeRelations) { %>
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="post-relations">Relations:</label>
|
||||
<div class="form-input">
|
||||
<input maxlength="200" type="text" name="relations" id="post-relations" placeholder="Post ids, separated with space" value="<%= _.pluck(post.relations, 'id').join(' ') %>"/>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (privileges.canChangeContent) { %>
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="post-content">Content:</label>
|
||||
|
|
|
@ -94,6 +94,19 @@
|
|||
|
||||
</ul>
|
||||
|
||||
<% if (_.any(post.relations)) { %>
|
||||
<h1>Related posts</h1>
|
||||
<ul class="related">
|
||||
<% _.each(post.relations, function(relatedPost) { %>
|
||||
<li>
|
||||
<a href="#/post/<%= relatedPost.id %>">
|
||||
<%= relatedPost.idMarkdown %>
|
||||
</a>
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<% } %>
|
||||
|
||||
<% if (_.any(privileges) || _.any(editPrivileges)) { %>
|
||||
<h1>Options</h1>
|
||||
|
||||
|
|
|
@ -35,20 +35,20 @@ final class PostController extends AbstractController
|
|||
public function getFeatured()
|
||||
{
|
||||
$post = $this->postService->getFeatured();
|
||||
return $this->postViewProxy->fromEntity($post);
|
||||
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
|
||||
}
|
||||
|
||||
public function getByNameOrId($postNameOrId)
|
||||
{
|
||||
$post = $this->postService->getByNameOrId($postNameOrId);
|
||||
return $this->postViewProxy->fromEntity($post);
|
||||
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
|
||||
}
|
||||
|
||||
public function getFiltered()
|
||||
{
|
||||
$formData = new \Szurubooru\FormData\SearchFormData($this->inputReader);
|
||||
$searchResult = $this->postService->getFiltered($formData);
|
||||
$entities = $this->postViewProxy->fromArray($searchResult->getEntities());
|
||||
$entities = $this->postViewProxy->fromArray($searchResult->getEntities(), $this->getLightFetchConfig());
|
||||
return [
|
||||
'data' => $entities,
|
||||
'pageSize' => $searchResult->getPageSize(),
|
||||
|
@ -66,7 +66,7 @@ final class PostController extends AbstractController
|
|||
$this->privilegeService->assertPrivilege(\Szurubooru\Privilege::UPLOAD_POSTS_ANONYMOUSLY);
|
||||
|
||||
$post = $this->postService->createPost($formData);
|
||||
return $this->postViewProxy->fromEntity($post);
|
||||
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
|
||||
}
|
||||
|
||||
public function updatePost($postNameOrId)
|
||||
|
@ -91,7 +91,7 @@ final class PostController extends AbstractController
|
|||
|
||||
$this->postService->updatePost($post, $formData);
|
||||
$post = $this->postService->getByNameOrId($postNameOrId);
|
||||
return $this->postViewProxy->fromEntity($post);
|
||||
return $this->postViewProxy->fromEntity($post, $this->getFullFetchConfig());
|
||||
}
|
||||
|
||||
public function deletePost($postNameOrId)
|
||||
|
@ -105,4 +105,22 @@ final class PostController extends AbstractController
|
|||
$post = $this->postService->getByNameOrId($postNameOrId);
|
||||
$this->postService->featurePost($post);
|
||||
}
|
||||
|
||||
private function getFullFetchConfig()
|
||||
{
|
||||
return
|
||||
[
|
||||
\Szurubooru\Controllers\ViewProxies\PostViewProxy::FETCH_RELATIONS => true,
|
||||
\Szurubooru\Controllers\ViewProxies\PostViewProxy::FETCH_TAGS => true,
|
||||
\Szurubooru\Controllers\ViewProxies\PostViewProxy::FETCH_USER => true,
|
||||
];
|
||||
}
|
||||
|
||||
private function getLightFetchConfig()
|
||||
{
|
||||
return
|
||||
[
|
||||
\Szurubooru\Controllers\ViewProxies\PostViewProxy::FETCH_TAGS => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,14 @@ namespace Szurubooru\Controllers\ViewProxies;
|
|||
|
||||
abstract class AbstractViewProxy
|
||||
{
|
||||
public abstract function fromEntity($entity);
|
||||
public abstract function fromEntity($entity, $config = []);
|
||||
|
||||
public function fromArray($entities)
|
||||
public function fromArray($entities, $config = [])
|
||||
{
|
||||
return array_values(array_map(
|
||||
function($entity)
|
||||
function($entity) use ($config)
|
||||
{
|
||||
return static::fromEntity($entity);
|
||||
return static::fromEntity($entity, $config);
|
||||
},
|
||||
$entities));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ namespace Szurubooru\Controllers\ViewProxies;
|
|||
|
||||
class PostViewProxy extends AbstractViewProxy
|
||||
{
|
||||
const FETCH_USER = 'fetchUser';
|
||||
const FETCH_TAGS = 'fetchTags';
|
||||
const FETCH_RELATIONS = 'fetchRelations';
|
||||
|
||||
private $tagViewProxy;
|
||||
private $userViewProxy;
|
||||
|
||||
|
@ -14,7 +18,7 @@ class PostViewProxy extends AbstractViewProxy
|
|||
$this->userViewProxy = $userViewProxy;
|
||||
}
|
||||
|
||||
public function fromEntity($post)
|
||||
public function fromEntity($post, $config = [])
|
||||
{
|
||||
$result = new \StdClass;
|
||||
if ($post)
|
||||
|
@ -34,9 +38,16 @@ class PostViewProxy extends AbstractViewProxy
|
|||
$result->imageHeight = $post->getImageHeight();
|
||||
$result->featureCount = $post->getFeatureCount();
|
||||
$result->lastFeatureTime = $post->getLastFeatureTime();
|
||||
$result->tags = $this->tagViewProxy->fromArray($post->getTags());
|
||||
$result->originalFileSize = $post->getOriginalFileSize();
|
||||
$result->user = $this->userViewProxy->fromEntity($post->getUser());
|
||||
|
||||
if (!empty($config[self::FETCH_TAGS]))
|
||||
$result->tags = $this->tagViewProxy->fromArray($post->getTags());
|
||||
|
||||
if (!empty($config[self::FETCH_USER]))
|
||||
$result->user = $this->userViewProxy->fromEntity($post->getUser());
|
||||
|
||||
if (!empty($config[self::FETCH_RELATIONS]))
|
||||
$result->relations = $this->fromArray($post->getRelatedPosts());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace Szurubooru\Controllers\ViewProxies;
|
|||
|
||||
class TagViewProxy extends AbstractViewProxy
|
||||
{
|
||||
public function fromEntity($tag)
|
||||
public function fromEntity($tag, $config = [])
|
||||
{
|
||||
$result = new \StdClass;
|
||||
if ($tag)
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace Szurubooru\Controllers\ViewProxies;
|
|||
|
||||
class TokenViewProxy extends AbstractViewProxy
|
||||
{
|
||||
public function fromEntity($token)
|
||||
public function fromEntity($token, $config = [])
|
||||
{
|
||||
$result = new \StdClass;
|
||||
if ($token)
|
||||
|
|
|
@ -10,7 +10,7 @@ class UserViewProxy extends AbstractViewProxy
|
|||
$this->privilegeService = $privilegeService;
|
||||
}
|
||||
|
||||
public function fromEntity($user)
|
||||
public function fromEntity($user, $config = [])
|
||||
{
|
||||
$result = new \StdClass;
|
||||
if ($user)
|
||||
|
|
|
@ -76,6 +76,13 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||
{
|
||||
return $this->getTags($post);
|
||||
});
|
||||
|
||||
$post->setLazyLoader(
|
||||
\Szurubooru\Entities\Post::LAZY_LOADER_RELATED_POSTS,
|
||||
function(\Szurubooru\Entities\Post $post)
|
||||
{
|
||||
return $this->getRelatedPosts($post);
|
||||
});
|
||||
}
|
||||
|
||||
protected function afterSave(\Szurubooru\Entities\Entity $post)
|
||||
|
@ -83,6 +90,7 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||
$this->syncContent($post);
|
||||
$this->syncThumbnailSourceContent($post);
|
||||
$this->syncTags($post);
|
||||
$this->syncPostRelations($post);
|
||||
}
|
||||
|
||||
private function getTags(\Szurubooru\Entities\Post $post)
|
||||
|
@ -95,6 +103,28 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||
return $this->userDao->findById($post->getUserId());
|
||||
}
|
||||
|
||||
private function getRelatedPosts(\Szurubooru\Entities\Post $post)
|
||||
{
|
||||
$query = $this->fpdo
|
||||
->from('postRelations')
|
||||
->where('post1id = :post1id OR post2id = :post2id', [
|
||||
':post1id' => $post->getId(),
|
||||
':post2id' => $post->getId()]);
|
||||
|
||||
$relatedPostIds = [];
|
||||
foreach ($query as $arrayEntity)
|
||||
{
|
||||
$post1id = intval($arrayEntity['post1id']);
|
||||
$post2id = intval($arrayEntity['post2id']);
|
||||
if ($post1id !== $post->getId())
|
||||
$relatedPostIds[] = $post1id;
|
||||
if ($post2id !== $post->getId())
|
||||
$relatedPostIds[] = $post2id;
|
||||
}
|
||||
|
||||
return $this->findByIds($relatedPostIds);
|
||||
}
|
||||
|
||||
private function syncContent(\Szurubooru\Entities\Post $post)
|
||||
{
|
||||
$targetPath = $post->getContentPath();
|
||||
|
@ -154,4 +184,29 @@ class PostDao extends AbstractDao implements ICrudDao
|
|||
$this->fpdo->deleteFrom('postTags')->where('postId', $post->getId())->where('tagId', $tagId)->execute();
|
||||
}
|
||||
}
|
||||
|
||||
private function syncPostRelations(\Szurubooru\Entities\Post $post)
|
||||
{
|
||||
$this->fpdo->deleteFrom('postRelations')->where('post1id', $post->getId())->execute();
|
||||
$this->fpdo->deleteFrom('postRelations')->where('post2id', $post->getId())->execute();
|
||||
|
||||
$relatedPostIds = array_filter(array_unique(array_map(
|
||||
function ($post)
|
||||
{
|
||||
if (!$post->getId())
|
||||
throw new \RuntimeException('Unsaved entities found');
|
||||
return $post->getId();
|
||||
},
|
||||
$post->getRelatedPosts())));
|
||||
|
||||
foreach ($relatedPostIds as $postId)
|
||||
{
|
||||
$this->fpdo
|
||||
->insertInto('postRelations')
|
||||
->values([
|
||||
'post1id' => $post->getId(),
|
||||
'post2id' => $postId])
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ final class Post extends Entity
|
|||
const LAZY_LOADER_TAGS = 'tags';
|
||||
const LAZY_LOADER_CONTENT = 'content';
|
||||
const LAZY_LOADER_THUMBNAIL_SOURCE_CONTENT = 'thumbnailSourceContent';
|
||||
const LAZY_LOADER_RELATED_POSTS = 'relatedPosts';
|
||||
|
||||
const META_TAG_COUNT = 'tagCount';
|
||||
|
||||
|
@ -201,6 +202,16 @@ final class Post extends Entity
|
|||
$this->setMeta(self::META_TAG_COUNT, count($tags));
|
||||
}
|
||||
|
||||
public function getRelatedPosts()
|
||||
{
|
||||
return $this->lazyLoad(self::LAZY_LOADER_RELATED_POSTS, []);
|
||||
}
|
||||
|
||||
public function setRelatedPosts(array $relatedPosts)
|
||||
{
|
||||
$this->lazySave(self::LAZY_LOADER_RELATED_POSTS, $relatedPosts);
|
||||
}
|
||||
|
||||
public function getUser()
|
||||
{
|
||||
return $this->lazyLoad(self::LAZY_LOADER_USER, null);
|
||||
|
|
|
@ -8,6 +8,7 @@ class PostEditFormData implements \Szurubooru\IValidatable
|
|||
public $safety;
|
||||
public $source;
|
||||
public $tags;
|
||||
public $relations;
|
||||
|
||||
public $seenEditTime;
|
||||
|
||||
|
@ -20,6 +21,7 @@ class PostEditFormData implements \Szurubooru\IValidatable
|
|||
$this->safety = \Szurubooru\Helpers\EnumHelper::postSafetyFromString($inputReader->safety);
|
||||
$this->source = $inputReader->source;
|
||||
$this->tags = preg_split('/[\s+]/', $inputReader->tags);
|
||||
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
||||
$this->seenEditTime = $inputReader->seenEditTime;
|
||||
}
|
||||
}
|
||||
|
@ -30,5 +32,11 @@ class PostEditFormData implements \Szurubooru\IValidatable
|
|||
|
||||
if ($this->source !== null)
|
||||
$validator->validatePostSource($this->source);
|
||||
|
||||
if ($this->relations)
|
||||
{
|
||||
foreach ($this->relations as $relatedPostId)
|
||||
$validator->validateNumber($relatedPostId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ class Privilege
|
|||
const CHANGE_POST_TAGS = 'changePostTags';
|
||||
const CHANGE_POST_CONTENT = 'changePostContent';
|
||||
const CHANGE_POST_THUMBNAIL = 'changePostThumbnail';
|
||||
const CHANGE_POST_RELATIONS = 'changePostRelations';
|
||||
|
||||
const LIST_TAGS = 'listTags';
|
||||
}
|
||||
|
|
|
@ -136,6 +136,9 @@ class PostService
|
|||
if ($formData->tags !== null)
|
||||
$this->updatePostTags($post, $formData->tags);
|
||||
|
||||
if ($formData->relations !== null)
|
||||
$this->updatePostRelations($post, $formData->relations);
|
||||
|
||||
return $this->postDao->save($post);
|
||||
};
|
||||
return $this->transactionManager->commit($transactionFunc);
|
||||
|
@ -244,6 +247,16 @@ class PostService
|
|||
$post->setTags($tags);
|
||||
}
|
||||
|
||||
private function updatePostRelations(\Szurubooru\Entities\Post $post, array $newRelatedPostIds)
|
||||
{
|
||||
$relatedPosts = $this->postDao->findByIds($newRelatedPostIds);
|
||||
foreach ($newRelatedPostIds as $postId)
|
||||
if (!isset($relatedPosts[$postId]))
|
||||
throw new \DomainException('Post with id "' . $postId . '" was not found.');
|
||||
|
||||
$post->setRelatedPosts($relatedPosts);
|
||||
}
|
||||
|
||||
public function deletePost(\Szurubooru\Entities\Post $post)
|
||||
{
|
||||
$transactionFunc = function() use ($post)
|
||||
|
|
19
src/Upgrades/Upgrade08.php
Normal file
19
src/Upgrades/Upgrade08.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace Szurubooru\Upgrades;
|
||||
|
||||
class Upgrade08 implements IUpgrade
|
||||
{
|
||||
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
|
||||
{
|
||||
$pdo = $databaseConnection->getPDO();
|
||||
|
||||
$pdo->exec('CREATE TABLE postRelations
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
post1id INTEGER NOT NULL,
|
||||
post2id INTEGER NOT NULL,
|
||||
UNIQUE (post1id, post2id)
|
||||
)');
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ return [
|
|||
$container->get(\Szurubooru\Upgrades\Upgrade05::class),
|
||||
$container->get(\Szurubooru\Upgrades\Upgrade06::class),
|
||||
$container->get(\Szurubooru\Upgrades\Upgrade07::class),
|
||||
$container->get(\Szurubooru\Upgrades\Upgrade08::class),
|
||||
];
|
||||
}),
|
||||
|
||||
|
|
|
@ -159,6 +159,37 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
|||
$this->assertEquals(2, count($tagDao->findAll()));
|
||||
}
|
||||
|
||||
public function testSavingUnsavedRelations()
|
||||
{
|
||||
$post1 = $this->getPost();
|
||||
$post2 = $this->getPost();
|
||||
$testPosts = [$post1, $post2];
|
||||
|
||||
$postDao = $this->getPostDao();
|
||||
$post = $this->getPost();
|
||||
$post->setRelatedPosts($testPosts);
|
||||
$this->setExpectedException(\Exception::class, 'Unsaved entities found');
|
||||
$postDao->save($post);
|
||||
}
|
||||
|
||||
public function testSavingRelations()
|
||||
{
|
||||
$post1 = $this->getPost();
|
||||
$post2 = $this->getPost();
|
||||
$testPosts = [$post1, $post2];
|
||||
|
||||
$postDao = $this->getPostDao();
|
||||
$postDao->save($post1);
|
||||
$postDao->save($post2);
|
||||
$post = $this->getPost();
|
||||
$post->setRelatedPosts($testPosts);
|
||||
$postDao->save($post);
|
||||
|
||||
$savedPost = $postDao->findById($post->getId());
|
||||
$this->assertEntitiesEqual($testPosts, $post->getRelatedPosts());
|
||||
$this->assertEquals(2, count($savedPost->getRelatedPosts()));
|
||||
}
|
||||
|
||||
public function testSavingUser()
|
||||
{
|
||||
$testUser = new \Szurubooru\Entities\User(5);
|
||||
|
|
Loading…
Reference in a new issue