Implement Channel Watching
This commit is contained in:
parent
2b3937d731
commit
d6b03c43eb
9 changed files with 244 additions and 27 deletions
|
@ -490,6 +490,14 @@ const endpoints: Endpoint[] = [
|
||||||
{
|
{
|
||||||
name: 'channels/posts'
|
name: 'channels/posts'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'channels/watch',
|
||||||
|
withCredential: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'channels/unwatch',
|
||||||
|
withCredential: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'channels'
|
name: 'channels'
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import Channel from '../../models/channel';
|
import Channel from '../../models/channel';
|
||||||
|
import Watching from '../../models/channel-watching';
|
||||||
import serialize from '../../serializers/channel';
|
import serialize from '../../serializers/channel';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,9 +23,17 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
title: title,
|
title: title,
|
||||||
index: 0
|
index: 0,
|
||||||
|
watching_count: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
// Response
|
// Response
|
||||||
res(await serialize(channel));
|
res(await serialize(channel));
|
||||||
|
|
||||||
|
// Create Watching
|
||||||
|
await Watching.insert({
|
||||||
|
created_at: new Date(),
|
||||||
|
user_id: user._id,
|
||||||
|
channel_id: channel._id
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
60
src/api/endpoints/channels/unwatch.ts
Normal file
60
src/api/endpoints/channels/unwatch.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Module dependencies
|
||||||
|
*/
|
||||||
|
import $ from 'cafy';
|
||||||
|
import Channel from '../../models/channel';
|
||||||
|
import Watching from '../../models/channel-watching';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwatch a channel
|
||||||
|
*
|
||||||
|
* @param {any} params
|
||||||
|
* @param {any} user
|
||||||
|
* @return {Promise<any>}
|
||||||
|
*/
|
||||||
|
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
// Get 'channel_id' parameter
|
||||||
|
const [channelId, channelIdErr] = $(params.channel_id).id().$;
|
||||||
|
if (channelIdErr) return rej('invalid channel_id param');
|
||||||
|
|
||||||
|
//#region Fetch channel
|
||||||
|
const channel = await Channel.findOne({
|
||||||
|
_id: channelId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (channel === null) {
|
||||||
|
return rej('channel not found');
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Check whether not watching
|
||||||
|
const exist = await Watching.findOne({
|
||||||
|
user_id: user._id,
|
||||||
|
channel_id: channel._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist === null) {
|
||||||
|
return rej('already not watching');
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// Delete watching
|
||||||
|
await Watching.update({
|
||||||
|
_id: exist._id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
deleted_at: new Date()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
res();
|
||||||
|
|
||||||
|
// Decrement watching count
|
||||||
|
Channel.update(channel._id, {
|
||||||
|
$inc: {
|
||||||
|
watching_count: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
58
src/api/endpoints/channels/watch.ts
Normal file
58
src/api/endpoints/channels/watch.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Module dependencies
|
||||||
|
*/
|
||||||
|
import $ from 'cafy';
|
||||||
|
import Channel from '../../models/channel';
|
||||||
|
import Watching from '../../models/channel-watching';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch a channel
|
||||||
|
*
|
||||||
|
* @param {any} params
|
||||||
|
* @param {any} user
|
||||||
|
* @return {Promise<any>}
|
||||||
|
*/
|
||||||
|
module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
// Get 'channel_id' parameter
|
||||||
|
const [channelId, channelIdErr] = $(params.channel_id).id().$;
|
||||||
|
if (channelIdErr) return rej('invalid channel_id param');
|
||||||
|
|
||||||
|
//#region Fetch channel
|
||||||
|
const channel = await Channel.findOne({
|
||||||
|
_id: channelId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (channel === null) {
|
||||||
|
return rej('channel not found');
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Check whether already watching
|
||||||
|
const exist = await Watching.findOne({
|
||||||
|
user_id: user._id,
|
||||||
|
channel_id: channel._id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exist !== null) {
|
||||||
|
return rej('already watching');
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// Create Watching
|
||||||
|
await Watching.insert({
|
||||||
|
created_at: new Date(),
|
||||||
|
user_id: user._id,
|
||||||
|
channel_id: channel._id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
res();
|
||||||
|
|
||||||
|
// Increment watching count
|
||||||
|
Channel.update(channel._id, {
|
||||||
|
$inc: {
|
||||||
|
watching_count: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,6 +10,7 @@ import { default as Channel, IChannel } from '../../models/channel';
|
||||||
import Following from '../../models/following';
|
import Following from '../../models/following';
|
||||||
import DriveFile from '../../models/drive-file';
|
import DriveFile from '../../models/drive-file';
|
||||||
import Watching from '../../models/post-watching';
|
import Watching from '../../models/post-watching';
|
||||||
|
import ChannelWatching from '../../models/channel-watching';
|
||||||
import serialize from '../../serializers/post';
|
import serialize from '../../serializers/post';
|
||||||
import notify from '../../common/notify';
|
import notify from '../../common/notify';
|
||||||
import watch from '../../common/watch-post';
|
import watch from '../../common/watch-post';
|
||||||
|
@ -249,26 +250,11 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// タイムラインへの投稿
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
// Publish event to myself's stream
|
// Publish event to myself's stream
|
||||||
event(user._id, 'post', postObj);
|
event(user._id, 'post', postObj);
|
||||||
}
|
|
||||||
|
|
||||||
if (channel) {
|
|
||||||
// Increment channel index(posts count)
|
|
||||||
Channel.update({ _id: channel._id }, {
|
|
||||||
$inc: {
|
|
||||||
index: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Publish event to channel
|
|
||||||
publishChannelStream(channel._id, 'post', postObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
if (!channel) {
|
|
||||||
// Fetch all followers
|
// Fetch all followers
|
||||||
const followers = await Following
|
const followers = await Following
|
||||||
.find({
|
.find({
|
||||||
|
@ -285,6 +271,31 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
|
||||||
event(following.follower_id, 'post', postObj));
|
event(following.follower_id, 'post', postObj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// チャンネルへの投稿
|
||||||
|
if (channel) {
|
||||||
|
// Increment channel index(posts count)
|
||||||
|
Channel.update({ _id: channel._id }, {
|
||||||
|
$inc: {
|
||||||
|
index: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Publish event to channel
|
||||||
|
publishChannelStream(channel._id, 'post', postObj);
|
||||||
|
|
||||||
|
// Get channel watchers
|
||||||
|
const watches = await ChannelWatching.find({
|
||||||
|
channel_id: channel._id,
|
||||||
|
// 削除されたドキュメントは除く
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
// チャンネルの視聴者(のタイムライン)に配信
|
||||||
|
watches.forEach(w => {
|
||||||
|
event(w.user_id, 'post', postObj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Increment my posts count
|
// Increment my posts count
|
||||||
User.update({ _id: user._id }, {
|
User.update({ _id: user._id }, {
|
||||||
$inc: {
|
$inc: {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import Post from '../../models/post';
|
import Post from '../../models/post';
|
||||||
|
import ChannelWatching from '../../models/channel-watching';
|
||||||
import getFriends from '../../common/get-friends';
|
import getFriends from '../../common/get-friends';
|
||||||
import serialize from '../../serializers/post';
|
import serialize from '../../serializers/post';
|
||||||
|
|
||||||
|
@ -32,26 +33,43 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => {
|
||||||
return rej('cannot set since_id and max_id');
|
return rej('cannot set since_id and max_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID list of the user $self and other users who the user follows
|
// ID list of the user itself and other users who the user follows
|
||||||
const followingIds = await getFriends(user._id);
|
const followingIds = await getFriends(user._id);
|
||||||
|
|
||||||
// Construct query
|
// Watchしているチャンネルを取得
|
||||||
|
const watches = await ChannelWatching.find({
|
||||||
|
user_id: user._id,
|
||||||
|
// 削除されたドキュメントは除く
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
//#region Construct query
|
||||||
const sort = {
|
const sort = {
|
||||||
_id: -1
|
_id: -1
|
||||||
};
|
};
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
user_id: {
|
|
||||||
$in: followingIds
|
|
||||||
},
|
|
||||||
// TODO
|
|
||||||
$or: [{
|
$or: [{
|
||||||
channel_id: {
|
// フォローしている人のタイムラインへの投稿
|
||||||
$exists: false
|
user_id: {
|
||||||
}
|
$in: followingIds
|
||||||
|
},
|
||||||
|
// 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る
|
||||||
|
$or: [{
|
||||||
|
channel_id: {
|
||||||
|
$exists: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
channel_id: null
|
||||||
|
}]
|
||||||
}, {
|
}, {
|
||||||
channel_id: null
|
// Watchしているチャンネルへの投稿
|
||||||
|
channel_id: {
|
||||||
|
$in: watches.map(w => w.channel_id)
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
if (sinceId) {
|
if (sinceId) {
|
||||||
sort._id = 1;
|
sort._id = 1;
|
||||||
query._id = {
|
query._id = {
|
||||||
|
@ -62,6 +80,7 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => {
|
||||||
$lt: maxId
|
$lt: maxId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
// Issue query
|
// Issue query
|
||||||
const timeline = await Post
|
const timeline = await Post
|
||||||
|
|
3
src/api/models/channel-watching.ts
Normal file
3
src/api/models/channel-watching.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import db from '../../db/mongodb';
|
||||||
|
|
||||||
|
export default db.get('channel_watching') as any; // fuck type definition
|
|
@ -5,6 +5,7 @@ import * as mongo from 'mongodb';
|
||||||
import deepcopy = require('deepcopy');
|
import deepcopy = require('deepcopy');
|
||||||
import { IUser } from '../models/user';
|
import { IUser } from '../models/user';
|
||||||
import { default as Channel, IChannel } from '../models/channel';
|
import { default as Channel, IChannel } from '../models/channel';
|
||||||
|
import Watching from '../models/channel-watching';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize a channel
|
* Serialize a channel
|
||||||
|
@ -40,5 +41,26 @@ export default (
|
||||||
// Remove needless properties
|
// Remove needless properties
|
||||||
delete _channel.user_id;
|
delete _channel.user_id;
|
||||||
|
|
||||||
|
// Me
|
||||||
|
const meId: mongo.ObjectID = me
|
||||||
|
? mongo.ObjectID.prototype.isPrototypeOf(me)
|
||||||
|
? me as mongo.ObjectID
|
||||||
|
: typeof me === 'string'
|
||||||
|
? new mongo.ObjectID(me)
|
||||||
|
: (me as IUser)._id
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (me) {
|
||||||
|
//#region Watchしているかどうか
|
||||||
|
const watch = await Watching.findOne({
|
||||||
|
user_id: meId,
|
||||||
|
channel_id: _channel.id,
|
||||||
|
deleted_at: { $exists: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
_channel.is_watching = watch !== null;
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
|
|
||||||
resolve(_channel);
|
resolve(_channel);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
<main if={ !fetching }>
|
<main if={ !fetching }>
|
||||||
<h1>{ channel.title }</h1>
|
<h1>{ channel.title }</h1>
|
||||||
|
|
||||||
|
<div if={ SIGNIN }>
|
||||||
|
<p if={ channel.is_watching }>このチャンネルをウォッチしています <a onclick={ unwatch }>ウォッチ解除</a></p>
|
||||||
|
<p if={ !channel.is_watching }><a onclick={ watch }>このチャンネルをウォッチする</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="share">
|
<div class="share">
|
||||||
<mk-twitter-button/>
|
<mk-twitter-button/>
|
||||||
<mk-line-button/>
|
<mk-line-button/>
|
||||||
|
@ -131,6 +136,28 @@
|
||||||
document.title = this.channel.title + ' | Misskey'
|
document.title = this.channel.title + ' | Misskey'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.watch = () => {
|
||||||
|
this.api('channels/watch', {
|
||||||
|
channel_id: this.id
|
||||||
|
}).then(() => {
|
||||||
|
this.channel.is_watching = true;
|
||||||
|
this.update();
|
||||||
|
}, e => {
|
||||||
|
alert('error');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unwatch = () => {
|
||||||
|
this.api('channels/unwatch', {
|
||||||
|
channel_id: this.id
|
||||||
|
}).then(() => {
|
||||||
|
this.channel.is_watching = false;
|
||||||
|
this.update();
|
||||||
|
}, e => {
|
||||||
|
alert('error');
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</mk-channel>
|
</mk-channel>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue