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/watch',
 | 
			
		||||
		withCredential: true
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		name: 'channels/unwatch',
 | 
			
		||||
		withCredential: true
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		name: 'channels'
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
 */
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import Channel from '../../models/channel';
 | 
			
		||||
import Watching from '../../models/channel-watching';
 | 
			
		||||
import serialize from '../../serializers/channel';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -22,9 +23,17 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
 | 
			
		|||
		created_at: new Date(),
 | 
			
		||||
		user_id: user._id,
 | 
			
		||||
		title: title,
 | 
			
		||||
		index: 0
 | 
			
		||||
		index: 0,
 | 
			
		||||
		watching_count: 1
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Response
 | 
			
		||||
	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 DriveFile from '../../models/drive-file';
 | 
			
		||||
import Watching from '../../models/post-watching';
 | 
			
		||||
import ChannelWatching from '../../models/channel-watching';
 | 
			
		||||
import serialize from '../../serializers/post';
 | 
			
		||||
import notify from '../../common/notify';
 | 
			
		||||
import watch from '../../common/watch-post';
 | 
			
		||||
| 
						 | 
				
			
			@ -249,26 +250,11 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO
 | 
			
		||||
	// タイムラインへの投稿
 | 
			
		||||
	if (!channel) {
 | 
			
		||||
		// Publish event to myself's stream
 | 
			
		||||
		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
 | 
			
		||||
		const followers = await Following
 | 
			
		||||
			.find({
 | 
			
		||||
| 
						 | 
				
			
			@ -285,6 +271,31 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => {
 | 
			
		|||
			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
 | 
			
		||||
	User.update({ _id: user._id }, {
 | 
			
		||||
		$inc: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
 */
 | 
			
		||||
import $ from 'cafy';
 | 
			
		||||
import Post from '../../models/post';
 | 
			
		||||
import ChannelWatching from '../../models/channel-watching';
 | 
			
		||||
import getFriends from '../../common/get-friends';
 | 
			
		||||
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');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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);
 | 
			
		||||
 | 
			
		||||
	// Construct query
 | 
			
		||||
	// Watchしているチャンネルを取得
 | 
			
		||||
	const watches = await ChannelWatching.find({
 | 
			
		||||
		user_id: user._id,
 | 
			
		||||
		// 削除されたドキュメントは除く
 | 
			
		||||
		deleted_at: { $exists: false }
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	//#region Construct query
 | 
			
		||||
	const sort = {
 | 
			
		||||
		_id: -1
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const query = {
 | 
			
		||||
		user_id: {
 | 
			
		||||
			$in: followingIds
 | 
			
		||||
		},
 | 
			
		||||
		// TODO
 | 
			
		||||
		$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;
 | 
			
		||||
 | 
			
		||||
	if (sinceId) {
 | 
			
		||||
		sort._id = 1;
 | 
			
		||||
		query._id = {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +80,7 @@ module.exports = (params, user, app) => new Promise(async (res, rej) => {
 | 
			
		|||
			$lt: maxId
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	// Issue query
 | 
			
		||||
	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 { IUser } from '../models/user';
 | 
			
		||||
import { default as Channel, IChannel } from '../models/channel';
 | 
			
		||||
import Watching from '../models/channel-watching';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialize a channel
 | 
			
		||||
| 
						 | 
				
			
			@ -40,5 +41,26 @@ export default (
 | 
			
		|||
	// Remove needless properties
 | 
			
		||||
	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);
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,11 @@
 | 
			
		|||
	<main if={ !fetching }>
 | 
			
		||||
		<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">
 | 
			
		||||
			<mk-twitter-button/>
 | 
			
		||||
			<mk-line-button/>
 | 
			
		||||
| 
						 | 
				
			
			@ -131,6 +136,28 @@
 | 
			
		|||
				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>
 | 
			
		||||
</mk-channel>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue