Database changes (#53)

* Implement database changes

* Apply fixes from StyleCI

* Update post column

* Split foreign keys into their own migrations, rename pivot tables

* Update pivot table references

* Update column name

* Update core columns

* Remove unnecessary argument

* Use whereColumn

* Update core attribute names

* Rename attributes
This commit is contained in:
Toby Zerner 2018-09-17 04:21:51 +09:30 committed by Franz Liedke
parent dd1f2d46fd
commit 2dac4d075d
26 changed files with 271 additions and 62 deletions

View File

@ -17,9 +17,9 @@ export default class Tag extends mixin(Model, {
isChild: Model.attribute('isChild'),
isHidden: Model.attribute('isHidden'),
discussionsCount: Model.attribute('discussionsCount'),
lastTime: Model.attribute('lastTime', Model.transformDate),
lastDiscussion: Model.hasOne('lastDiscussion'),
discussionCount: Model.attribute('discussionCount'),
lastPostedAt: Model.attribute('lastPostedAt', Model.transformDate),
lastPostedDiscussion: Model.hasOne('lastPostedDiscussion'),
isRestricted: Model.attribute('isRestricted'),
canStartDiscussion: Model.attribute('canStartDiscussion'),

View File

@ -6,7 +6,7 @@ export default function sortTags(tags) {
// If they're both secondary tags, sort them by their discussions count,
// descending.
if (aPos === null && bPos === null)
return b.discussionsCount() - a.discussionsCount();
return b.discussionCount() - a.discussionCount();
// If just one is a secondary tag, then the primary tag should
// come first.

View File

@ -41,7 +41,7 @@ export default function() {
const more = tags
.filter(tag => tag.position() === null)
.sort((a, b) => b.discussionsCount() - a.discussionsCount());
.sort((a, b) => b.discussionCount() - a.discussionCount());
more.splice(0, 3).forEach(addTag);

View File

@ -30,7 +30,7 @@ export default class TagsPage extends Page {
<div className="TagsPage-content sideNavOffset">
<ul className="TagTiles">
{pinned.map(tag => {
const lastDiscussion = tag.lastDiscussion();
const lastPostedDiscussion = tag.lastPostedDiscussion();
const children = sortTags(app.store.all('tags').filter(child => child.parent() === tag));
return (
@ -55,16 +55,16 @@ export default class TagsPage extends Page {
</div>
) : ''}
</a>
{lastDiscussion
{lastPostedDiscussion
? (
<a className="TagTile-lastDiscussion"
href={app.route.discussion(lastDiscussion, lastDiscussion.lastPostNumber())}
<a className="TagTile-lastPostedDiscussion"
href={app.route.discussion(lastPostedDiscussion, lastPostedDiscussion.lastPostNumber())}
config={m.route}>
<span className="TagTile-lastDiscussion-title">{lastDiscussion.title()}</span>
{humanTime(lastDiscussion.lastTime())}
<span className="TagTile-lastPostedDiscussion-title">{lastPostedDiscussion.title()}</span>
{humanTime(lastPostedDiscussion.lastPostedAt())}
</a>
) : (
<span className="TagTile-lastDiscussion"/>
<span className="TagTile-lastPostedDiscussion"/>
)}
</li>
);

View File

@ -68,7 +68,7 @@
}
}
}
.TagTile-info, .TagTile-lastDiscussion {
.TagTile-info, .TagTile-lastPostedDiscussion {
padding: 20px;
text-decoration: none !important;
display: block;
@ -108,7 +108,7 @@
margin-right: 10px;
}
}
.TagTile-lastDiscussion {
.TagTile-lastPostedDiscussion {
bottom: 0;
height: 42px;
padding: 7px 0;
@ -124,7 +124,7 @@
color: fade(@body-bg, 70%);
}
&:hover .TagTile-lastDiscussion-title {
&:hover .TagTile-lastPostedDiscussion-title {
text-decoration: underline;
}
@ -134,6 +134,6 @@
font-weight: bold;
}
}
.TagTile-lastDiscussion-title {
.TagTile-lastPostedDiscussion-title {
margin-right: 10px;
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('tags', function (Blueprint $table) {
$table->renameColumn('discussions_count', 'discussion_count');
$table->renameColumn('last_time', 'last_posted_at');
$table->renameColumn('last_discussion_id', 'last_posted_discussion_id');
$table->integer('parent_id')->unsigned()->nullable()->change();
$table->integer('last_posted_user_id')->unsigned()->nullable();
});
},
'down' => function (Builder $schema) {
$schema->table('tags', function (Blueprint $table) {
$table->dropColumn('last_posted_user_id');
$table->integer('parent_id')->nullable()->change();
$table->renameColumn('discussion_count', 'discussions_count');
$table->renameColumn('last_posted_at', 'last_time');
$table->renameColumn('last_posted_discussion_id', 'last_discussion_id');
});
}
];

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
// Set non-existent entity IDs to NULL so that we will be able to create
// foreign keys without any issues.
$connection = $schema->getConnection();
$select = function ($id, $table, $column) use ($connection) {
return new Expression(
'('.$connection->table($table)->whereColumn('id', $column)->select($id)->toSql().')'
);
};
$connection->table('tags')->update([
'last_posted_user_id' => $select('last_posted_user_id', 'discussions', 'last_posted_discussion_id'),
'last_posted_discussion_id' => $select('id', 'discussions', 'last_posted_discussion_id'),
]);
$schema->table('tags', function (Blueprint $table) {
$table->foreign('parent_id')->references('id')->on('tags')->onDelete('set null');
$table->foreign('last_posted_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('last_posted_discussion_id')->references('id')->on('discussions')->onDelete('set null');
});
},
'down' => function (Builder $schema) {
$schema->table('tags', function (Blueprint $table) {
$table->dropForeign(['parent_id', 'last_posted_discussion_id', 'last_posted_user_id']);
});
}
];

View File

@ -0,0 +1,14 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
return Migration::renameTable('users_tags', 'tag_user');

View File

@ -0,0 +1,14 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
return Migration::renameColumn('tag_user', 'read_time', 'marked_as_read_at');

View File

@ -0,0 +1,40 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
// Delete rows with non-existent entities so that we will be able to create
// foreign keys without any issues.
$schema->getConnection()
->table('tag_user')
->whereNotExists(function ($query) {
$query->selectRaw(1)->from('tags')->whereColumn('id', 'tag_id');
})
->orWhereNotExists(function ($query) {
$query->selectRaw(1)->from('users')->whereColumn('id', 'user_id');
})
->delete();
$schema->table('tag_user', function (Blueprint $table) {
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
},
'down' => function (Builder $schema) {
$schema->table('tag_user', function (Blueprint $table) {
$table->dropForeign(['tag_id', 'user_id']);
});
}
];

View File

@ -0,0 +1,14 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
return Migration::renameTable('discussions_tags', 'discussion_tag');

View File

@ -0,0 +1,40 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
// Delete rows with non-existent entities so that we will be able to create
// foreign keys without any issues.
$schema->getConnection()
->table('discussion_tag')
->whereNotExists(function ($query) {
$query->selectRaw(1)->from('discussions')->whereColumn('id', 'discussion_id');
})
->orWhereNotExists(function ($query) {
$query->selectRaw(1)->from('tags')->whereColumn('id', 'tag_id');
})
->delete();
$schema->table('discussion_tag', function (Blueprint $table) {
$table->foreign('discussion_id')->references('id')->on('discussions')->onDelete('cascade');
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});
},
'down' => function (Builder $schema) {
$schema->table('discussion_tag', function (Blueprint $table) {
$table->dropForeign(['discussion_id', 'tag_id']);
});
}
];

View File

@ -80,7 +80,7 @@ class DiscussionPolicy extends AbstractPolicy
// Hide discussions which have tags that the user is not allowed to see.
$query->whereNotExists(function ($query) use ($actor) {
return $query->selectRaw('1')
->from('discussions_tags')
->from('discussion_tag')
->whereIn('tag_id', Tag::getIdsWhereCannot($actor, 'viewDiscussions'))
->whereColumn('discussions.id', 'discussion_id');
});
@ -104,7 +104,7 @@ class DiscussionPolicy extends AbstractPolicy
// permission for any of the discussion's tags.
$query->whereExists(function ($query) use ($actor, $ability) {
return $query->selectRaw('1')
->from('discussions_tags')
->from('discussion_tag')
->whereIn('tag_id', Tag::getIdsWhereCan($actor, 'discussion.'.$ability))
->whereColumn('discussions.id', 'discussion_id');
});
@ -120,12 +120,12 @@ class DiscussionPolicy extends AbstractPolicy
*/
public function tag(User $actor, Discussion $discussion)
{
if ($discussion->start_user_id == $actor->id) {
if ($discussion->user_id == $actor->id) {
$allowEditTags = $this->settings->get('allow_tag_change');
if ($allowEditTags === '-1'
|| ($allowEditTags === 'reply' && $discussion->participants_count <= 1)
|| (is_numeric($allowEditTags) && $discussion->start_time->diffInMinutes(new Carbon) < $allowEditTags)
|| ($allowEditTags === 'reply' && $discussion->participant_count <= 1)
|| (is_numeric($allowEditTags) && $discussion->created_at->diffInMinutes(new Carbon) < $allowEditTags)
) {
return true;
}

View File

@ -36,7 +36,7 @@ class FlagPolicy extends AbstractPolicy
->leftJoin('discussions', 'discussions.id', '=', 'posts.discussion_id')
->whereNotExists(function ($query) use ($actor) {
return $query->selectRaw('1')
->from('discussions_tags')
->from('discussion_tag')
->whereIn('tag_id', Tag::getIdsWhereCannot($actor, 'discussion.viewFlags'))
->whereColumn('discussions.id', 'discussion_id');
});

View File

@ -35,7 +35,7 @@ class ListTagsController extends AbstractListController
* {@inheritdoc}
*/
public $optionalInclude = [
'lastDiscussion',
'lastPostedDiscussion',
];
/**

View File

@ -34,12 +34,12 @@ class TagSerializer extends AbstractSerializer
'backgroundUrl' => $tag->background_path,
'backgroundMode' => $tag->background_mode,
'iconUrl' => $tag->icon_path,
'discussionsCount' => (int) $tag->discussions_count,
'discussionCount' => (int) $tag->discussion_count,
'position' => $tag->position === null ? null : (int) $tag->position,
'defaultSort' => $tag->default_sort,
'isChild' => (bool) $tag->parent_id,
'isHidden' => (bool) $tag->is_hidden,
'lastTime' => $this->formatDate($tag->last_time),
'lastPostedAt' => $this->formatDate($tag->last_posted_at),
'canStartDiscussion' => $this->actor->can('startDiscussion', $tag),
'canAddToDiscussion' => $this->actor->can('addToDiscussion', $tag)
];
@ -62,7 +62,7 @@ class TagSerializer extends AbstractSerializer
/**
* @return \Tobscure\JsonApi\Relationship
*/
protected function lastDiscussion($tag)
protected function lastPostedDiscussion($tag)
{
return $this->hasOne($tag, BasicDiscussionSerializer::class);
}

View File

@ -44,10 +44,6 @@ class DeleteTagHandler
$this->assertCan($actor, 'delete', $tag);
$this->tags->query()
->where('parent_id', $tag->id)
->update(['parent_id' => null]);
$tag->delete();
return $tag;

View File

@ -47,7 +47,7 @@ class TagGambit extends AbstractRegexGambit
if ($slug === 'untagged') {
$query->orWhereExists(function ($query) {
$query->selectRaw('1')
->from('discussions_tags')
->from('discussion_tag')
->whereColumn('discussions.id', 'discussion_id');
}, ! $negate);
} else {
@ -55,7 +55,7 @@ class TagGambit extends AbstractRegexGambit
$query->orWhereExists(function ($query) use ($id) {
$query->selectRaw('1')
->from('discussions_tags')
->from('discussion_tag')
->whereColumn('discussions.id', 'discussion_id')
->where('tag_id', $id);
}, $negate);

View File

@ -41,7 +41,7 @@ class AddDiscussionTagsRelationship
public function getModelRelationship(GetModelRelationship $event)
{
if ($event->isRelationship(Discussion::class, 'tags')) {
return $event->model->belongsToMany(Tag::class, 'discussions_tags', null, null, null, null, 'tags');
return $event->model->belongsToMany(Tag::class, 'discussion_tag', null, null, null, null, 'tags');
}
}

View File

@ -70,7 +70,7 @@ class AddForumTagsRelationship
if ($event->isController(ShowForumController::class)) {
$event->data['tags'] = Tag::whereVisibleTo($event->actor)
->withStateFor($event->actor)
->with(['parent', 'lastDiscussion'])
->with(['parent', 'lastPostedDiscussion'])
->get();
}
}
@ -81,7 +81,7 @@ class AddForumTagsRelationship
public function includeTagsRelationship(WillGetData $event)
{
if ($event->isController(ShowForumController::class)) {
$event->addInclude(['tags', 'tags.lastDiscussion', 'tags.parent']);
$event->addInclude(['tags', 'tags.lastPostedDiscussion', 'tags.parent']);
}
}

View File

@ -51,7 +51,7 @@ class FilterDiscussionListByTags
$query->whereNotExists(function ($query) {
return $query->selectRaw('1')
->from('discussions_tags')
->from('discussion_tag')
->whereIn('tag_id', Tag::where('is_hidden', 1)->pluck('id'))
->whereColumn('discussions.id', 'discussion_id');
});

View File

@ -31,8 +31,8 @@ class FilterPostsQueryByTag
{
if ($tagId = array_get($event->filter, 'tag')) {
$event->query
->join('discussions_tags', 'discussions_tags.discussion_id', '=', 'posts.discussion_id')
->where('discussions_tags.tag_id', $tagId);
->join('discussion_tag', 'discussion_tag.discussion_id', '=', 'posts.discussion_id')
->where('discussion_tag.tag_id', $tagId);
}
}
}

View File

@ -120,12 +120,12 @@ class UpdateTagMetadata
}
foreach ($tags as $tag) {
$tag->discussions_count += $delta;
$tag->discussion_count += $delta;
if ($discussion->last_time > $tag->last_time) {
$tag->setLastDiscussion($discussion);
} elseif ($discussion->id == $tag->last_discussion_id) {
$tag->refreshLastDiscussion();
if ($discussion->last_posted_at > $tag->last_posted_at) {
$tag->setLastPostedDiscussion($discussion);
} elseif ($discussion->id == $tag->last_posted_discussion_id) {
$tag->refreshLastPostedDiscussion();
}
$tag->save();

View File

@ -36,7 +36,7 @@ class DiscussionTaggedPost extends AbstractEventPost implements MergeableInterfa
$previous->delete();
} else {
$previous->content = static::buildContent($previous->content[0], $this->content[1]);
$previous->time = $this->time;
$previous->created_at = $this->created_at;
$previous->save();
}
@ -63,7 +63,7 @@ class DiscussionTaggedPost extends AbstractEventPost implements MergeableInterfa
$post = new static;
$post->content = static::buildContent($oldTagIds, $newTagIds);
$post->time = time();
$post->created_at = time();
$post->discussion_id = $discussionId;
$post->user_id = $userId;

View File

@ -30,7 +30,7 @@ class Tag extends AbstractModel
/**
* {@inheritdoc}
*/
protected $dates = ['last_time'];
protected $dates = ['last_posted_at'];
/**
* {@inheritdoc}
@ -40,8 +40,6 @@ class Tag extends AbstractModel
parent::boot();
static::deleted(function ($tag) {
$tag->discussions()->detach();
Permission::where('permission', 'like', "tag{$tag->id}.%")->delete();
});
}
@ -74,15 +72,23 @@ class Tag extends AbstractModel
*/
public function parent()
{
return $this->belongsTo('Flarum\Tags\Tag', 'parent_id');
return $this->belongsTo(self::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function lastDiscussion()
public function lastPostedDiscussion()
{
return $this->belongsTo(Discussion::class, 'last_discussion_id');
return $this->belongsTo(Discussion::class, 'last_posted_discussion_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function lastPostedUser()
{
return $this->belongsTo(User::class, 'last_posted_user_id');
}
/**
@ -90,7 +96,7 @@ class Tag extends AbstractModel
*/
public function discussions()
{
return $this->belongsToMany(Discussion::class, 'discussions_tags');
return $this->belongsToMany(Discussion::class);
}
/**
@ -98,10 +104,10 @@ class Tag extends AbstractModel
*
* @return $this
*/
public function refreshLastDiscussion()
public function refreshLastPostedDiscussion()
{
if ($lastDiscussion = $this->discussions()->latest('last_time')->first()) {
$this->setLastDiscussion($lastDiscussion);
if ($lastPostedDiscussion = $this->discussions()->latest('last_posted_at')->first()) {
$this->setLastPostedDiscussion($lastPostedDiscussion);
}
return $this;
@ -113,10 +119,11 @@ class Tag extends AbstractModel
* @param Discussion $discussion
* @return $this
*/
public function setLastDiscussion(Discussion $discussion)
public function setLastPostedDiscussion(Discussion $discussion)
{
$this->last_time = $discussion->last_time;
$this->last_discussion_id = $discussion->id;
$this->last_posted_at = $discussion->last_posted_at;
$this->last_posted_discussion_id = $discussion->id;
$this->last_posted_user_id = $discussion->last_posted_user_id;
return $this;
}

View File

@ -31,12 +31,12 @@ class TagState extends AbstractModel
/**
* {@inheritdoc}
*/
protected $table = 'users_tags';
protected $table = 'tag_user';
/**
* {@inheritdoc}
*/
protected $dates = ['read_time'];
protected $dates = ['marked_as_read_at'];
/**
* Define the relationship with the tag that this state is for.
@ -45,7 +45,7 @@ class TagState extends AbstractModel
*/
public function tag()
{
return $this->belongsTo(Tag::class, 'tag_id');
return $this->belongsTo(Tag::class);
}
/**
@ -55,7 +55,7 @@ class TagState extends AbstractModel
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id');
return $this->belongsTo(User::class);
}
/**