From 06eabcbc636800c551e4ba602325d227ca463460 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 7 Mar 2018 01:54:56 +0900 Subject: [PATCH 1/7] wip --- src/api/endpoints/othello/sessions/create.ts | 18 ++++++++++ src/api/endpoints/othello/sessions/in.ts | 34 +++++++++++++++++++ src/api/models/othello-game.ts | 33 ++++++++++++++++++ src/api/models/othello-session.ts | 29 ++++++++++++++++ src/web/app/common/views/components/index.ts | 2 ++ .../app/common/views/components/othello.vue | 32 +++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 src/api/endpoints/othello/sessions/create.ts create mode 100644 src/api/endpoints/othello/sessions/in.ts create mode 100644 src/api/models/othello-game.ts create mode 100644 src/api/models/othello-session.ts create mode 100644 src/web/app/common/views/components/othello.vue diff --git a/src/api/endpoints/othello/sessions/create.ts b/src/api/endpoints/othello/sessions/create.ts new file mode 100644 index 0000000000..09c3cff62b --- /dev/null +++ b/src/api/endpoints/othello/sessions/create.ts @@ -0,0 +1,18 @@ +import rndstr from 'rndstr'; +import Session, { pack } from '../../../models/othello-session'; + +module.exports = (params, user) => new Promise(async (res, rej) => { + // 以前のセッションはすべて削除しておく + await Session.remove({ + user_id: user._id + }); + + // セッションを作成 + const session = await Session.insert({ + user_id: user._id, + code: rndstr('a-z0-9', 3) + }); + + // Reponse + res(await pack(session)); +}); diff --git a/src/api/endpoints/othello/sessions/in.ts b/src/api/endpoints/othello/sessions/in.ts new file mode 100644 index 0000000000..d4b95bc4f9 --- /dev/null +++ b/src/api/endpoints/othello/sessions/in.ts @@ -0,0 +1,34 @@ +import $ from 'cafy'; +import Session from '../../../models/othello-session'; +import Game, { pack } from '../../../models/othello-game'; + +module.exports = (params, user) => new Promise(async (res, rej) => { + // Get 'code' parameter + const [code, codeErr] = $(params.code).string().$; + if (codeErr) return rej('invalid code param'); + + // Fetch session + const session = await Session.findOne({ code }); + + if (session == null) { + return rej('session not found'); + } + + // Destroy session + Session.remove({ + _id: session._id + }); + + const parentIsBlack = Math.random() > 0.5; + + // Start game + const game = await Game.insert({ + created_at: new Date(), + black_user_id: parentIsBlack ? session.user_id : user._id, + white_user_id: parentIsBlack ? user._id : session.user_id, + logs: [] + }); + + // Reponse + res(await pack(game)); +}); diff --git a/src/api/models/othello-game.ts b/src/api/models/othello-game.ts new file mode 100644 index 0000000000..a6beaaf9c7 --- /dev/null +++ b/src/api/models/othello-game.ts @@ -0,0 +1,33 @@ +import * as mongo from 'mongodb'; +import deepcopy = require('deepcopy'); +import db from '../../db/mongodb'; + +const Game = db.get('othello_games'); +export default Game; + +export interface IGame { + _id: mongo.ObjectID; + created_at: Date; + black_user_id: mongo.ObjectID; + white_user_id: mongo.ObjectID; + logs: any[]; +} + +/** + * Pack an othello game for API response + * + * @param {any} game + * @return {Promise} + */ +export const pack = ( + game: any +) => new Promise(async (resolve, reject) => { + + const _game = deepcopy(game); + + // Rename _id to id + _game.id = _game._id; + delete _game._id; + + resolve(_game); +}); diff --git a/src/api/models/othello-session.ts b/src/api/models/othello-session.ts new file mode 100644 index 0000000000..0aa1d01e54 --- /dev/null +++ b/src/api/models/othello-session.ts @@ -0,0 +1,29 @@ +import * as mongo from 'mongodb'; +import deepcopy = require('deepcopy'); +import db from '../../db/mongodb'; + +const Session = db.get('othello_sessions'); +export default Session; + +export interface ISession { + _id: mongo.ObjectID; + code: string; + user_id: mongo.ObjectID; +} + +/** + * Pack an othello session for API response + * + * @param {any} session + * @return {Promise} + */ +export const pack = ( + session: any +) => new Promise(async (resolve, reject) => { + + const _session = deepcopy(session); + + delete _session._id; + + resolve(_session); +}); diff --git a/src/web/app/common/views/components/index.ts b/src/web/app/common/views/components/index.ts index 5274920228..98fc2352f2 100644 --- a/src/web/app/common/views/components/index.ts +++ b/src/web/app/common/views/components/index.ts @@ -21,6 +21,7 @@ import urlPreview from './url-preview.vue'; import twitterSetting from './twitter-setting.vue'; import fileTypeIcon from './file-type-icon.vue'; import Switch from './switch.vue'; +import Othello from './othello.vue'; Vue.component('mk-signin', signin); Vue.component('mk-signup', signup); @@ -43,3 +44,4 @@ Vue.component('mk-url-preview', urlPreview); Vue.component('mk-twitter-setting', twitterSetting); Vue.component('mk-file-type-icon', fileTypeIcon); Vue.component('mk-switch', Switch); +Vue.component('mk-othello', Othello); diff --git a/src/web/app/common/views/components/othello.vue b/src/web/app/common/views/components/othello.vue new file mode 100644 index 0000000000..136046db24 --- /dev/null +++ b/src/web/app/common/views/components/othello.vue @@ -0,0 +1,32 @@ + + + + From 6c495268aec2d2fa02ac16dbc119fb9c4e34cdae Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 7 Mar 2018 11:40:40 +0900 Subject: [PATCH 2/7] wip --- src/api/endpoints/othello/match.ts | 80 ++++++++++++++++++ src/api/endpoints/othello/sessions/create.ts | 18 ---- src/api/endpoints/othello/sessions/in.ts | 34 -------- src/api/event.ts | 6 ++ src/api/models/othello-matching.ts | 11 +++ src/api/models/othello-session.ts | 29 ------- src/api/stream/messaging.ts | 2 +- src/api/stream/othello-game.ts | 12 +++ src/api/stream/othello-matching.ts | 12 +++ src/api/stream/requests.ts | 2 +- src/api/stream/server.ts | 2 +- src/api/streaming.ts | 4 + .../app/common/views/components/messaging.vue | 2 +- .../common/views/components/othello.game.vue | 29 +++++++ .../app/common/views/components/othello.vue | 82 ++++++++++++++++--- 15 files changed, 230 insertions(+), 95 deletions(-) create mode 100644 src/api/endpoints/othello/match.ts delete mode 100644 src/api/endpoints/othello/sessions/create.ts delete mode 100644 src/api/endpoints/othello/sessions/in.ts create mode 100644 src/api/models/othello-matching.ts delete mode 100644 src/api/models/othello-session.ts create mode 100644 src/api/stream/othello-game.ts create mode 100644 src/api/stream/othello-matching.ts create mode 100644 src/web/app/common/views/components/othello.game.vue diff --git a/src/api/endpoints/othello/match.ts b/src/api/endpoints/othello/match.ts new file mode 100644 index 0000000000..2dc22d11f9 --- /dev/null +++ b/src/api/endpoints/othello/match.ts @@ -0,0 +1,80 @@ +import $ from 'cafy'; +import Matching from '../../models/othello-matchig'; +import Game, { pack } from '../../models/othello-game'; +import User from '../../models/user'; +import { publishOthelloStream } from '../../event'; + +module.exports = (params, user) => new Promise(async (res, rej) => { + // Get 'user_id' parameter + const [childId, childIdErr] = $(params.user_id).id().$; + if (childIdErr) return rej('invalid user_id param'); + + // Myself + if (childId.equals(user._id)) { + return rej('invalid user_id param'); + } + + // Find session + const exist = await Matching.findOne({ + parent_id: childId, + child_id: user._id + }); + + if (exist) { + // Destroy session + Matching.remove({ + _id: exist._id + }); + + const parentIsBlack = Math.random() > 0.5; + + // Start game + const game = await Game.insert({ + created_at: new Date(), + black_user_id: parentIsBlack ? exist.parent_id : user._id, + white_user_id: parentIsBlack ? user._id : exist.parent_id, + logs: [] + }); + + const packedGame = await pack(game); + + // Reponse + res(packedGame); + + publishOthelloStream(exist.parent_id, 'matched', { + game + }); + } else { + // Fetch child + const child = await User.findOne({ + _id: childId + }, { + fields: { + _id: true + } + }); + + if (child === null) { + return rej('user not found'); + } + + // 以前のセッションはすべて削除しておく + await Matching.remove({ + parent_id: user._id + }); + + // セッションを作成 + await Matching.insert({ + parent_id: user._id, + child_id: child._id + }); + + // Reponse + res(204); + + // 招待 + publishOthelloStream(child._id, 'invited', { + user_id: user._id + }); + } +}); diff --git a/src/api/endpoints/othello/sessions/create.ts b/src/api/endpoints/othello/sessions/create.ts deleted file mode 100644 index 09c3cff62b..0000000000 --- a/src/api/endpoints/othello/sessions/create.ts +++ /dev/null @@ -1,18 +0,0 @@ -import rndstr from 'rndstr'; -import Session, { pack } from '../../../models/othello-session'; - -module.exports = (params, user) => new Promise(async (res, rej) => { - // 以前のセッションはすべて削除しておく - await Session.remove({ - user_id: user._id - }); - - // セッションを作成 - const session = await Session.insert({ - user_id: user._id, - code: rndstr('a-z0-9', 3) - }); - - // Reponse - res(await pack(session)); -}); diff --git a/src/api/endpoints/othello/sessions/in.ts b/src/api/endpoints/othello/sessions/in.ts deleted file mode 100644 index d4b95bc4f9..0000000000 --- a/src/api/endpoints/othello/sessions/in.ts +++ /dev/null @@ -1,34 +0,0 @@ -import $ from 'cafy'; -import Session from '../../../models/othello-session'; -import Game, { pack } from '../../../models/othello-game'; - -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'code' parameter - const [code, codeErr] = $(params.code).string().$; - if (codeErr) return rej('invalid code param'); - - // Fetch session - const session = await Session.findOne({ code }); - - if (session == null) { - return rej('session not found'); - } - - // Destroy session - Session.remove({ - _id: session._id - }); - - const parentIsBlack = Math.random() > 0.5; - - // Start game - const game = await Game.insert({ - created_at: new Date(), - black_user_id: parentIsBlack ? session.user_id : user._id, - white_user_id: parentIsBlack ? user._id : session.user_id, - logs: [] - }); - - // Reponse - res(await pack(game)); -}); diff --git a/src/api/event.ts b/src/api/event.ts index 4a2e4e453d..e68082f0a9 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -38,6 +38,10 @@ class MisskeyEvent { this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value); } + public publishOthelloStream(userId: ID, type: string, value?: any): void { + this.publish(`othello-stream:${userId}`, type, typeof value === 'undefined' ? null : value); + } + public publishChannelStream(channelId: ID, type: string, value?: any): void { this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value); } @@ -65,4 +69,6 @@ export const publishMessagingStream = ev.publishMessagingStream.bind(ev); export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev); +export const publishOthelloStream = ev.publishOthelloStream.bind(ev); + export const publishChannelStream = ev.publishChannelStream.bind(ev); diff --git a/src/api/models/othello-matching.ts b/src/api/models/othello-matching.ts new file mode 100644 index 0000000000..bd7aeef3cf --- /dev/null +++ b/src/api/models/othello-matching.ts @@ -0,0 +1,11 @@ +import * as mongo from 'mongodb'; +import db from '../../db/mongodb'; + +const Matching = db.get('othello_matchings'); +export default Matching; + +export interface IMatching { + _id: mongo.ObjectID; + parent_id: mongo.ObjectID; + child_id: mongo.ObjectID; +} diff --git a/src/api/models/othello-session.ts b/src/api/models/othello-session.ts deleted file mode 100644 index 0aa1d01e54..0000000000 --- a/src/api/models/othello-session.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import db from '../../db/mongodb'; - -const Session = db.get('othello_sessions'); -export default Session; - -export interface ISession { - _id: mongo.ObjectID; - code: string; - user_id: mongo.ObjectID; -} - -/** - * Pack an othello session for API response - * - * @param {any} session - * @return {Promise} - */ -export const pack = ( - session: any -) => new Promise(async (resolve, reject) => { - - const _session = deepcopy(session); - - delete _session._id; - - resolve(_session); -}); diff --git a/src/api/stream/messaging.ts b/src/api/stream/messaging.ts index 3f505cfafa..a4a12426a3 100644 --- a/src/api/stream/messaging.ts +++ b/src/api/stream/messaging.ts @@ -2,7 +2,7 @@ import * as websocket from 'websocket'; import * as redis from 'redis'; import read from '../common/read-messaging-message'; -export default function messagingStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { +export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { const otherparty = request.resourceURL.query.otherparty; // Subscribe messaging stream diff --git a/src/api/stream/othello-game.ts b/src/api/stream/othello-game.ts new file mode 100644 index 0000000000..ab91ef6422 --- /dev/null +++ b/src/api/stream/othello-game.ts @@ -0,0 +1,12 @@ +import * as websocket from 'websocket'; +import * as redis from 'redis'; + +export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void { + const game = request.resourceURL.query.game; + + // Subscribe game stream + subscriber.subscribe(`misskey:othello-game-stream:${game}`); + subscriber.on('message', (_, data) => { + connection.send(data); + }); +} diff --git a/src/api/stream/othello-matching.ts b/src/api/stream/othello-matching.ts new file mode 100644 index 0000000000..f30ce6eb0a --- /dev/null +++ b/src/api/stream/othello-matching.ts @@ -0,0 +1,12 @@ +import * as websocket from 'websocket'; +import * as redis from 'redis'; + +export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { + const otherparty = request.resourceURL.query.otherparty; + + // Subscribe matching stream + subscriber.subscribe(`misskey:othello-matching:${user._id}-${otherparty}`); + subscriber.on('message', (_, data) => { + connection.send(data); + }); +} diff --git a/src/api/stream/requests.ts b/src/api/stream/requests.ts index 2c36e58b6e..d7bb5e6c5c 100644 --- a/src/api/stream/requests.ts +++ b/src/api/stream/requests.ts @@ -3,7 +3,7 @@ import Xev from 'xev'; const ev = new Xev(); -export default function homeStream(request: websocket.request, connection: websocket.connection): void { +export default function(request: websocket.request, connection: websocket.connection): void { const onRequest = request => { connection.send(JSON.stringify({ type: 'request', diff --git a/src/api/stream/server.ts b/src/api/stream/server.ts index 0db6643d40..4ca2ad1b10 100644 --- a/src/api/stream/server.ts +++ b/src/api/stream/server.ts @@ -3,7 +3,7 @@ import Xev from 'xev'; const ev = new Xev(); -export default function homeStream(request: websocket.request, connection: websocket.connection): void { +export default function(request: websocket.request, connection: websocket.connection): void { const onStats = stats => { connection.send(JSON.stringify({ type: 'stats', diff --git a/src/api/streaming.ts b/src/api/streaming.ts index c06d64c245..66c2e0cec0 100644 --- a/src/api/streaming.ts +++ b/src/api/streaming.ts @@ -10,6 +10,8 @@ import homeStream from './stream/home'; import driveStream from './stream/drive'; import messagingStream from './stream/messaging'; import messagingIndexStream from './stream/messaging-index'; +import othelloGameStream from './stream/othello-game'; +import othelloMatchingStream from './stream/othello-matching'; import serverStream from './stream/server'; import requestsStream from './stream/requests'; import channelStream from './stream/channel'; @@ -62,6 +64,8 @@ module.exports = (server: http.Server) => { request.resourceURL.pathname === '/drive' ? driveStream : request.resourceURL.pathname === '/messaging' ? messagingStream : request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream : + request.resourceURL.pathname === '/othello-game' ? othelloGameStream : + request.resourceURL.pathname === '/othello-matching' ? othelloMatchingStream : null; if (channel !== null) { diff --git a/src/web/app/common/views/components/messaging.vue b/src/web/app/common/views/components/messaging.vue index a94a996685..2ec488c247 100644 --- a/src/web/app/common/views/components/messaging.vue +++ b/src/web/app/common/views/components/messaging.vue @@ -89,7 +89,7 @@ export default Vue.extend({ beforeDestroy() { this.connection.off('message', this.onMessage); this.connection.off('read', this.onRead); - (this as any).os.stream.dispose(this.connectionId); + (this as any).streams.messagingIndexStream.dispose(this.connectionId); }, methods: { isMe(message) { diff --git a/src/web/app/common/views/components/othello.game.vue b/src/web/app/common/views/components/othello.game.vue new file mode 100644 index 0000000000..3d3ffb2c07 --- /dev/null +++ b/src/web/app/common/views/components/othello.game.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/web/app/common/views/components/othello.vue b/src/web/app/common/views/components/othello.vue index 136046db24..f5abcfb103 100644 --- a/src/web/app/common/views/components/othello.vue +++ b/src/web/app/common/views/components/othello.vue @@ -1,16 +1,19 @@ @@ -36,11 +37,13 @@ import Vue from 'vue'; import { chUrl } from '../../../config'; import MkMessagingWindow from './messaging-window.vue'; +import MkGameWindow from './game-window.vue'; export default Vue.extend({ data() { return { hasUnreadMessagingMessages: false, + hasGameInvitations: false, connection: null, connectionId: null, chUrl @@ -80,6 +83,10 @@ export default Vue.extend({ messaging() { (this as any).os.new(MkMessagingWindow); + }, + + game() { + (this as any).os.new(MkGameWindow); } } }); diff --git a/src/web/app/desktop/views/widgets/channel.channel.vue b/src/web/app/desktop/views/widgets/channel.channel.vue index 70dac316cf..02cdf6de13 100644 --- a/src/web/app/desktop/views/widgets/channel.channel.vue +++ b/src/web/app/desktop/views/widgets/channel.channel.vue @@ -11,7 +11,7 @@ From a53874edc265f6cd1aab54dbbbfcd996e81aec17 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 7 Mar 2018 18:56:55 +0900 Subject: [PATCH 6/7] wip --- src/api/endpoints/othello/games.ts | 6 +++++- src/api/endpoints/othello/invitations.ts | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/api/endpoints/othello/games.ts b/src/api/endpoints/othello/games.ts index da85de1c1f..39963fcd29 100644 --- a/src/api/endpoints/othello/games.ts +++ b/src/api/endpoints/othello/games.ts @@ -15,7 +15,11 @@ module.exports = (params, user) => new Promise(async (res, rej) => { } : {}; // Fetch games - const games = await Game.find(q); + const games = await Game.find(q, { + sort: { + _id: -1 + } + }); // Reponse res(Promise.all(games.map(async (g) => await pack(g, user)))); diff --git a/src/api/endpoints/othello/invitations.ts b/src/api/endpoints/othello/invitations.ts index f462ef0bf9..02fb421fbc 100644 --- a/src/api/endpoints/othello/invitations.ts +++ b/src/api/endpoints/othello/invitations.ts @@ -4,6 +4,10 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Find session const invitations = await Matching.find({ child_id: user._id + }, { + sort: { + _id: -1 + } }); // Reponse From d6594b6e65f3b5f88e2181489b4e2d2ec6ce70cb Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 7 Mar 2018 18:58:19 +0900 Subject: [PATCH 7/7] oops --- src/web/app/common/views/components/messaging.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/app/common/views/components/messaging.vue b/src/web/app/common/views/components/messaging.vue index 2ec488c247..db60e9259a 100644 --- a/src/web/app/common/views/components/messaging.vue +++ b/src/web/app/common/views/components/messaging.vue @@ -89,7 +89,7 @@ export default Vue.extend({ beforeDestroy() { this.connection.off('message', this.onMessage); this.connection.off('read', this.onRead); - (this as any).streams.messagingIndexStream.dispose(this.connectionId); + (this as any).os.streams.messagingIndexStream.dispose(this.connectionId); }, methods: { isMe(message) {