From c773753b716bf8d940ff3993b2eea0fde3ec83ba Mon Sep 17 00:00:00 2001 From: Ryohei Ikegami Date: Fri, 22 Jul 2016 19:53:49 +0900 Subject: [PATCH] Use strictNullChecks (#118) * Use strictNullChecks * strictNullChecks in ui --- common/data.ts | 2 +- server/api.ts | 4 +-- server/auth.ts | 6 +++-- server/events.ts | 41 ++++++++++++++++++++--------- server/models.ts | 51 +++++++++++++++++++++++-------------- server/wsserver.ts | 25 +++++++++++------- test/test_events.ts | 4 +-- tsconfig.json | 1 + ui/src/index.tsx | 2 +- ui/src/thread.ts | 8 +++--- ui/src/views/ThreadView.tsx | 8 +++--- ui/tsconfig.json | 1 + 12 files changed, 97 insertions(+), 56 deletions(-) diff --git a/common/data.ts b/common/data.ts index f253f8d..c0d40b1 100644 --- a/common/data.ts +++ b/common/data.ts @@ -9,6 +9,6 @@ interface IMessage { export interface IUser { name: string - iconUrl: string + iconUrl: string|null connecting?: boolean } diff --git a/server/api.ts b/server/api.ts index 473d656..3eec40c 100644 --- a/server/api.ts +++ b/server/api.ts @@ -14,12 +14,12 @@ app.get("/messages", async (req, res) => { order: '"message"."id" DESC', // <- TODO: fix Sequelize where, limit }) - const data = messages.map(m => messageToJSON(m, m.user)).reverse() + const data = messages.map(m => messageToJSON(m, m.user!)).reverse() res.json(data) }) app.get("/user", (req, res) => { - const user: User = req.user + const user: User|undefined = req.user if (user) { const json: IUser = { name: user.name, diff --git a/server/auth.ts b/server/auth.ts index 1bbde52..08d9dd8 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -60,8 +60,10 @@ passport.use(new TwitterStrategy({ user = await User.create({name: profile.username}); integration = await TwitterIntegration.create({twitterId: id, userId: user.id}); } - const iconUrl = profile.photos[0].value - user.iconUrl = iconUrl.replace("_normal.", ".") + if (profile.photos) { + const iconUrl = profile.photos[0].value + user.iconUrl = iconUrl.replace("_normal.", ".") + } await user.save() done(null, user); } catch (error) { diff --git a/server/events.ts b/server/events.ts index 24a15dc..fcfaf67 100644 --- a/server/events.ts +++ b/server/events.ts @@ -10,17 +10,19 @@ export function setConnectionNumber(num: number) { } export -abstract class BaseReceiveEvent { +interface ReceiveEvent { ev: SendEventType - constructor(protected user: IUser, protected value?: string) { - } - abstract prepare(): Promise - abstract response(): Promise + + prepare(): Promise + response(): Promise } export -class WhoamiEvent extends BaseReceiveEvent{ +class WhoamiEvent implements ReceiveEvent { ev = SendEventType.WHOAMI + constructor(public user: IUser) { + } + async prepare() { } async response() { @@ -28,10 +30,13 @@ class WhoamiEvent extends BaseReceiveEvent{ } } export -class CreateMessageEvent extends BaseReceiveEvent{ +class CreateMessageEvent implements ReceiveEvent { ev = SendEventType.NEW_MESSAGE message: Message + constructor(public user: IUser, public value: string) { + } + async prepare() { const user = await User.findOne({ where: { @@ -55,19 +60,27 @@ class CreateMessageEvent extends BaseReceiveEvent{ } } export -class DeleteMessageEvent extends BaseReceiveEvent{ +class DeleteMessageEvent implements ReceiveEvent { ev = SendEventType.DELETE_MESSAGE + + constructor(public id: number) { + } + async prepare() { - await Message.destroy({where: {id: this.value}}) + await Message.destroy({where: {id: this.id}}) } async response() { await this.prepare() - return newMessage(this.ev, this.value) + return newMessage(this.ev, this.id) } } export -class JoinEvent extends BaseReceiveEvent { +class JoinEvent implements ReceiveEvent { ev = SendEventType.USER_JOIN + + constructor(public user: IUser) { + } + async prepare() { } async response() { @@ -80,8 +93,12 @@ class JoinEvent extends BaseReceiveEvent { } } export -class LeftEvent extends BaseReceiveEvent { +class LeftEvent implements ReceiveEvent { ev = SendEventType.USER_LEAVE + + constructor(public user: IUser) { + } + async prepare() { } async response() { diff --git a/server/models.ts b/server/models.ts index e7a5459..d5f12a6 100644 --- a/server/models.ts +++ b/server/models.ts @@ -5,13 +5,17 @@ const config = require("../../config/db")[process.env.NODE_ENV || "development"] export let sequelize = new Sequelize(config.url) interface MessageParams { - text?: string; - userId?: number; - id?: number; + text?: string + userId?: number + id?: number createdAt?: Date } -export interface Message extends Sequelize.Instance, MessageParams { - user?: User; +export interface Message extends Sequelize.Instance { + user?: User + text: string + userId: number + id: number + createdAt: Date } export let Message = sequelize.define('message', { @@ -35,10 +39,13 @@ interface UserParams { id?: number iconUrl?: string } -export interface User extends Sequelize.Instance, UserParams { - messages?: Message[]; - connections?: Connection[]; - twitterIntegration?: TwitterIntegration; +export interface User extends Sequelize.Instance { + messages?: Message[] + connections?: Connection[] + twitterIntegration?: TwitterIntegration + name: string + id: number + iconUrl: string|null } export let User = sequelize.define('user', { @@ -47,12 +54,15 @@ export let User = sequelize.define('user', { }) interface ConnectionParams { - available?: boolean; - userId?: number; - id?: number; + available?: boolean + userId?: number + id?: number } -export interface Connection extends Sequelize.Instance, ConnectionParams { - user?: User; +export interface Connection extends Sequelize.Instance { + user?: User + available: boolean + userId: number + id: number } export let Connection = sequelize.define('connection', { @@ -60,12 +70,15 @@ export let Connection = sequelize.define('connecti }) interface TwitterIntegrationParams { - twitterId?: string; - userId?: number; - id?: number; + twitterId?: string + userId?: number + id?: number } -export interface TwitterIntegration extends Sequelize.Instance, TwitterIntegrationParams { - user?: User; +export interface TwitterIntegration extends Sequelize.Instance { + user?: User + twitterId: string + userId: number + id: number } export let TwitterIntegration = sequelize.define('twitterIntegration', { diff --git a/server/wsserver.ts b/server/wsserver.ts index 6a04c82..44fc50a 100644 --- a/server/wsserver.ts +++ b/server/wsserver.ts @@ -5,7 +5,7 @@ import { Message, User, Connection } from "./models" import { IMessage, IUser } from "../common/data"; import { app, server } from "./app"; import { ReceiveEventType, SendEventType } from "../common/eventType" -import { newMessage, WhoamiEvent, BaseReceiveEvent, JoinEvent, CreateMessageEvent, DeleteMessageEvent, LeftEvent } from "./events" +import { newMessage, WhoamiEvent, ReceiveEvent, JoinEvent, CreateMessageEvent, DeleteMessageEvent, LeftEvent } from "./events" const expressWs = require('express-ws')(app, server); const wss: WebSocket.Server = expressWs.getWss(); @@ -22,9 +22,9 @@ const broadcast = (message: string): void => { } app["ws"]("/", async (ws: WebSocket, req: express.Request) => { - let user: User = req.user - let userData: IUser - let connection: Connection + let user: User|undefined = req.user + let userData: IUser|undefined + let connection: Connection|undefined // validate connection if(!req.headers["origin"]) { ws.close() @@ -92,9 +92,9 @@ app["ws"]("/", async (ws: WebSocket, req: express.Request) => { try { let json = JSON.parse(undecoded_json) let {ev, value} = json - let messageEvent: BaseReceiveEvent + let messageEvent: ReceiveEvent|undefined; - if(user) { + if (userData) { if (ev === ReceiveEventType[ReceiveEventType.CREATE_MESSAGE]) { if (messageCount < messageLimitPerHour) { messageEvent = new CreateMessageEvent(userData, value) @@ -107,19 +107,26 @@ app["ws"]("/", async (ws: WebSocket, req: express.Request) => { ping_available = true } } - broadcast(await messageEvent.response()) + if (messageEvent) { + const res = await messageEvent.response(); + if (res) { + broadcast(res); + } + } } catch(e) { // failed } }) let onClose = async () => { - if(user) { + if (userData) { let event = new LeftEvent(userData) try { broadcast(await event.response()) // destroy connection - await connection.destroy() + if (connection) { + await connection.destroy() + } } catch(e) { // failed } diff --git a/test/test_events.ts b/test/test_events.ts index 1438eca..cefdb83 100644 --- a/test/test_events.ts +++ b/test/test_events.ts @@ -60,7 +60,7 @@ describe("events", () => { it("returns correct message", async () => { const val = await event.response() - let parsed = JSON.parse(val) + let parsed = JSON.parse(val!) const expected: IMessage = { id: event.message.id, text: message, @@ -74,7 +74,7 @@ describe("events", () => { }) }) describe("DeleteMessageEvent", () => { - let event = new events.DeleteMessageEvent(user, "1") + let event = new events.DeleteMessageEvent(1) // override prepare methods for testing let prepare_called: boolean event.prepare = async () => { prepare_called = true } diff --git a/tsconfig.json b/tsconfig.json index 2fe1932..b4c2754 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "target": "es6", "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, + "strictNullChecks": true, "sourceMap": true, "outDir": "./lib" }, diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 3c7df22..b153076 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -8,7 +8,7 @@ import * as ReactDOM from "react-dom"; import ThreadView from "./views/ThreadView"; window.addEventListener("DOMContentLoaded", () => { - ReactDOM.render(, document.getElementById("app")); + ReactDOM.render(, document.getElementById("app")!); }); const {GA_TRACKING_ID} = process.env diff --git a/ui/src/thread.ts b/ui/src/thread.ts index 88c9316..73eb24e 100644 --- a/ui/src/thread.ts +++ b/ui/src/thread.ts @@ -14,10 +14,10 @@ export class Thread extends EventEmitter { connetion = new ReconnectingWebSocket(WS_URL); messages: IMessage[] = []; - latestMessage: IMessage = null; + latestMessage: IMessage|undefined connectionCount = 0; availableUsers: IUser[] = []; - currentUser: IUser = null; + currentUser: IUser|undefined hasOlderMessages = true fetchingOlderMessages = false @@ -69,7 +69,7 @@ class Thread extends EventEmitter { const response = await fetch(`${API_URL}/messages?limit=${MESSAGE_PER_PAGE}`); const messages: IMessage[] = await response.json(); this.messages = messages; - this.latestMessage = null; + this.latestMessage = undefined this.emit("messageAppend"); } @@ -91,7 +91,7 @@ class Thread extends EventEmitter { this.hasOlderMessages = false } else { this.messages.unshift(...messages) - this.latestMessage = null + this.latestMessage = undefined this.emit("messagePrepend") } this.fetchingOlderMessages = false diff --git a/ui/src/views/ThreadView.tsx b/ui/src/views/ThreadView.tsx index 0f9f61f..97e5bc1 100644 --- a/ui/src/views/ThreadView.tsx +++ b/ui/src/views/ThreadView.tsx @@ -29,7 +29,7 @@ class UserList extends React.Component<{}, UserListState> { render() { const MAX_USERS = 10 - const {users} = this.state; + const users = this.state.users!; return (
    @@ -50,7 +50,7 @@ class UserView extends React.Component<{}, UserLoginState> { constructor() { super(); this.state = { - user: null, + user: undefined, loggedOut: false }; auth.on("change", () => { @@ -115,7 +115,7 @@ class MessageForm extends React.Component<{}, UserLoginState> { constructor() { super() this.state = { - user: null, + user: undefined, loggedOut: true } auth.on("change", () => { @@ -231,7 +231,7 @@ class ThreadView extends React.Component<{}, ThreadViewState> { } render() { - const {messages} = this.state; + const messages = this.state.messages!; return (
    diff --git a/ui/tsconfig.json b/ui/tsconfig.json index f88c40f..2df574a 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -4,6 +4,7 @@ "target": "es6", "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, + "strictNullChecks": true, "sourceMap": false, "jsx": "react", "outDir": "./lib",