Merge branch 'next' into next-respect

This commit is contained in:
Chi Vinh Le
2018-10-04 19:15:00 +02:00
102 changed files with 13134 additions and 9240 deletions
@@ -0,0 +1,5 @@
import { RequestHandler } from "express";
export const adminHandler: RequestHandler = (req, res) => {
res.render("admin");
};
@@ -1,5 +0,0 @@
import { RequestHandler } from "express";
export const streamHandler: RequestHandler = (req, res) => {
res.render("stream");
};
@@ -9,7 +9,7 @@ export const nocacheMiddleware: RequestHandler = (req, res, next) => {
next();
};
export const cacheHeadersMiddleware = (duration: string): RequestHandler => {
export const cacheHeadersMiddleware = (duration?: string): RequestHandler => {
const maxAge = duration ? Math.floor(ms(duration) / 1000) : false;
if (!maxAge) {
return nocacheMiddleware;
-156
View File
@@ -1,156 +0,0 @@
import express from "express";
import passport from "passport";
import {
logoutHandler,
signupHandler,
} from "talk-server/app/handlers/auth/local";
import { streamHandler } from "talk-server/app/handlers/embed/stream";
import { apiErrorHandler } from "talk-server/app/middleware/error";
import { errorLogger } from "talk-server/app/middleware/logging";
import { wrapAuthn } from "talk-server/app/middleware/passport";
import tenantMiddleware from "talk-server/app/middleware/tenant";
import managementGraphMiddleware from "talk-server/graph/management/middleware";
import tenantGraphMiddleware from "talk-server/graph/tenant/middleware";
import {
cacheHeadersMiddleware,
nocacheMiddleware,
} from "talk-server/app/middleware/cacheHeaders";
import { AppOptions } from "./index";
import playground from "./middleware/playground";
async function createManagementRouter(app: AppOptions, options: RouterOptions) {
const router = express.Router();
// Management API
router.use(
"/graphql",
express.json(),
await managementGraphMiddleware(
app.schemas.management,
app.config,
app.mongo
)
);
return router;
}
async function createTenantRouter(app: AppOptions, options: RouterOptions) {
const router = express.Router();
// Tenant identification middleware.
router.use(tenantMiddleware({ cache: app.tenantCache }));
// Setup Passport middleware.
router.use(options.passport.initialize());
// Setup auth routes.
router.use("/auth", createNewAuthRouter(app, options));
// Tenant API
router.use(
"/graphql",
express.json(),
// Any users may submit their GraphQL requests with authentication, this
// middleware will unpack their user into the request.
options.passport.authenticate("jwt", { session: false }),
await tenantGraphMiddleware({
schema: app.schemas.tenant,
config: app.config,
mongo: app.mongo,
redis: app.redis,
queue: app.queue,
})
);
return router;
}
function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
const router = express.Router();
// Mount the passport routes.
router.delete(
"/",
options.passport.authenticate("jwt", { session: false }),
logoutHandler({ redis: app.redis })
);
router.post(
"/local",
express.json(),
wrapAuthn(options.passport, app.signingConfig, "local")
);
router.post(
"/local/signup",
express.json(),
signupHandler({ db: app.mongo, signingConfig: app.signingConfig })
);
router.get("/oidc", wrapAuthn(options.passport, app.signingConfig, "oidc"));
router.get(
"/oidc/callback",
wrapAuthn(options.passport, app.signingConfig, "oidc")
);
return router;
}
async function createAPIRouter(app: AppOptions, options: RouterOptions) {
// Create a router.
const router = express.Router();
// Configure the tenant routes.
router.use("/tenant", await createTenantRouter(app, options));
// Configure the management routes.
router.use("/management", await createManagementRouter(app, options));
// General API error handler.
router.use(errorLogger);
router.use(apiErrorHandler);
return router;
}
export interface RouterOptions {
/**
* passport is the instance of the Authenticator that can be used to create
* and mount new authentication middleware.
*/
passport: passport.Authenticator;
}
export async function createRouter(app: AppOptions, options: RouterOptions) {
// Create a router.
const router = express.Router();
router.use("/api", nocacheMiddleware, await createAPIRouter(app, options));
if (app.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",
})
);
}
// Handle the stream handler.
router.get("/embed/stream", cacheHeadersMiddleware("1h"), streamHandler);
return router;
}
+39
View File
@@ -0,0 +1,39 @@
import express from "express";
import { AppOptions } from "talk-server/app";
import {
logoutHandler,
signupHandler,
} from "talk-server/app/handlers/auth/local";
import { wrapAuthn } from "talk-server/app/middleware/passport";
import { RouterOptions } from "talk-server/app/router/types";
export function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
const router = express.Router();
// Mount the passport routes.
router.delete(
"/",
options.passport.authenticate("jwt", { session: false }),
logoutHandler({ redis: app.redis })
);
router.post(
"/local",
express.json(),
wrapAuthn(options.passport, app.signingConfig, "local")
);
router.post(
"/local/signup",
express.json(),
signupHandler({ db: app.mongo, signingConfig: app.signingConfig })
);
router.get("/oidc", wrapAuthn(options.passport, app.signingConfig, "oidc"));
router.get(
"/oidc/callback",
wrapAuthn(options.passport, app.signingConfig, "oidc")
);
return router;
}
+34
View File
@@ -0,0 +1,34 @@
import express from "express";
import passport from "passport";
import { AppOptions } from "talk-server/app";
import { apiErrorHandler } from "talk-server/app/middleware/error";
import { errorLogger } from "talk-server/app/middleware/logging";
import { createManagementRouter } from "./management";
import { createTenantRouter } from "./tenant";
export interface RouterOptions {
/**
* passport is the instance of the Authenticator that can be used to create
* and mount new authentication middleware.
*/
passport: passport.Authenticator;
}
export async function createAPIRouter(app: AppOptions, options: RouterOptions) {
// Create a router.
const router = express.Router();
// Configure the tenant routes.
router.use("/tenant", await createTenantRouter(app, options));
// Configure the management routes.
router.use("/management", await createManagementRouter(app));
// General API error handler.
router.use(errorLogger);
router.use(apiErrorHandler);
return router;
}
@@ -0,0 +1,21 @@
import express from "express";
import { AppOptions } from "talk-server/app";
import managementGraphMiddleware from "talk-server/graph/management/middleware";
export async function createManagementRouter(app: AppOptions) {
const router = express.Router();
// Management API
router.use(
"/graphql",
express.json(),
await managementGraphMiddleware(
app.schemas.management,
app.config,
app.mongo
)
);
return router;
}
+42
View File
@@ -0,0 +1,42 @@
import express from "express";
import { AppOptions } from "talk-server/app";
import tenantMiddleware from "talk-server/app/middleware/tenant";
import { RouterOptions } from "talk-server/app/router/types";
import tenantGraphMiddleware from "talk-server/graph/tenant/middleware";
import { createNewAuthRouter } from "./auth";
export async function createTenantRouter(
app: AppOptions,
options: RouterOptions
) {
const router = express.Router();
// Tenant identification middleware.
router.use(tenantMiddleware({ cache: app.tenantCache }));
// Setup Passport middleware.
router.use(options.passport.initialize());
// Setup auth routes.
router.use("/auth", createNewAuthRouter(app, options));
// Tenant API
router.use(
"/graphql",
express.json(),
// Any users may submit their GraphQL requests with authentication, this
// middleware will unpack their user into the request.
options.passport.authenticate("jwt", { session: false }),
await tenantGraphMiddleware({
schema: app.schemas.tenant,
config: app.config,
mongo: app.mongo,
redis: app.redis,
queue: app.queue,
})
);
return router;
}
+29
View File
@@ -0,0 +1,29 @@
import express from "express";
import { cacheHeadersMiddleware } from "talk-server/app/middleware/cacheHeaders";
export interface ClientTargetHandlerOptions {
/**
* view is the name of the template to render.
*/
view: string;
/**
* cacheDuration is the cache duration that a given request should be cached for.
*/
cacheDuration?: string;
}
export function createClientTargetRouter({
view,
cacheDuration = "1h",
}: ClientTargetHandlerOptions) {
// Create a router.
const router = express.Router();
router.get("/", cacheHeadersMiddleware(cacheDuration), (req, res) =>
res.render(view)
);
return router;
}
+60
View File
@@ -0,0 +1,60 @@
import express, { Router } from "express";
import { AppOptions } from "talk-server/app";
import { nocacheMiddleware } from "talk-server/app/middleware/cacheHeaders";
import playground from "talk-server/app/middleware/playground";
import { RouterOptions } from "talk-server/app/router/types";
import logger from "talk-server/logger";
import { createAPIRouter } from "./api";
import { createClientTargetRouter } from "./client";
export async function createRouter(app: AppOptions, options: RouterOptions) {
// Create a router.
const router = express.Router();
router.use("/api", nocacheMiddleware, await createAPIRouter(app, options));
// Attach the GraphiQL if enabled.
if (app.config.get("enable_graphiql")) {
attachGraphiQL(router, app);
}
// Add the client targets.
router.get("/embed/stream", createClientTargetRouter({ view: "stream" }));
router.get("/admin", createClientTargetRouter({ view: "admin" }));
return router;
}
/**
* attachGraphiQL will attach the GraphiQL routes to the router.
*
* @param router the router to attach the GraphiQL routes to
* @param app the application to read the configuration from
*/
function attachGraphiQL(router: Router, app: AppOptions) {
if (app.config.get("env") === "production") {
logger.warn(
"enable_graphiql is enabled, but we're in production mode, this is not recommended"
);
}
// 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",
})
);
}
+9
View File
@@ -0,0 +1,9 @@
import passport from "passport";
export interface RouterOptions {
/**
* passport is the instance of the Authenticator that can be used to create
* and mount new authentication middleware.
*/
passport: passport.Authenticator;
}
@@ -17,6 +17,7 @@ import {
retrieveCommentAssetConnection,
retrieveCommentParentsConnection,
retrieveCommentRepliesConnection,
retrieveCommentUserConnection,
retrieveManyComments,
} from "talk-server/models/comment";
import { Connection } from "talk-server/models/connection";
@@ -53,6 +54,20 @@ export default (ctx: Context) => ({
itemIDs
)
),
forUser: (
userID: string,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: AssetToCommentsArgs
) =>
retrieveCommentUserConnection(ctx.mongo, ctx.tenant.id, userID, {
first,
orderBy,
after,
}),
forAsset: (
assetID: string,
// Apply the graph schema defaults at the loader.
@@ -84,6 +84,8 @@ const Comment: GQLCommentTypeResolver<Comment> = {
comment.parent_id
? ctx.loaders.Comments.parents(comment, input)
: createConnection(),
asset: (comment, input, ctx) =>
ctx.loaders.Assets.asset.load(comment.asset_id),
};
export default Comment;
@@ -8,6 +8,7 @@ import CommentCounts from "./comment_counts";
import Mutation from "./mutation";
import Profile from "./profile";
import Query from "./query";
import User from "./user";
const Resolvers: GQLResolver = {
Asset,
@@ -18,6 +19,7 @@ const Resolvers: GQLResolver = {
Mutation,
Profile,
Query,
User,
};
export default Resolvers;
@@ -0,0 +1,8 @@
import { GQLUserTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
import { User } from "talk-server/models/user";
const User: GQLUserTypeResolver<User> = {
comments: (user, input, ctx) => ctx.loaders.Comments.forUser(user.id, input),
};
export default User;
@@ -5,7 +5,7 @@
"""
auth is a directive that will enforce authorization rules on the schema
definition. It will restrict the viewer of the field based on roles or if the
`userIDField` is specified, it will see if the current users ID equals the field
`userIDField` is specified, it will see if the current users ID equals the field
specified. This allows users that own a specific resource (like a comment, or a
flag) see their own content, but restrict it to everyone else. If the directive
is used without options, it simply requires a logged in user.
@@ -761,6 +761,15 @@ type User {
role is the current role of the User.
"""
role: USER_ROLE! @auth(roles: [ADMIN, MODERATOR], userIDField: "id")
"""
comments are the comments of the User.
"""
comments(
first: Int = 10
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
): CommentsConnection! @auth(roles: [ADMIN, MODERATOR], userIDField: "id")
}
################################################################################
@@ -897,6 +906,11 @@ type Comment {
left by the current User on this Comment.
"""
myActionPresence: ActionPresence
"""
asset is the Asset that the Comment was written on.
"""
asset: Asset!
}
type PageInfo {
+24
View File
@@ -422,6 +422,30 @@ export async function retrieveCommentAssetConnection(
return retrieveConnection(input, query);
}
/**
* retrieveCommentUserConnection returns a Connection<Comment> for a given User's
* comments.
*
* @param db database connection
* @param userID the User id for the comment to retrieve
* @param input connection configuration
*/
export async function retrieveCommentUserConnection(
db: Db,
tenantID: string,
userID: string,
input: ConnectionInput
) {
// Create the query.
const query = new Query(collection(db)).where({
tenant_id: tenantID,
author_id: userID,
});
// Return a connection for the comments query.
return retrieveConnection(input, query);
}
/**
* retrieveConnection returns a Connection<Comment> for the given input and
* Query.