linting + initial mutation

- added tslint
- mutation support
This commit is contained in:
Wyatt Johnson
2018-06-25 14:32:01 -06:00
parent 6f80a2458c
commit fa57c72842
43 changed files with 807 additions and 405 deletions
+2 -1
View File
@@ -10,5 +10,6 @@
"dist": true,
".vscode": true,
"package-lock.json": true
}
},
"tslint.autoFixOnSave": true
}
+9
View File
@@ -21,3 +21,12 @@
1. No tenants
2. Create a tenant <-- consuming the TMA
## Database connections
### Redis Clients
1. Tenant RedisPubSub Subscriber
2. Tenant RedisPubSub Publisher
3. Management RedisPubSub Subscriber
4. Management RedisPubSub Publisher
+261 -1
View File
@@ -148,6 +148,15 @@
"integrity": "sha512-IsX9aDHDzJohkm3VCDB8tkzl5RQ34E/PFA29TQk6uDGb7Oc869ZBtmdKVDBzY3+h9GnXB8ssrRXEPVZrlIOPOw==",
"dev": true
},
"@types/passport": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-0.4.5.tgz",
"integrity": "sha512-Ow5akVXwEZlOPCWGbEGy0GX4ocdwKz7JJH1K+BMd/BSOxmJTo2obH2AKbsgcncQvw5z7AGopdIu1Ap/j9sMRnQ==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/range-parser": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz",
@@ -207,6 +216,18 @@
"string-width": "^2.0.0"
}
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"anymatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
@@ -348,6 +369,32 @@
"integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=",
"dev": true
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"dev": true,
"requires": {
"chalk": "^1.1.3",
"esutils": "^2.0.2",
"js-tokens": "^3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
}
}
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
@@ -541,6 +588,12 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
"integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ=="
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true
},
"bunyan": {
"version": "1.8.12",
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
@@ -585,6 +638,37 @@
"integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=",
"dev": true
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"chokidar": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
@@ -670,6 +754,12 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true
},
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
@@ -939,11 +1029,27 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"eslint-plugin-prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz",
"integrity": "sha512-floiaI4F7hRkTrFe8V2ItOK97QYrX75DjmdzmVITZoAP6Cn06oEDPQRsO6MlHEP/u2SxI3xQ52Kpjw6j5WGfeQ==",
"dev": true,
"requires": {
"fast-diff": "^1.1.1",
"jest-docblock": "^21.0.0"
}
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -1150,6 +1256,12 @@
}
}
},
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
@@ -1228,6 +1340,12 @@
"integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
@@ -1945,6 +2063,21 @@
"uuid": "^3.1.0"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"has-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -2020,7 +2153,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -2314,6 +2446,12 @@
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz",
"integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA=="
},
"jest-docblock": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz",
"integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==",
"dev": true
},
"joi": {
"version": "13.4.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-13.4.0.tgz",
@@ -2324,6 +2462,12 @@
"topo": "3.x.x"
}
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
@@ -2856,6 +3000,20 @@
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
"dev": true
},
"passport": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.4.0.tgz",
"integrity": "sha1-xQlWkTR71a07XhgCOMORTRbwWBE=",
"requires": {
"passport-strategy": "1.x.x",
"pause": "0.0.1"
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
},
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
@@ -2879,11 +3037,22 @@
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"path-parse": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
"dev": true
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
},
"pause-stream": {
"version": "0.0.11",
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
@@ -3115,6 +3284,15 @@
"semver": "^5.1.0"
}
},
"resolve": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
"integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
"dev": true,
"requires": {
"path-parse": "^1.0.5"
}
},
"resolve-from": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
@@ -3503,6 +3681,15 @@
"safe-buffer": "~5.1.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -3536,6 +3723,12 @@
"ws": "^5.2.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
@@ -3648,6 +3841,73 @@
"strip-json-comments": "^2.0.1"
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
},
"tslint": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
"integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
"dev": true,
"requires": {
"babel-code-frame": "^6.22.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^3.2.0",
"glob": "^7.1.1",
"js-yaml": "^3.7.0",
"minimatch": "^3.0.4",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.12.1"
},
"dependencies": {
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"tslint-config-prettier": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.13.0.tgz",
"integrity": "sha512-assE77K7K8Q9j8CVIHiU3d1uoKc8N5v7UPpkQ9IE8BEPWkvSYR37lDuYekDlAMFqR1IpD6CrS+uOJLl6pw7Wdw==",
"dev": true
},
"tslint-plugin-prettier": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-1.3.0.tgz",
"integrity": "sha512-6UqeeV6EABp0RdQkW6eC1vwnAXcKMGJgPeJ5soXiKdSm2vv7c3dp+835CM8pjgx9l4uSa7tICm1Kli+SMsADDg==",
"dev": true,
"requires": {
"eslint-plugin-prettier": "^2.2.0",
"tslib": "^1.7.1"
}
},
"tsutils": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz",
"integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@coralproject/talk",
"version": "1.0.0",
"version": "5.0.0",
"description": "A better commenting experience from Mozilla, The Washington Post, and The New York Times.",
"scripts": {
"start": "node dist/index.js",
@@ -28,6 +28,7 @@
"lodash": "^4.17.10",
"luxon": "^1.2.1",
"mongodb": "^3.0.10",
"passport": "^0.4.0",
"performance-now": "^2.1.0",
"subscriptions-transport-ws": "^0.9.11",
"uuid": "^3.2.1"
@@ -43,6 +44,7 @@
"@types/lodash": "^4.14.109",
"@types/luxon": "^0.5.3",
"@types/mongodb": "^3.0.19",
"@types/passport": "^0.4.5",
"@types/uuid": "^3.4.3",
"@types/ws": "^5.1.2",
"graphql-playground-middleware-express": "^1.7.0",
@@ -50,6 +52,9 @@
"prettier": "^1.13.4",
"ts-node": "^6.1.1",
"tsconfig-paths": "^3.4.0",
"tslint": "^5.10.0",
"tslint-config-prettier": "^1.13.0",
"tslint-plugin-prettier": "^1.3.0",
"typescript": "^2.9.1"
}
}
+4 -4
View File
@@ -1,18 +1,18 @@
import { Express } from "express";
import { Db } from "mongodb";
import http from "http";
import { Redis } from "ioredis";
import { Db } from "mongodb";
import { Config } from "talk-server/config";
import { Schemas } from "talk-server/graph/schemas";
import { handleSubscriptions } from "talk-server/graph/common/subscriptions/middleware";
import { Schemas } from "talk-server/graph/schemas";
import { createRouter } from "./router";
import serveStatic from "./middleware/serveStatic";
import {
access as accessLogger,
error as errorLogger,
} from "./middleware/logging";
import serveStatic from "./middleware/serveStatic";
import { createRouter } from "./router";
export interface AppOptions {
parent: Express;
+3 -3
View File
@@ -1,11 +1,11 @@
import { RequestHandler, ErrorRequestHandler } from "express";
import logger from "../../logger";
import { ErrorRequestHandler, RequestHandler } from "express";
import now from "performance-now";
import logger from "../../logger";
export const access: RequestHandler = (req, res, next) => {
const startTime = now();
const end = res.end;
res.end = (chunk: any, encodingOrCb?: string | Function, cb?: Function) => {
res.end = (chunk: any, encodingOrCb?: any, cb?: any) => {
// Compute the end time.
const responseTime = Math.round(now() - startTime);
@@ -0,0 +1,15 @@
import { Db } from "mongodb";
import passport, { Authenticator } from "passport";
export interface PassportOptions {
db: Db;
}
export function createPassport({
db,
}: PassportOptions): passport.Authenticator {
// Create the authenticator.
const auth = new Authenticator();
return auth;
}
+26 -24
View File
@@ -1,7 +1,8 @@
import express, { Router } from "express";
import express from "express";
import passport from "passport";
import tenantGraphMiddleware from "talk-server/graph/tenant/middleware";
import managementGraphMiddleware from "talk-server/graph/management/middleware";
import tenantGraphMiddleware from "talk-server/graph/tenant/middleware";
import { AppOptions } from "./index";
import playground from "./middleware/playground";
@@ -9,17 +10,6 @@ import playground from "./middleware/playground";
async function createManagementRouter(opts: AppOptions) {
const router = express.Router();
if (opts.config.get("env") === "development") {
// GraphiQL
router.get(
"/graphiql",
playground({
endpoint: "/api/management/graphql",
subscriptionEndpoint: "/api/management/live",
})
);
}
// Management API
router.use(
"/graphql",
@@ -37,17 +27,6 @@ async function createManagementRouter(opts: AppOptions) {
async function createTenantRouter(opts: AppOptions) {
const router = express.Router();
if (opts.config.get("env") === "development") {
// GraphiQL
router.get(
"/graphiql",
playground({
endpoint: "/api/tenant/graphql",
subscriptionEndpoint: "/api/tenant/live",
})
);
}
// Tenant API
router.use(
"/graphql",
@@ -62,6 +41,9 @@ async function createAPIRouter(opts: AppOptions) {
// Create a router.
const router = express.Router();
// Setup Passport.
router.use(passport.initialize());
// Configure the tenant routes.
router.use("/tenant", await createTenantRouter(opts));
@@ -77,5 +59,25 @@ export async function createRouter(opts: AppOptions) {
router.use("/api", await createAPIRouter(opts));
if (opts.config.get("env") === "development") {
// Tenant GraphiQL
router.get(
"/tenant/graphiql",
playground({
endpoint: "/api/tenant/graphql",
subscriptionEndpoint: "/api/tenant/live",
})
);
// Management GraphiQL
router.get(
"/management/graphiql",
playground({
endpoint: "/api/management/graphql",
subscriptionEndpoint: "/api/management/live",
})
);
}
return router;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import Joi from "joi";
import convict from "convict";
import dotenv from "dotenv";
import Joi from "joi";
// Apply all the configuration provided in the .env file if it isn't already in
// the environment.
@@ -1,10 +1,10 @@
import { resolveGraphqlOptions } from "apollo-server-core";
import {
graphqlExpress,
ExpressGraphQLOptionsFunction,
graphqlExpress,
GraphQLOptions,
} from "apollo-server-express";
import { GraphQLError, FieldDefinitionNode, ValidationContext } from "graphql";
import { resolveGraphqlOptions } from "apollo-server-core";
import { FieldDefinitionNode, GraphQLError, ValidationContext } from "graphql";
import { Config } from "talk-server/config";
// Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57
@@ -28,7 +28,7 @@ export const graphqlMiddleware = (
// Generate the validation rules.
const validationRules: Array<(context: ValidationContext) => any> = [];
if (config.get("env") !== "production") {
if (config.get("env") === "production") {
// Disable introspection in production.
validationRules.push(NoIntrospection);
}
@@ -0,0 +1,3 @@
export interface ClientMutationProps {
clientMutationId: string;
}
@@ -1,11 +1,11 @@
import { DateTime } from "luxon";
import { GraphQLScalarType } from "graphql";
import { Kind } from "graphql/language";
import { DateTime } from "luxon";
import { Cursor } from "talk-server/models/connection";
function parseIntegerCursor(value: string): number {
try {
const cursor = parseInt(value);
const cursor = parseInt(value, 10);
return cursor;
} catch (err) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { addResolveFunctionsToSchema, IResolvers } from "graphql-tools";
import { getGraphQLProjectConfig } from "graphql-config";
import { addResolveFunctionsToSchema, IResolvers } from "graphql-tools";
export default function loadSchema(projectName: string, resolvers: IResolvers) {
// Load the configuration from the provided `.graphqlconfig` file.
@@ -1,6 +1,6 @@
import { execute, GraphQLSchema, subscribe } from "graphql";
import http from "http";
import { SubscriptionServer } from "subscriptions-transport-ws";
import { GraphQLSchema, execute, subscribe } from "graphql";
export interface SubscriptionMiddlewareOptions {
schema: GraphQLSchema;
@@ -1,6 +1,6 @@
import { RedisPubSub } from "graphql-redis-subscriptions";
import { createRedisClient } from "talk-server/services/redis";
import { Config } from "talk-server/config";
import { createRedisClient } from "talk-server/services/redis";
export async function createPubSub(config: Config): Promise<RedisPubSub> {
// Create the Redis clients for the PubSub server.
@@ -1,8 +1,8 @@
import { Db } from "mongodb";
import { GraphQLSchema } from "graphql";
import { Db } from "mongodb";
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
import { Config } from "talk-server/config";
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
import Context from "./context";
+10 -3
View File
@@ -1,20 +1,27 @@
import loaders from "./loaders";
import { Db } from "mongodb";
import { Tenant } from "talk-server/models/tenant";
import { User } from "talk-server/models/user";
import loaders from "./loaders";
import mutators from "./mutators";
export interface TenantContextOptions {
tenant?: Tenant;
db: Db;
tenant?: Tenant;
user?: User;
}
export default class TenantContext {
public loaders: ReturnType<typeof loaders>;
public mutators: ReturnType<typeof mutators>;
public db: Db;
public tenant?: Tenant;
public user?: User;
constructor({ tenant, db }: TenantContextOptions) {
constructor({ user, tenant, db }: TenantContextOptions) {
this.tenant = tenant;
this.user = user;
this.loaders = loaders(this);
this.mutators = mutators(this);
this.db = db;
}
}
@@ -1,11 +1,8 @@
import DataLoader from "dataloader";
import {
Asset,
retrieveMany as retrieveManyAssets,
} from "talk-server/models/asset";
import Context from "talk-server/graph/tenant/context";
import TenantContext from "talk-server/graph/tenant/context";
import { Asset, retrieveManyAssets } from "talk-server/models/asset";
export default (ctx: Context) => ({
export default (ctx: TenantContext) => ({
asset: new DataLoader<string, Asset>(ids =>
retrieveManyAssets(ctx.db, ctx.tenant.id, ids)
),
@@ -1,12 +1,12 @@
import DataLoader from "dataloader";
import Context from "talk-server/graph/tenant/context";
import {
Comment,
retrieveMany,
retrieveAssetConnection,
ConnectionInput,
retrieveAssetConnection,
retrieveMany,
retrieveRepliesConnection,
} from "talk-server/models/comment";
import Context from "talk-server/graph/tenant/context";
export default (ctx: Context) => ({
comment: new DataLoader((ids: string[]) =>
@@ -1,7 +1,7 @@
import Context from "talk-server/graph/tenant/context";
import Assets from "./assets";
import Comments from "./comments";
import Users from "./users";
import Context from "talk-server/graph/tenant/context";
export default (ctx: Context) => ({
Assets: Assets(ctx),
@@ -1,6 +1,6 @@
import DataLoader from "dataloader";
import { User, retrieveMany } from "talk-server/models/user";
import Context from "talk-server/graph/tenant/context";
import { retrieveMany, User } from "talk-server/models/user";
export default (ctx: Context) => ({
user: new DataLoader<string, User>(ids =>
+9 -5
View File
@@ -1,10 +1,10 @@
import { Db } from "mongodb";
import { GraphQLSchema } from "graphql";
import { Db } from "mongodb";
import { retrieveByDomain } from "talk-server/models/tenant";
import { createPubSub } from "talk-server/graph/common/subscriptions/pubsub";
import { Config } from "talk-server/config";
import { graphqlMiddleware } from "talk-server/graph/common/middleware";
import { createPubSub } from "talk-server/graph/common/subscriptions/pubsub";
import { retrieveTenantByDomain } from "talk-server/models/tenant";
import TenantContext from "./context";
@@ -14,11 +14,15 @@ export default async (schema: GraphQLSchema, config: Config, db: Db) => {
return graphqlMiddleware(config, async req => {
// TODO: replace with shared synced cache instead of direct db access.
const tenant = await retrieveByDomain(db, req.hostname);
const tenant = await retrieveTenantByDomain(db, req.hostname);
// Load the user from the request.
const user = req.user;
// Return the graph options.
return {
schema,
context: new TenantContext({ db, tenant }),
context: new TenantContext({ db, tenant, user }),
};
});
};
@@ -0,0 +1,15 @@
import TenantContext from "talk-server/graph/tenant/context";
import { CreateCommentInput } from "talk-server/graph/tenant/resolvers/mutation";
import { Comment } from "talk-server/models/comment";
import { create } from "talk-server/services/comments";
export default (ctx: TenantContext) => ({
create: (input: CreateCommentInput): Promise<Comment> => {
return create(ctx.db, ctx.tenant.id, {
author_id: ctx.user.id,
asset_id: input.assetID,
body: input.body,
parent_id: input.parentID,
});
},
});
@@ -0,0 +1,6 @@
import TenantContext from "talk-server/graph/tenant/context";
import Comment from "./comment";
export default (ctx: TenantContext) => ({
Comment: Comment(ctx),
});
@@ -1,5 +1,5 @@
import { Asset } from "talk-server/models/asset";
import Context from "talk-server/graph/tenant/context";
import { Asset } from "talk-server/models/asset";
import { ConnectionInput } from "talk-server/models/comment";
export default {
@@ -1,5 +1,5 @@
import { Comment, ConnectionInput } from "talk-server/models/comment";
import Context from "talk-server/graph/tenant/context";
import { Comment, ConnectionInput } from "talk-server/models/comment";
export default {
author: async (comment: Comment, _: any, ctx: Context) =>
@@ -1,6 +1,7 @@
import Cursor from "../../common/scalars/cursor";
import Asset from "./asset";
import Comment from "./comment";
import Cursor from "../../common/scalars/cursor";
import Mutation from "./mutation";
import Query from "./query";
export default {
@@ -8,4 +9,5 @@ export default {
Comment,
Cursor,
Query,
Mutation,
};
@@ -0,0 +1,26 @@
import { ClientMutationProps } from "talk-server/graph/common/resolvers/mutation";
import TenantContext from "talk-server/graph/tenant/context";
import { Comment } from "talk-server/models/comment";
export interface CreateCommentInput extends ClientMutationProps {
assetID: string;
parentID?: string;
body: string;
}
export interface CreateCommentPayload extends ClientMutationProps {
comment: Comment;
}
const Mutation = {
createComment: async (
source: void,
input: CreateCommentInput,
ctx: TenantContext
): Promise<CreateCommentPayload> => ({
comment: await ctx.mutators.Comment.create(input),
clientMutationId: input.clientMutationId,
}),
};
export default Mutation;
@@ -3,7 +3,7 @@ import { Asset } from "talk-server/models/asset";
export default {
asset: async (
_: any,
source: void,
{ id, url }: { id?: string; url: string },
ctx: TenantContext
): Promise<Asset> => {
+278 -283
View File
@@ -18,146 +18,146 @@ scalar Cursor
# The moderation mode of the site.
enum MODERATION_MODE {
"""
Comments posted while in `PRE` mode will be labeled with a `PREMOD`
status and will require a moderator decision before being visible.
"""
PRE
"""
Comments posted while in `PRE` mode will be labeled with a `PREMOD`
status and will require a moderator decision before being visible.
"""
PRE
"""
Comments posted while in `POST` will be visible immediately.
"""
POST
"""
Comments posted while in `POST` will be visible immediately.
"""
POST
}
"""
Wordlist describes all the available wordlists.
"""
type Wordlist {
"""
banned words will by default reject the comment if it is found.
"""
banned: [String!]!
"""
banned words will by default reject the comment if it is found.
"""
banned: [String!]!
"""
suspect words will simply flag the comment.
"""
suspect: [String!]!
"""
suspect words will simply flag the comment.
"""
suspect: [String!]!
}
# Settings stores the global settings for a given installation.
type Settings {
"""
moderation is the moderation mode for all Asset's on the site.
"""
moderation: MODERATION_MODE!
"""
moderation is the moderation mode for all Asset's on the site.
"""
moderation: MODERATION_MODE!
"""
Enables a requirement for email confirmation before a user can login.
"""
requireEmailConfirmation: Boolean!
"""
Enables a requirement for email confirmation before a user can login.
"""
requireEmailConfirmation: Boolean!
"""
infoBoxEnable will enable the Info Box content visible above the question
box.
"""
infoBoxEnable: Boolean!
"""
infoBoxEnable will enable the Info Box content visible above the question
box.
"""
infoBoxEnable: Boolean!
"""
infoBoxContent is the content of the Info Box.
"""
infoBoxContent: String
"""
infoBoxContent is the content of the Info Box.
"""
infoBoxContent: String
"""
questionBoxEnable will enable the Question Box's content to be visible above
the comment box.
"""
questionBoxEnable: Boolean!
"""
questionBoxEnable will enable the Question Box's content to be visible above
the comment box.
"""
questionBoxEnable: Boolean!
"""
questionBoxContent is the content of the Question Box.
"""
questionBoxContent: String
"""
questionBoxContent is the content of the Question Box.
"""
questionBoxContent: String
"""
questionBoxIcon is the icon for the Question Box.
"""
questionBoxIcon: String
"""
questionBoxIcon is the icon for the Question Box.
"""
questionBoxIcon: String
"""
premodLinksEnable will put all comments that contain links into premod.
"""
premodLinksEnable: Boolean!
"""
premodLinksEnable will put all comments that contain links into premod.
"""
premodLinksEnable: Boolean!
"""
autoCloseStream when true will auto close the stream when the `closeTimeout`
amount of seconds have been reached.
"""
autoCloseStream: Boolean!
"""
autoCloseStream when true will auto close the stream when the `closeTimeout`
amount of seconds have been reached.
"""
autoCloseStream: Boolean!
"""
customCssUrl is the URL of the custom CSS used to display on the frontend.
"""
customCssUrl: String
"""
customCssUrl is the URL of the custom CSS used to display on the frontend.
"""
customCssUrl: String
"""
closedTimeout is the amount of seconds from the created_at timestamp that a
given asset will be considered closed.
"""
closedTimeout: Int!
"""
closedTimeout is the amount of seconds from the created_at timestamp that a
given asset will be considered closed.
"""
closedTimeout: Int!
"""
closedMessage is the message shown to the user when the given Asset is
closed.
"""
closedMessage: String
"""
closedMessage is the message shown to the user when the given Asset is
closed.
"""
closedMessage: String
"""
disableCommenting will disable commenting site-wide.
"""
disableCommenting: Boolean!
"""
disableCommenting will disable commenting site-wide.
"""
disableCommenting: Boolean!
"""
disableCommentingMessage will be shown above the comment stream while
commenting is disabled site-wide.
"""
disableCommentingMessage: String
"""
disableCommentingMessage will be shown above the comment stream while
commenting is disabled site-wide.
"""
disableCommentingMessage: String
"""
editCommentWindowLength is the length of time (in milliseconds) after a
comment is posted that it can still be edited by the author.
"""
editCommentWindowLength: Int!
"""
editCommentWindowLength is the length of time (in milliseconds) after a
comment is posted that it can still be edited by the author.
"""
editCommentWindowLength: Int!
"""
charCountEnable is true when the character count restriction is enabled.
"""
charCountEnable: Boolean!
"""
charCountEnable is true when the character count restriction is enabled.
"""
charCountEnable: Boolean!
"""
charCount is the maximum number of characters a comment may be.
"""
charCount: Int
"""
charCount is the maximum number of characters a comment may be.
"""
charCount: Int
"""
organizationName is the name of the organization.
"""
organizationName: String
"""
organizationName is the name of the organization.
"""
organizationName: String
"""
organizationContactEmail is the email of the organization.
"""
organizationContactEmail: String
"""
organizationContactEmail is the email of the organization.
"""
organizationContactEmail: String
"""
wordlist will return a given list of words.
"""
wordlist: Wordlist!
"""
wordlist will return a given list of words.
"""
wordlist: Wordlist!
"""
domains will return a given list of whitelisted domains.
"""
domains: [String!]!
"""
domains will return a given list of whitelisted domains.
"""
domains: [String!]!
}
################################################################################
@@ -168,15 +168,15 @@ type Settings {
User is someone that leaves Comments, and logs in.
"""
type User {
"""
id is the identifier of the User.
"""
id: ID!
"""
id is the identifier of the User.
"""
id: ID!
"""
username is the name of the User visible to other Users.
"""
username: String!
"""
username is the name of the User visible to other Users.
"""
username: String!
}
################################################################################
@@ -184,95 +184,85 @@ type User {
################################################################################
enum COMMENT_STATUS {
NONE
ACCEPTED
NONE
ACCEPTED
}
"""
Comment is a comment left by a User on an Asset or another Comment as a reply.
"""
type Comment {
"""
id is the identifier of the Comment.
"""
id: ID!
"""
id is the identifier of the Comment.
"""
id: ID!
"""
body is the content of the Comment.
"""
body: String
"""
body is the content of the Comment.
"""
body: String
"""
author is the User that authored the Comment.
"""
author: User
"""
author is the User that authored the Comment.
"""
author: User
"""
status represents the Comment's current Status.
"""
status: COMMENT_STATUS!
"""
status represents the Comment's current Status.
"""
status: COMMENT_STATUS!
"""
replyCount is the number of replies. Only direct replies to this Comment
are counted. Deleted comments are included in this count.
"""
replyCount: Int
"""
replyCount is the number of replies. Only direct replies to this Comment
are counted. Deleted comments are included in this count.
"""
replyCount: Int
"""
replies will return the replies to this comment.
"""
replies(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection
"""
replies will return the replies to this comment.
"""
replies(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection
}
type PageInfo {
"""
Cursor of first node in subset.
"""
startCursor: Cursor
"""
Cursor of last node in subset.
"""
endCursor: Cursor
"""
Indicates that there are more nodes after this subset.
"""
hasNextPage: Boolean!
"""
Indicates that there are more nodes after this subset.
"""
hasNextPage: Boolean!
}
"""
CommentEdge represents a unique Comment in a CommentConnection.
"""
type CommentEdge {
"""
node is the Comment for this edge.
"""
node: Comment
"""
node is the Comment for this edge.
"""
node: Comment
"""
"""
"""
cursor: Cursor
"""
cursor: Cursor
}
"""
CommentsConnection represents a subset of a comment list.
"""
type CommentsConnection {
"""
edges are a subset of CommentEdge's.
"""
edges: [CommentEdge!]!
"""
edges are a subset of CommentEdge's.
"""
edges: [CommentEdge!]!
"""
pageInfo is
"""
pageInfo: PageInfo!
"""
pageInfo is
"""
pageInfo: PageInfo!
}
################################################################################
@@ -280,84 +270,89 @@ type CommentsConnection {
################################################################################
enum COMMENT_SORT {
CREATED_AT_DESC
CREATED_AT_ASC
REPLIES_DESC
RESPECT_DESC
CREATED_AT_DESC
CREATED_AT_ASC
REPLIES_DESC
RESPECT_DESC
}
"""
Asset is an Article or Page where Comments are written on by Users.
"""
type Asset {
"""
id is the identifier of the Asset.
"""
id: ID!
"""
id is the identifier of the Asset.
"""
id: ID!
"""
url is the url that the Asset is located on.
"""
url: String!
"""
url is the url that the Asset is located on.
"""
url: String!
"""
title is the title of the scraped Asset.
"""
title: String
"""
title is the title of the scraped Asset.
"""
title: String
"""
comments are the comments on the Asset.
"""
comments(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection
"""
comments are the comments on the Asset.
"""
comments(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection
"""
author is the authors listed in the meta tags for the Asset.
"""
author: String
"""
author is the authors listed in the meta tags for the Asset.
"""
author: String
"""
closedAt is the Time that the Asset is closed for commenting.
"""
closedAt: Time
"""
closedAt is the Time that the Asset is closed for commenting.
"""
closedAt: Time
"""
isClosed returns true when the Asset is currently closed for commenting.
"""
isClosed: Boolean!
"""
isClosed returns true when the Asset is currently closed for commenting.
"""
isClosed: Boolean!
"""
createdAt is the date that the Asset was created at.
"""
createdAt: Time!
"""
createdAt is the date that the Asset was created at.
"""
createdAt: Time!
}
"""
AssetEdge represents a unique Asset in a AssetConnection.
"""
type AssetEdge {
"""
node is the Asset for this edge.
"""
node: Asset
"""
"""
cursor: Cursor
}
"""
AssetsConnection represents a subset of a Asset list.
"""
type AssetsConnection {
"""
Indicates that there are more Assets after this subset.
"""
hasNextPage: Boolean!
"""
edges are a subset of AssetEdge's.
"""
edges: [AssetEdge!]!
"""
Cursor of first Asset in subset.
"""
startCursor: Cursor
"""
Cursor of last Asset in subset.
"""
endCursor: Cursor
"""
Subset of Assets.
"""
nodes: [Asset!]!
"""
pageInfo is
"""
pageInfo: PageInfo!
}
################################################################################
@@ -365,30 +360,30 @@ type AssetsConnection {
################################################################################
type Query {
"""
comment returns a specific comment.
"""
comment(id: ID!): Comment
"""
comment returns a specific comment.
"""
comment(id: ID!): Comment
"""
assets returns a AssetsConnection.
"""
assets(cursor: Cursor, limit: Int = 10): AssetsConnection
"""
assets returns a AssetsConnection.
"""
assets(cursor: Cursor, limit: Int = 10): AssetsConnection
"""
asset is the Asset specified by its ID.
"""
asset(id: ID!): Asset
"""
asset is the Asset specified by its ID.
"""
asset(id: ID!): Asset
"""
me is the current logged in User.
"""
me: User
"""
me is the current logged in User.
"""
me: User
"""
settings is the Settings for a given Tenant.
"""
settings: Settings!
"""
settings is the Settings for a given Tenant.
"""
settings: Settings!
}
################################################################################
@@ -403,25 +398,25 @@ type Query {
CreateCommentInput provides the input for the createComment Mutation.
"""
input CreateCommentInput {
"""
assetID is the ID of the Asset where we are creating a comment on.
"""
assetID: ID!
"""
assetID is the ID of the Asset where we are creating a comment on.
"""
assetID: ID!
"""
parentID is the optional ID of the Comment that we are replying to.
"""
parentID: ID
"""
parentID is the optional ID of the Comment that we are replying to.
"""
parentID: ID
"""
body is the Comment body, the content of the Comment.
"""
body: String!
"""
body is the Comment body, the content of the Comment.
"""
body: String!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
"""
@@ -429,15 +424,15 @@ CreateCommentPayload contains the created Comment after the createComment
mutation.
"""
type CreateCommentPayload {
"""
comment is the possibly created comment.
"""
comment: Comment
"""
comment is the possibly created comment.
"""
comment: Comment
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
##################
@@ -445,10 +440,10 @@ type CreateCommentPayload {
##################
type Mutation {
"""
createComment will create a Comment as the current logged in User.
"""
createComment(input: CreateCommentInput!): CreateCommentPayload
"""
createComment will create a Comment as the current logged in User.
"""
createComment(input: CreateCommentInput!): CreateCommentPayload
}
################################################################################
@@ -456,5 +451,5 @@ type Mutation {
################################################################################
type Subscription {
commentCreated(assetID: ID!): Comment
commentCreated(assetID: ID!): Comment
}
+4 -4
View File
@@ -1,14 +1,14 @@
import express, { Express } from "express";
import http from "http";
import getManagementSchema from "talk-server/graph/management/schema";
import { Schemas } from "talk-server/graph/schemas";
import getTenantSchema from "talk-server/graph/tenant/schema";
import { attachSubscriptionHandlers, createApp, listenAndServe } from "./app";
import config, { Config } from "./config";
import { createApp, listenAndServe, attachSubscriptionHandlers } from "./app";
import logger from "./logger";
import { createMongoDB } from "./services/mongodb";
import { createRedisClient } from "./services/redis";
import getManagementSchema from "talk-server/graph/management/schema";
import getTenantSchema from "talk-server/graph/tenant/schema";
import { Schemas } from "talk-server/graph/schemas";
export interface ServerOptions {
config?: Config;
+10 -10
View File
@@ -1,10 +1,10 @@
import { Db, Collection } from "mongodb";
import Query, { FilterQuery } from "./query";
import { defaults } from "lodash";
import uuid from "uuid";
import { Omit } from "talk-common/types";
import dotize from "dotize";
import { defaults } from "lodash";
import { Collection, Db } from "mongodb";
import { Omit } from "talk-common/types";
import { TenantResource } from "talk-server/models/tenant";
import uuid from "uuid";
import Query from "./query";
function collection(db: Db): Collection<Asset> {
return db.collection<Asset>("assets");
@@ -29,7 +29,7 @@ export interface Asset extends TenantResource {
export type CreateAssetInput = Pick<Asset, "id" | "url">;
export async function create(
export async function createAsset(
db: Db,
tenantID: string,
input: CreateAssetInput
@@ -69,7 +69,7 @@ export async function create(
return result.value;
}
export async function retrieve(
export async function retrieveAsset(
db: Db,
tenantID: string,
id: string
@@ -79,11 +79,11 @@ export async function retrieve(
.findOne({ id, tenant_id: tenantID });
}
export async function retrieveMany(
export async function retrieveManyAssets(
db: Db,
tenantID: string,
ids: string[]
): Promise<Array<Asset>> {
): Promise<Asset[]> {
const cursor = await db
.collection<Asset>("assets")
.find({ id: { $in: ids }, tenant_id: tenantID });
@@ -98,7 +98,7 @@ export type UpdateAssetInput = Omit<
"id" | "tenant_id" | "url" | "created_at"
>;
export async function update(
export async function updateAsset(
db: Db,
tenantID: string,
id: string,
+7 -7
View File
@@ -1,11 +1,11 @@
import { Db, Collection } from "mongodb";
import { Omit, Sub } from "talk-common/types";
import { merge } from "lodash";
import uuid from "uuid";
import { Connection, Edge, Cursor } from "talk-server/models/connection";
import Query from "talk-server/models/query";
import { Collection, Db } from "mongodb";
import { Omit, Sub } from "talk-common/types";
import { ActionCounts } from "talk-server/models/actions";
import { Connection, Cursor, Edge } from "talk-server/models/connection";
import Query from "talk-server/models/query";
import { TenantResource } from "talk-server/models/tenant";
import uuid from "uuid";
function collection(db: Db): Collection<Comment> {
return db.collection<Comment>("comments");
@@ -116,7 +116,7 @@ export async function retrieveMany(
db: Db,
tenantID: string,
ids: string[]
): Promise<Readonly<Comment>[]> {
): Promise<Array<Readonly<Comment>>> {
const cursor = await collection(db).find({
id: {
$in: ids,
@@ -152,7 +152,7 @@ export interface ConnectionInput {
function nodesToEdge(
input: ConnectionInput,
nodes: Comment[]
): Edge<Comment>[] {
): Array<Edge<Comment>> {
let getCursor: (comment: Comment, index: number) => Cursor;
switch (input.orderBy) {
case CommentSort.CREATED_AT_DESC:
+1 -1
View File
@@ -10,6 +10,6 @@ export interface PageInfo {
}
export interface Connection<T> {
edges: Edge<T>[];
edges: Array<Edge<T>>;
pageInfo: PageInfo;
}
+3 -3
View File
@@ -20,7 +20,7 @@ export default class Query<T> {
private collection: Collection<T>;
private skip?: number;
private limit?: number;
private sort?: Object;
private sort?: object;
constructor(collection: Collection<T>) {
this.collection = collection;
@@ -61,7 +61,7 @@ export default class Query<T> {
*
* @param sort the sorting option for the documents
*/
public orderBy(sort: Object): Query<T> {
public orderBy(sort: object): Query<T> {
this.sort = merge({}, this.sort || {}, sort);
return this;
}
@@ -69,7 +69,7 @@ export default class Query<T> {
/**
* exec will return a cursor to the query.
*/
async exec(): Promise<Cursor<T>> {
public async exec(): Promise<Cursor<T>> {
let cursor = await this.collection.find(this.filter);
if (this.limit) {
+13 -11
View File
@@ -1,8 +1,8 @@
import { Db, Collection } from "mongodb";
import { merge } from "lodash";
import dotize from "dotize";
import { merge } from "lodash";
import { Collection, Db } from "mongodb";
import { Sub } from "talk-common/types";
import uuid from "uuid";
import { Omit, Sub } from "talk-common/types";
function collection(db: Db): Collection<Tenant> {
return db.collection<Tenant>("tenants");
@@ -75,7 +75,7 @@ export type CreateTenantInput = Pick<
* @param db the MongoDB connection used to create the tenant.
* @param input the customizable parts of the Tenant available during creation
*/
export async function create(
export async function createTenant(
db: Db,
input: CreateTenantInput
): Promise<Readonly<Tenant>> {
@@ -113,7 +113,7 @@ export async function create(
return tenant;
}
export async function retrieveByDomain(
export async function retrieveTenantByDomain(
db: Db,
domain: string
): Promise<Readonly<Tenant>> {
@@ -124,10 +124,10 @@ export async function retrieve(db: Db, id: string): Promise<Readonly<Tenant>> {
return collection(db).findOne({ id });
}
export async function retrieveMany(
export async function retrieveManyTenants(
db: Db,
ids: string[]
): Promise<Readonly<Tenant>[]> {
): Promise<Array<Readonly<Tenant>>> {
const cursor = await collection(db).find({
id: {
$in: ids,
@@ -139,10 +139,10 @@ export async function retrieveMany(
return ids.map(id => tenants.find(tenant => tenant.id === id));
}
export async function retrieveManyByDomain(
export async function retrieveManyTenantsByDomain(
db: Db,
domains: string[]
): Promise<Readonly<Tenant>[]> {
): Promise<Array<Readonly<Tenant>>> {
const cursor = await collection(db).find({
domain: {
$in: domains,
@@ -156,13 +156,15 @@ export async function retrieveManyByDomain(
);
}
export async function retrieveAll(db: Db): Promise<Readonly<Tenant>[]> {
export async function retrieveAllTenants(
db: Db
): Promise<Array<Readonly<Tenant>>> {
return collection(db)
.find({})
.toArray();
}
export async function update(
export async function updateTenant(
db: Db,
id: string,
update: Partial<CreateTenantInput>
+6 -6
View File
@@ -1,9 +1,9 @@
import { ActionCounts } from "talk-server/models/actions";
import { Db, Collection } from "mongodb";
import uuid from "uuid";
import { Omit, Sub } from "talk-common/types";
import { merge } from "lodash";
import { Collection, Db } from "mongodb";
import { Omit, Sub } from "talk-common/types";
import { ActionCounts } from "talk-server/models/actions";
import { TenantResource } from "talk-server/models/tenant";
import uuid from "uuid";
function collection(db: Db): Collection<User> {
return db.collection<User>("users");
@@ -61,7 +61,7 @@ export interface UserStatusHistory<T> {
export interface UserStatusItem<T> {
status: T; // TODO: migrate field
history: UserStatusHistory<T>[];
history: Array<UserStatusHistory<T>>;
}
export interface UserStatus {
@@ -152,7 +152,7 @@ export async function retrieveMany(
db: Db,
tenantID: string,
ids: string[]
): Promise<Readonly<User>[]> {
): Promise<Array<Readonly<User>>> {
const cursor = await collection(db).find({
id: {
$in: ids,
+26 -3
View File
@@ -1,6 +1,29 @@
import { Db } from "mongodb";
import { Comment } from "talk-server/models/comment";
export async function create(db: Db): Promise<Comment> {
return null;
import { Omit } from "talk-common/types";
import {
Comment,
CommentStatus,
create as createComment,
CreateCommentInput,
} from "talk-server/models/comment";
export type CreateComment = Omit<
CreateCommentInput,
"status" | "action_counts"
>;
export async function create(
db: Db,
tenantID: string,
input: CreateComment
): Promise<Comment> {
// TODO: run the comment through the moderation phases.
const comment = await createComment(db, tenantID, {
status: CommentStatus.ACCEPTED,
action_counts: {},
...input,
});
return comment;
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { MongoClient, Db } from "mongodb";
import { Db, MongoClient } from "mongodb";
import { Config } from "talk-server/config";
/**
+9 -5
View File
@@ -1,8 +1,12 @@
import { Db } from "mongodb";
import { Redis } from "ioredis";
import DataLoader from "dataloader";
import { Redis } from "ioredis";
import { Db } from "mongodb";
import { Tenant, retrieveAll, retrieveMany } from "talk-server/models/tenant";
import {
retrieveAllTenants,
retrieveManyTenants,
Tenant,
} from "talk-server/models/tenant";
const CacheUpdateChannel = "tenant";
@@ -18,7 +22,7 @@ export default class Cache {
this.db = db;
// Prepare the list of all tenant's maintained by this instance.
this.tenants = new DataLoader(ids => retrieveMany(db, ids));
this.tenants = new DataLoader(ids => retrieveManyTenants(db, ids));
// Subscribe to tenant notifications.
subscriber.subscribe(CacheUpdateChannel);
@@ -33,7 +37,7 @@ export default class Cache {
*/
public async primeAll() {
// Grab all the tenants for this node.
const tenants = await retrieveAll(this.db);
const tenants = await retrieveAllTenants(this.db);
// Clear out all the items in the cache.
this.tenants.clearAll();
+7 -2
View File
@@ -1,6 +1,9 @@
import createTalk from "./core";
import express from "express";
import logger from "talk-server/logger";
import createTalk from "./core";
// Create the app that will serve as the mounting point for the Talk Server.
const app = express();
@@ -11,7 +14,9 @@ async function bootstrap() {
// Start the server.
await server.start(app);
} catch (err) {}
} catch (err) {
logger.error({ err }, "can not bootstrap server");
}
}
bootstrap();
+7
View File
@@ -0,0 +1,7 @@
import { User } from "talk-server/models/user";
declare module "express" {
interface Request {
user?: User;
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": [
"tslint:recommended",
"tslint-config-prettier",
"tslint-plugin-prettier"
],
"rules": {
"prettier": true,
"object-literal-sort-keys": false,
"interface-name": [true, "never-prefix"],
"no-switch-case-fall-through": true,
"member-ordering": false
}
}