mirror of
https://github.com/flarum/core.git
synced 2025-07-20 08:11:27 +02:00
Radically simplify user activity system
The activity system we were using was built around a separate table. Whenever the user posted something, or deleted a post, we would sync the table. The advantage of this was that we could aggregate activity of all different types very efficiently. It turns out that it came with a huge disadvantage: there was no efficient way to enforce permissions on activity. If a user posted something in a private tag, everyone could still see it on their activity feed. My stopgap solution was to only sync activity for posts that are viewable by guests, but that was way too limited. It also turns out that aggregating activity of different types is really not that useful, especially considering most of it is the user making posts. So I've gotten rid of that whole overly-complicated system, and just made the user profile display separate lists of posts and discussions, retrieved from those respective APIs. The discussions page is an actual discussion list too, which is pretty cool. It's still technically possible to aggregate different activity types (basically just aggregate API responses together), but we can do that later if there's a need for it. This is probably my favourite commit of the day :)
This commit is contained in:
@@ -22,7 +22,7 @@ export default class Activity extends Component {
|
||||
|
||||
<div className="Activity-header">
|
||||
<strong className="Activity-description">{this.description()}</strong>
|
||||
{humanTime(activity.time())}
|
||||
{humanTime(this.time())}
|
||||
</div>
|
||||
|
||||
{this.content()}
|
||||
@@ -34,9 +34,18 @@ export default class Activity extends Component {
|
||||
* Get the user whose avatar should be displayed.
|
||||
*
|
||||
* @return {User}
|
||||
* @abstract
|
||||
*/
|
||||
user() {
|
||||
return this.props.activity.user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of the activity.
|
||||
*
|
||||
* @return {Date}
|
||||
* @abstract
|
||||
*/
|
||||
time() {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import Activity from 'flarum/components/Activity';
|
||||
|
||||
/**
|
||||
* The `JoinedActivity` component displays an activity feed item for when a user
|
||||
* joined the forum.
|
||||
*/
|
||||
export default class JoinedActivity extends Activity {
|
||||
description() {
|
||||
return app.trans('core.joined_the_forum');
|
||||
}
|
||||
}
|
@@ -12,14 +12,20 @@ import { truncate } from 'flarum/utils/string';
|
||||
* - All of the props for Activity
|
||||
*/
|
||||
export default class PostedActivity extends Activity {
|
||||
description() {
|
||||
const post = this.props.activity.subject();
|
||||
user() {
|
||||
return this.props.post.user();
|
||||
}
|
||||
|
||||
return app.trans(post.number() === 1 ? 'core.started_a_discussion' : 'core.posted_a_reply');
|
||||
time() {
|
||||
return this.props.post.time();
|
||||
}
|
||||
|
||||
description() {
|
||||
return app.trans(this.props.post.number() === 1 ? 'core.started_a_discussion' : 'core.posted_a_reply');
|
||||
}
|
||||
|
||||
content() {
|
||||
const post = this.props.activity.subject();
|
||||
const post = this.props.post;
|
||||
|
||||
return (
|
||||
<a className="Activity-content PostedActivity-preview"
|
||||
@@ -43,7 +49,7 @@ export default class PostedActivity extends Activity {
|
||||
headerItems() {
|
||||
const items = new ItemList();
|
||||
|
||||
items.add('title', <h3>{this.props.activity.subject().discussion().title()}</h3>);
|
||||
items.add('title', <h3>{this.props.post.discussion().title()}</h3>);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
@@ -0,0 +1,26 @@
|
||||
import UserPage from 'flarum/components/UserPage';
|
||||
import DiscussionList from 'flarum/components/DiscussionList';
|
||||
|
||||
/**
|
||||
* The `UserDiscussionsPage` component shows a user's activity feed inside of their
|
||||
* profile.
|
||||
*/
|
||||
export default class UserDiscussionsPage extends UserPage {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.loadUser(m.route.param('username'));
|
||||
}
|
||||
|
||||
content() {
|
||||
return (
|
||||
<div className="UserPostsPage">
|
||||
{DiscussionList.component({
|
||||
params: {
|
||||
q: 'author:' + this.user.username()
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@@ -134,11 +134,11 @@ export default class UserPage extends Component {
|
||||
const items = new ItemList();
|
||||
const user = this.user;
|
||||
|
||||
items.add('activity',
|
||||
items.add('posts',
|
||||
LinkButton.component({
|
||||
href: app.route('user.activity', {username: user.username()}),
|
||||
children: app.trans('core.activity'),
|
||||
icon: 'user'
|
||||
href: app.route('user.posts', {username: user.username()}),
|
||||
children: [app.trans('core.posts'), <span className="Button-badge">{user.commentsCount()}</span>],
|
||||
icon: 'comment-o'
|
||||
})
|
||||
);
|
||||
|
||||
@@ -150,14 +150,6 @@ export default class UserPage extends Component {
|
||||
})
|
||||
);
|
||||
|
||||
items.add('posts',
|
||||
LinkButton.component({
|
||||
href: app.route('user.posts', {username: user.username()}),
|
||||
children: [app.trans('core.posts'), <span className="Button-badge">{user.commentsCount()}</span>],
|
||||
icon: 'comment-o'
|
||||
})
|
||||
);
|
||||
|
||||
if (app.session.user === user) {
|
||||
items.add('separator', Separator.component());
|
||||
items.add('settings',
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import UserPage from 'flarum/components/UserPage';
|
||||
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||
import Button from 'flarum/components/Button';
|
||||
import PostedActivity from 'flarum/components/PostedActivity';
|
||||
|
||||
/**
|
||||
* The `ActivityPage` component shows a user's activity feed inside of their
|
||||
* The `UserPostsPage` component shows a user's activity feed inside of their
|
||||
* profile.
|
||||
*/
|
||||
export default class ActivityPage extends UserPage {
|
||||
export default class UserPostsPage extends UserPage {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
@@ -25,10 +26,11 @@ export default class ActivityPage extends UserPage {
|
||||
this.moreResults = false;
|
||||
|
||||
/**
|
||||
* The Activity models in the feed.
|
||||
* @type {Activity[]}
|
||||
* The Post models in the feed.
|
||||
*
|
||||
* @type {Post[]}
|
||||
*/
|
||||
this.activity = [];
|
||||
this.posts = [];
|
||||
|
||||
/**
|
||||
* The number of activity items to load per request.
|
||||
@@ -47,7 +49,7 @@ export default class ActivityPage extends UserPage {
|
||||
footer = LoadingIndicator.component();
|
||||
} else if (this.moreResults) {
|
||||
footer = (
|
||||
<div className="ActivityPage-loadMore">
|
||||
<div className="UserPostsPage-loadMore">
|
||||
{Button.component({
|
||||
children: app.trans('core.load_more'),
|
||||
className: 'Button',
|
||||
@@ -58,11 +60,10 @@ export default class ActivityPage extends UserPage {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ActivityPage">
|
||||
<ul className="ActivityPage-list">
|
||||
{this.activity.map(activity => {
|
||||
const ActivityComponent = app.activityComponents[activity.contentType()];
|
||||
return ActivityComponent ? <li>{ActivityComponent.component({activity})}</li> : '';
|
||||
<div className="UserPostsPage">
|
||||
<ul className="UserPostsPage-list ActivityList">
|
||||
{this.posts.map(post => {
|
||||
return <li>{PostedActivity.component({post})}</li>;
|
||||
})}
|
||||
</ul>
|
||||
{footer}
|
||||
@@ -87,7 +88,7 @@ export default class ActivityPage extends UserPage {
|
||||
*/
|
||||
refresh() {
|
||||
this.loading = true;
|
||||
this.activity = [];
|
||||
this.posts = [];
|
||||
|
||||
// Redraw, but only if we're not in the middle of a route change.
|
||||
m.startComputation();
|
||||
@@ -104,12 +105,13 @@ export default class ActivityPage extends UserPage {
|
||||
* @protected
|
||||
*/
|
||||
loadResults(offset) {
|
||||
return app.store.find('activity', {
|
||||
return app.store.find('posts', {
|
||||
filter: {
|
||||
user: this.user.id(),
|
||||
type: this.props.filter
|
||||
type: 'comment'
|
||||
},
|
||||
page: {offset, limit: this.loadLimit}
|
||||
page: {offset, limit: this.loadLimit},
|
||||
sort: '-time'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,19 +122,19 @@ export default class ActivityPage extends UserPage {
|
||||
*/
|
||||
loadMore() {
|
||||
this.loading = true;
|
||||
this.loadResults(this.activity.length).then(this.parseResults.bind(this));
|
||||
this.loadResults(this.posts.length).then(this.parseResults.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse results and append them to the activity feed.
|
||||
*
|
||||
* @param {Activity[]} results
|
||||
* @return {Activity[]}
|
||||
* @param {Post[]} results
|
||||
* @return {Post[]}
|
||||
*/
|
||||
parseResults(results) {
|
||||
this.loading = false;
|
||||
|
||||
[].push.apply(this.activity, results);
|
||||
[].push.apply(this.posts, results);
|
||||
|
||||
this.moreResults = results.length >= this.loadLimit;
|
||||
m.redraw();
|
@@ -14,9 +14,5 @@ export default function components(app) {
|
||||
app.postComponents.comment = CommentPost;
|
||||
app.postComponents.discussionRenamed = DiscussionRenamedPost;
|
||||
|
||||
app.activityComponents.posted = PostedActivity;
|
||||
app.activityComponents.startedDiscussion = PostedActivity;
|
||||
app.activityComponents.joined = JoinedActivity;
|
||||
|
||||
app.notificationComponents.discussionRenamed = DiscussionRenamedNotification;
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import IndexPage from 'flarum/components/IndexPage';
|
||||
import DiscussionPage from 'flarum/components/DiscussionPage';
|
||||
import ActivityPage from 'flarum/components/ActivityPage';
|
||||
import UserPostsPage from 'flarum/components/UserPostsPage';
|
||||
import UserDiscussionsPage from 'flarum/components/UserDiscussionsPage';
|
||||
import SettingsPage from 'flarum/components/SettingsPage';
|
||||
import NotificationsPage from 'flarum/components/NotificationsPage';
|
||||
|
||||
@@ -18,10 +19,9 @@ export default function(app) {
|
||||
'discussion': {path: '/d/:id/:slug', component: DiscussionPage.component()},
|
||||
'discussion.near': {path: '/d/:id/:slug/:near', component: DiscussionPage.component()},
|
||||
|
||||
'user': {path: '/u/:username', component: ActivityPage.component()},
|
||||
'user.activity': {path: '/u/:username', component: ActivityPage.component()},
|
||||
'user.discussions': {path: '/u/:username/discussions', component: ActivityPage.component({filter: 'startedDiscussion'})},
|
||||
'user.posts': {path: '/u/:username/posts', component: ActivityPage.component({filter: 'posted'})},
|
||||
'user': {path: '/u/:username', component: UserPostsPage.component()},
|
||||
'user.posts': {path: '/u/:username', component: UserPostsPage.component()},
|
||||
'user.discussions': {path: '/u/:username/discussions', component: UserDiscussionsPage.component()},
|
||||
|
||||
'settings': {path: '/settings', component: SettingsPage.component()},
|
||||
'notifications': {path: '/notifications', component: NotificationsPage.component()}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import Model from 'flarum/Model';
|
||||
import mixin from 'flarum/utils/mixin';
|
||||
|
||||
export default class Activity extends mixin(Model, {
|
||||
contentType: Model.attribute('contentType'),
|
||||
content: Model.attribute('content'),
|
||||
time: Model.attribute('time', Model.transformDate),
|
||||
|
||||
user: Model.hasOne('user'),
|
||||
subject: Model.hasOne('subject')
|
||||
}) {}
|
Reference in New Issue
Block a user