mirror of
https://github.com/wassname/talk.git
synced 2026-07-23 13:10:20 +08:00
[CORL-406] Tenant Locale Selection (#2450)
* feat: added preload config * feat: support changing locale * fix: name case * fix: removed unused code * feat: added translations for default reactions * fix: do not translate icon * fix: shorter i18n keys
This commit is contained in:
@@ -54,13 +54,14 @@ const TenantInstallBodySchema = Joi.object().keys({
|
||||
|
||||
export type TenantInstallHandlerOptions = Pick<
|
||||
AppOptions,
|
||||
"redis" | "mongo" | "config" | "mailerQueue"
|
||||
"redis" | "mongo" | "config" | "mailerQueue" | "i18n"
|
||||
>;
|
||||
|
||||
export const installHandler = ({
|
||||
mongo,
|
||||
redis,
|
||||
config,
|
||||
i18n,
|
||||
}: TenantInstallHandlerOptions): RequestHandler => async (req, res, next) => {
|
||||
try {
|
||||
if (!req.coral) {
|
||||
@@ -95,6 +96,7 @@ export const installHandler = ({
|
||||
mongo,
|
||||
redis,
|
||||
req.coral.cache.tenant,
|
||||
i18n,
|
||||
{
|
||||
...tenantInput,
|
||||
// Infer the Tenant domain via the hostname parameter.
|
||||
|
||||
@@ -2,16 +2,20 @@ import express, { Router } from "express";
|
||||
import { minify } from "html-minifier";
|
||||
import path from "path";
|
||||
|
||||
import { LanguageCode } from "coral-common/helpers/i18n/locales";
|
||||
import { cacheHeadersMiddleware } from "coral-server/app/middleware/cacheHeaders";
|
||||
import { cspTenantMiddleware } from "coral-server/app/middleware/csp/tenant";
|
||||
import { installedMiddleware } from "coral-server/app/middleware/installed";
|
||||
import { tenantMiddleware } from "coral-server/app/middleware/tenant";
|
||||
import logger from "coral-server/logger";
|
||||
import TenantCache from "coral-server/services/tenant/cache";
|
||||
import { RequestHandler } from "coral-server/types/express";
|
||||
|
||||
import Entrypoints, { Entrypoint } from "../helpers/entrypoints";
|
||||
|
||||
export interface ClientTargetHandlerOptions {
|
||||
defaultLocale: LanguageCode;
|
||||
|
||||
/**
|
||||
* entrypoint is the entrypoint entry to load.
|
||||
*/
|
||||
@@ -35,50 +39,64 @@ export interface ClientTargetHandlerOptions {
|
||||
staticURI: string;
|
||||
}
|
||||
|
||||
function createClientTargetRouter({
|
||||
staticURI,
|
||||
entrypoint,
|
||||
enableCustomCSS = false,
|
||||
cacheDuration = "1h",
|
||||
}: ClientTargetHandlerOptions) {
|
||||
function createClientTargetRouter(options: ClientTargetHandlerOptions) {
|
||||
// Create a router.
|
||||
const router = express.Router();
|
||||
|
||||
// Always send the cache headers.
|
||||
router.use(cacheHeadersMiddleware(cacheDuration));
|
||||
router.use(cacheHeadersMiddleware(options.cacheDuration));
|
||||
|
||||
// Wildcard display all the client routes under the provided prefix.
|
||||
router.get("/*", (req, res, next) =>
|
||||
res.render(
|
||||
"client",
|
||||
{ staticURI, entrypoint, enableCustomCSS },
|
||||
(err, html) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Send back the HTML minified.
|
||||
res.send(
|
||||
minify(html, {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
router.get("/*", clientHandler(options));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
interface MountClientRouteOptions {
|
||||
defaultLocale: LanguageCode;
|
||||
tenantCache: TenantCache;
|
||||
staticURI: string;
|
||||
}
|
||||
|
||||
const clientHandler = ({
|
||||
staticURI,
|
||||
entrypoint,
|
||||
enableCustomCSS,
|
||||
defaultLocale,
|
||||
}: ClientTargetHandlerOptions): RequestHandler => (req, res, next) => {
|
||||
// Provide configuration to the frontend in the HTML.
|
||||
const config = {
|
||||
staticURI,
|
||||
};
|
||||
|
||||
// Grab the locale code from the tenant configuration, if available.
|
||||
let locale: LanguageCode = defaultLocale;
|
||||
if (req.coral && req.coral.tenant) {
|
||||
locale = req.coral.tenant.locale;
|
||||
}
|
||||
|
||||
res.render(
|
||||
"client",
|
||||
{ staticURI, entrypoint, enableCustomCSS, locale, config },
|
||||
(err, html) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Send back the HTML minified.
|
||||
res.send(
|
||||
minify(html, {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export function mountClientRoutes(
|
||||
router: Router,
|
||||
{ staticURI, tenantCache }: MountClientRouteOptions
|
||||
{ staticURI, tenantCache, defaultLocale }: MountClientRouteOptions
|
||||
) {
|
||||
// TODO: (wyattjoh) figure out a better way of referencing paths.
|
||||
// Load the entrypoint manifest.
|
||||
@@ -119,6 +137,7 @@ export function mountClientRoutes(
|
||||
staticURI,
|
||||
enableCustomCSS: true,
|
||||
entrypoint: entrypoints.get("stream"),
|
||||
defaultLocale,
|
||||
})
|
||||
);
|
||||
router.use(
|
||||
@@ -127,6 +146,7 @@ export function mountClientRoutes(
|
||||
staticURI,
|
||||
cacheDuration: false,
|
||||
entrypoint: entrypoints.get("authCallback"),
|
||||
defaultLocale,
|
||||
})
|
||||
);
|
||||
router.use(
|
||||
@@ -135,6 +155,7 @@ export function mountClientRoutes(
|
||||
staticURI,
|
||||
cacheDuration: false,
|
||||
entrypoint: entrypoints.get("auth"),
|
||||
defaultLocale,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -147,6 +168,7 @@ export function mountClientRoutes(
|
||||
staticURI,
|
||||
cacheDuration: false,
|
||||
entrypoint: entrypoints.get("account"),
|
||||
defaultLocale,
|
||||
})
|
||||
);
|
||||
// Add the standalone targets.
|
||||
@@ -158,6 +180,7 @@ export function mountClientRoutes(
|
||||
staticURI,
|
||||
cacheDuration: false,
|
||||
entrypoint: entrypoints.get("admin"),
|
||||
defaultLocale,
|
||||
})
|
||||
);
|
||||
router.use(
|
||||
@@ -171,6 +194,7 @@ export function mountClientRoutes(
|
||||
staticURI,
|
||||
cacheDuration: false,
|
||||
entrypoint: entrypoints.get("install"),
|
||||
defaultLocale,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import cookies from "cookie-parser";
|
||||
import express, { Router } from "express";
|
||||
import { register } from "prom-client";
|
||||
|
||||
import { LanguageCode } from "coral-common/helpers/i18n/locales";
|
||||
import { AppOptions } from "coral-server/app";
|
||||
import { noCacheMiddleware } from "coral-server/app/middleware/cacheHeaders";
|
||||
import playground from "coral-server/app/middleware/playground";
|
||||
@@ -31,6 +32,7 @@ export function createRouter(app: AppOptions, options: RouterOptions) {
|
||||
|
||||
if (!options.disableClientRoutes) {
|
||||
mountClientRoutes(router, {
|
||||
defaultLocale: app.config.get("default_locale") as LanguageCode,
|
||||
// When mounting client routes, we need to provide a staticURI even when
|
||||
// not provided to the default current domain relative "/".
|
||||
staticURI: app.config.get("static_uri") || "/",
|
||||
|
||||
@@ -4,8 +4,24 @@
|
||||
{% block title %}Coral{% endblock %}
|
||||
|
||||
{% block meta %}
|
||||
<script type="application/javascript" id="config">
|
||||
{{ staticURI | dump | safe }}
|
||||
{# Insert the link preload tags here. #}
|
||||
{% if entrypoint.css %}
|
||||
{% for asset in entrypoint.css %}
|
||||
{{ macros.preload(asset.src, "style", prefix = staticURI) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if enableCustomCSS and tenant and tenant.customCSSURL %}
|
||||
{{ macros.preload(tenant.customCSSURL, "style", crossorigin = true) }}
|
||||
{% endif %}
|
||||
{% if entrypoint.js %}
|
||||
{% for asset in entrypoint.js %}
|
||||
{{ macros.preload(asset.src, "script", prefix = staticURI) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{# Insert the staticURI via the configuration object during insertion. #}
|
||||
<script type="application/json" id="config">
|
||||
{{ config | dump | safe }}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -18,9 +34,11 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{# Custom CSS is included after the CSS block so that its overrides will apply #}
|
||||
{% if enableCustomCSS %}
|
||||
{# Custom CSS is included after the CSS block so that its overrides will apply #}
|
||||
{% include "partials/customCSS.html" %}
|
||||
{% if tenant and tenant.customCSSURL %}
|
||||
{{ macros.css(tenant.customCSSURL) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
{% macro css(src, integrity = '', prefix = '') %}
|
||||
{% macro preload(src, htmlAs, prefix = "", crossorigin = false) %}
|
||||
{% if crossorigin %}
|
||||
<link rel="preload" href="{{ prefix }}{{ src }}" as="{{ htmlAs }}" crossorigin>
|
||||
{% else %}
|
||||
<link rel="preload" href="{{ prefix }}{{ src }}" as="{{ htmlAs }}">
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro css(src, integrity = "", prefix = "") %}
|
||||
<link type="text/css" rel="stylesheet" href="{{ prefix }}{{ src }}"/>
|
||||
{# TODO: evaluate when to enable SRI, non-SSL connections cause issues #}
|
||||
{# {% if integrity %}
|
||||
@@ -8,7 +16,7 @@
|
||||
{% endif %} #}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro js(src, integrity = '', prefix = '') %}
|
||||
{% macro js(src, integrity = "", prefix = "") %}
|
||||
<script type="application/javascript" src="{{ prefix }}{{ src }}"></script>
|
||||
{# TODO: evaluate when to enable SRI, non-SSL connections cause issues #}
|
||||
{# {% if false %}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{% import "../macros.html" as macros %}
|
||||
|
||||
{% if tenant and tenant.customCSSURL %}
|
||||
{{ macros.css(tenant.customCSSURL) }}
|
||||
{% endif %}
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="{{ locale | default('en-US', true) }}">
|
||||
<head>
|
||||
{# Meta tags #}
|
||||
<meta charset="utf-8" />
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Kind } from "graphql";
|
||||
|
||||
import Locale from "./locale";
|
||||
|
||||
describe("parseLiteral", () => {
|
||||
it("parses a valid locale from a string", () => {
|
||||
expect(
|
||||
Locale.parseLiteral({
|
||||
kind: Kind.STRING,
|
||||
value: "en-US",
|
||||
})
|
||||
).toBe("en-US");
|
||||
});
|
||||
it("parses an unsupported locale from a string", () => {
|
||||
expect(() =>
|
||||
Locale.parseLiteral({
|
||||
kind: Kind.STRING,
|
||||
value: "xyz",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("throws when not a string", () => {
|
||||
expect(() =>
|
||||
Locale.parseLiteral({
|
||||
kind: Kind.INT,
|
||||
value: "4",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseValue", () => {
|
||||
it("parses a valid locale from a string", () => {
|
||||
expect(Locale.parseValue("en-US")).toBe("en-US");
|
||||
});
|
||||
it("parses an unsupported locale from a string", () => {
|
||||
expect(() => Locale.parseValue("xyz")).toThrow();
|
||||
});
|
||||
it("throws when not a string", () => {
|
||||
expect(() => Locale.parseValue(4)).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { LOCALES } from "coral-common/helpers/i18n";
|
||||
import { GraphQLScalarType } from "graphql";
|
||||
import { Kind } from "graphql/language";
|
||||
|
||||
function assertSupportLocale(locale: string) {
|
||||
if (!LOCALES.includes(locale as any)) {
|
||||
throw new Error(`Supported locales are ${JSON.stringify(LOCALES)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new GraphQLScalarType({
|
||||
name: "Locale",
|
||||
description: "Locale represents a language code in the BCP 47 format.",
|
||||
serialize(value) {
|
||||
return value;
|
||||
},
|
||||
parseValue(value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Locale must be a string in BCP 47 format.");
|
||||
}
|
||||
assertSupportLocale(value);
|
||||
return value;
|
||||
},
|
||||
parseLiteral(ast) {
|
||||
if (ast.kind !== Kind.STRING) {
|
||||
throw new Error("Locale must be a string in BCP 47 format.");
|
||||
}
|
||||
const value = ast.value.toString();
|
||||
assertSupportLocale(value);
|
||||
return value;
|
||||
},
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { LanguageCode } from "coral-common/helpers/i18n/locales";
|
||||
|
||||
import { GQLLOCALES } from "../schema/__generated__/types";
|
||||
import { LOCALES } from "./LOCALES";
|
||||
|
||||
it("does not contain duplicate entries", () => {
|
||||
const seen: Partial<Record<LanguageCode, true>> = {};
|
||||
for (const key in LOCALES) {
|
||||
if (!LOCALES.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = LOCALES[key as GQLLOCALES];
|
||||
expect(value in seen).toBeFalsy();
|
||||
seen[value] = true;
|
||||
}
|
||||
});
|
||||
|
||||
it("contains the correct mappings to the BCP 47 format", () => {
|
||||
for (const key in LOCALES) {
|
||||
if (!LOCALES.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = LOCALES[key as GQLLOCALES];
|
||||
expect(value).toEqual(key.replace(/_/, "-"));
|
||||
}
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import { LanguageCode } from "coral-common/helpers/i18n/locales";
|
||||
|
||||
import { GQLLOCALES } from "../schema/__generated__/types";
|
||||
|
||||
export const LOCALES: Record<GQLLOCALES, LanguageCode> = {
|
||||
en_US: "en-US",
|
||||
pt_BR: "pt-BR",
|
||||
es: "es",
|
||||
de: "de",
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import Cursor from "coral-server/graph/common/scalars/cursor";
|
||||
import Locale from "coral-server/graph/common/scalars/locale";
|
||||
import Time from "coral-server/graph/common/scalars/time";
|
||||
import { GQLResolver } from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
@@ -81,6 +82,7 @@ const Resolvers: GQLResolver = {
|
||||
UsernameHistory,
|
||||
Tag,
|
||||
Time,
|
||||
Locale,
|
||||
User,
|
||||
UserStatus,
|
||||
UsernameStatus,
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
addResolveFunctionsToSchema,
|
||||
attachDirectiveResolvers,
|
||||
IEnumResolver,
|
||||
IResolvers,
|
||||
} from "graphql-tools";
|
||||
import { attachDirectiveResolvers, IResolvers } from "graphql-tools";
|
||||
|
||||
import { loadSchema } from "coral-common/graphql";
|
||||
import auth from "coral-server/graph/common/directives/auth";
|
||||
import resolvers from "coral-server/graph/tenant/resolvers";
|
||||
import { LOCALES } from "coral-server/graph/tenant/resolvers/LOCALES";
|
||||
|
||||
export default function getTenantSchema() {
|
||||
const schema = loadSchema("tenant", resolvers as IResolvers);
|
||||
@@ -16,15 +10,5 @@ export default function getTenantSchema() {
|
||||
// Attach the directive resolvers.
|
||||
attachDirectiveResolvers(schema, { auth });
|
||||
|
||||
// Attach the GraphQL enum fields.
|
||||
addResolveFunctionsToSchema({
|
||||
schema,
|
||||
resolvers: {
|
||||
// For some reason, the resolver doesn't quite work without coercing the
|
||||
// type.
|
||||
LOCALES: LOCALES as IEnumResolver,
|
||||
},
|
||||
});
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ Cursor represents a paginating cursor.
|
||||
"""
|
||||
scalar Cursor
|
||||
|
||||
"""
|
||||
Locale represents a language code in the BCP 47 format.
|
||||
"""
|
||||
scalar Locale
|
||||
|
||||
################################################################################
|
||||
## Actions
|
||||
################################################################################
|
||||
@@ -990,17 +995,6 @@ type StoryMessageBox {
|
||||
## Settings
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
LOCALES list all the supported locales in a modified BCP 47 format, where the
|
||||
hyphen is replaced by an underscore.
|
||||
"""
|
||||
enum LOCALES {
|
||||
en_US
|
||||
pt_BR
|
||||
es
|
||||
de
|
||||
}
|
||||
|
||||
"""
|
||||
CloseCommenting contains settings related to the automatic closing of commenting
|
||||
on Stories.
|
||||
@@ -1124,7 +1118,7 @@ type Settings {
|
||||
"""
|
||||
locale is the specified locale for this Tenant.
|
||||
"""
|
||||
locale: LOCALES!
|
||||
locale: Locale!
|
||||
|
||||
"""
|
||||
live provides configuration options related to live updates for stories on
|
||||
@@ -3371,6 +3365,11 @@ input SettingsInput {
|
||||
accountFeatures specifies the configuration for accounts.
|
||||
"""
|
||||
accountFeatures: CommenterAccountFeaturesInput
|
||||
|
||||
"""
|
||||
locale specifies the locale for this Tenant.
|
||||
"""
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
closeCommentingDefaultMessage = Comments are closed on this story.
|
||||
disableCommentingDefaultMessage = Comments are closed on this story.
|
||||
|
||||
reaction-labelRespect = Respect
|
||||
reaction-labelActiveRespected = Respected
|
||||
reaction-sortLabelMostRespected = Most Respected
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import crypto from "crypto";
|
||||
import { FluentBundle } from "fluent/compat";
|
||||
|
||||
import { GQLReactionConfiguration } from "coral-server/graph/tenant/schema/__generated__/types";
|
||||
import { translate } from "coral-server/services/i18n";
|
||||
|
||||
export const getDefaultReactionConfiguration = (
|
||||
bundle: FluentBundle
|
||||
): GQLReactionConfiguration => ({
|
||||
// By default, the standard reaction style will use the Respect with the
|
||||
// handshake.
|
||||
label: translate(bundle, "Respect", "reaction-labelRespect"),
|
||||
labelActive: translate(bundle, "Respected", "reaction-labelActiveRespected"),
|
||||
sortLabel: translate(
|
||||
bundle,
|
||||
"Most Respected",
|
||||
"reaction-sortLabelMostRespected"
|
||||
),
|
||||
icon: "thumb_up",
|
||||
});
|
||||
|
||||
export function generateSSOKey() {
|
||||
// Generate a new key. We generate a key of minimum length 32 up to 37 bytes,
|
||||
// as 16 was the minimum length recommended.
|
||||
//
|
||||
// Reference: https://security.stackexchange.com/a/96176
|
||||
return crypto.randomBytes(32 + Math.floor(Math.random() * 5)).toString("hex");
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./tenant";
|
||||
export * from "./helpers";
|
||||
@@ -1,4 +1,3 @@
|
||||
import crypto from "crypto";
|
||||
import { Db } from "mongodb";
|
||||
import uuid from "uuid";
|
||||
|
||||
@@ -14,6 +13,8 @@ import {
|
||||
createIndexFactory,
|
||||
} from "coral-server/models/helpers";
|
||||
import { Settings } from "coral-server/models/settings";
|
||||
import { I18n } from "coral-server/services/i18n";
|
||||
import { generateSSOKey, getDefaultReactionConfiguration } from "./helpers";
|
||||
|
||||
const collection = createCollection<Tenant>("tenants");
|
||||
|
||||
@@ -72,6 +73,7 @@ export type CreateTenantInput = Pick<
|
||||
*/
|
||||
export async function createTenant(
|
||||
mongo: Db,
|
||||
i18n: I18n,
|
||||
input: CreateTenantInput,
|
||||
now = new Date()
|
||||
) {
|
||||
@@ -178,14 +180,7 @@ export async function createTenant(
|
||||
doNotStore: true,
|
||||
},
|
||||
},
|
||||
reaction: {
|
||||
// By default, the standard reaction style will use the Respect with the
|
||||
// handshake.
|
||||
label: "Respect",
|
||||
labelActive: "Respected",
|
||||
sortLabel: "Most Respected",
|
||||
icon: "thumb_up",
|
||||
},
|
||||
reaction: getDefaultReactionConfiguration(i18n.getBundle(input.locale)),
|
||||
stories: {
|
||||
scraping: {
|
||||
enabled: true,
|
||||
@@ -281,14 +276,6 @@ export async function updateTenant(
|
||||
return result.value || null;
|
||||
}
|
||||
|
||||
function generateSSOKey() {
|
||||
// Generate a new key. We generate a key of minimum length 32 up to 37 bytes,
|
||||
// as 16 was the minimum length recommended.
|
||||
//
|
||||
// Reference: https://security.stackexchange.com/a/96176
|
||||
return crypto.randomBytes(32 + Math.floor(Math.random() * 5)).toString("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* regenerateTenantSSOKey will regenerate the SSO key used for Single Sing-On
|
||||
* for the specified Tenant. All existing user sessions signed with the old
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Tenant,
|
||||
updateTenant,
|
||||
} from "coral-server/models/tenant";
|
||||
import { I18n } from "coral-server/services/i18n";
|
||||
|
||||
import TenantCache from "./cache";
|
||||
|
||||
@@ -55,6 +56,7 @@ export async function install(
|
||||
mongo: Db,
|
||||
redis: Redis,
|
||||
cache: TenantCache,
|
||||
i18n: I18n,
|
||||
input: InstallTenant,
|
||||
now = new Date()
|
||||
) {
|
||||
@@ -64,12 +66,10 @@ export async function install(
|
||||
|
||||
// TODO: (wyattjoh) perform any pending migrations.
|
||||
|
||||
// TODO: (wyattjoh) setup database indexes.
|
||||
|
||||
logger.info({ tenant: input }, "installing tenant");
|
||||
|
||||
// Create the Tenant.
|
||||
const tenant = await createTenant(mongo, input, now);
|
||||
const tenant = await createTenant(mongo, i18n, input, now);
|
||||
|
||||
// Update the tenant cache.
|
||||
await cache.update(redis, tenant);
|
||||
|
||||
Reference in New Issue
Block a user