mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add basic activation page to grist-ee
Summary: Adds an activation page to grist-ee that currently shows activation status. Follow-up diffs will introduce additional functionality, such as the ability to enter activation keys directly from the activation page. Test Plan: No grist-ee tests (yet). Reviewers: paulfitz Reviewed By: paulfitz Subscribers: dsagal, paulfitz Differential Revision: https://phab.getgrist.com/D3582
This commit is contained in:
@@ -4,11 +4,9 @@ import {encodeUrl, getSlugIfNeeded, GristLoadConfig, IGristUrlState, isOrgInPath
|
||||
parseSubdomain, sanitizePathTail} from 'app/common/gristUrls';
|
||||
import {getOrgUrlInfo} from 'app/common/gristUrls';
|
||||
import {UserProfile} from 'app/common/LoginSessionAPI';
|
||||
import {BillingTask} from 'app/common/BillingAPI';
|
||||
import {TEAM_FREE_PLAN, TEAM_PLAN} from 'app/common/Features';
|
||||
import {tbind} from 'app/common/tbind';
|
||||
import * as version from 'app/common/version';
|
||||
import {ApiServer, getOrgFromRequest} from 'app/gen-server/ApiServer';
|
||||
import {ApiServer} from 'app/gen-server/ApiServer';
|
||||
import {Document} from "app/gen-server/entity/Document";
|
||||
import {Organization} from "app/gen-server/entity/Organization";
|
||||
import {Workspace} from 'app/gen-server/entity/Workspace';
|
||||
@@ -48,8 +46,7 @@ import {IPermitStore} from 'app/server/lib/Permit';
|
||||
import {getAppPathTo, getAppRoot, getUnpackedAppRoot} from 'app/server/lib/places';
|
||||
import {addPluginEndpoints, limitToPlugins} from 'app/server/lib/PluginEndpoint';
|
||||
import {PluginManager} from 'app/server/lib/PluginManager';
|
||||
import {
|
||||
adaptServerUrl, addOrgToPath, addPermit, getOrgUrl, getOriginUrl, getScope, optStringParam,
|
||||
import {adaptServerUrl, addOrgToPath, getOrgUrl, getOriginUrl, getScope, optStringParam,
|
||||
RequestWithGristInfo, stringParam, TEST_HTTPS_OFFSET, trustOrigin} from 'app/server/lib/requestUtils';
|
||||
import {ISendAppPageOptions, makeGristConfig, makeMessagePage, makeSendAppPage} from 'app/server/lib/sendAppPage';
|
||||
import {getDatabaseUrl, listenPromise} from 'app/server/lib/serverUtils';
|
||||
@@ -616,7 +613,7 @@ export class FlexServer implements GristServer {
|
||||
public addBillingApi() {
|
||||
if (this._check('billing-api', 'homedb', 'json', 'api-mw')) { return; }
|
||||
this._getBilling();
|
||||
this._billing.addEndpoints(this.app, this);
|
||||
this._billing.addEndpoints(this.app);
|
||||
this._billing.addEventHandlers();
|
||||
}
|
||||
|
||||
@@ -1147,55 +1144,8 @@ export class FlexServer implements GristServer {
|
||||
this._redirectToLoginWithoutExceptionsMiddleware
|
||||
];
|
||||
|
||||
function getPrefix(req: express.Request) {
|
||||
const org = getOrgFromRequest(req);
|
||||
if (!org) {
|
||||
return getOriginUrl(req);
|
||||
}
|
||||
const prefix = isOrgInPathOnly(req.hostname) ? `/o/${org}` : '';
|
||||
return prefix;
|
||||
}
|
||||
|
||||
// Add billing summary page (.../billing)
|
||||
this.app.get('/billing', ...middleware, expressWrap(async (req, resp, next) => {
|
||||
const mreq = req as RequestWithLogin;
|
||||
const orgDomain = mreq.org;
|
||||
if (!orgDomain) {
|
||||
return this._sendAppPage(req, resp, {path: 'error.html', status: 404, config: {errPage: 'not-found'}});
|
||||
}
|
||||
// Allow the support user access to billing pages.
|
||||
const scope = addPermit(getScope(mreq), this._dbManager.getSupportUserId(), {org: orgDomain});
|
||||
const query = await this._dbManager.getOrg(scope, orgDomain);
|
||||
const org = this._dbManager.unwrapQueryResult(query);
|
||||
// This page isn't available for personal site.
|
||||
if (org.owner) {
|
||||
return this._sendAppPage(req, resp, {path: 'error.html', status: 404, config: {errPage: 'not-found'}});
|
||||
}
|
||||
return this._sendAppPage(req, resp, {path: 'billing.html', status: 200, config: {}});
|
||||
}));
|
||||
|
||||
this.app.get('/billing/payment', ...middleware, expressWrap(async (req, resp, next) => {
|
||||
const task = (optStringParam(req.query.billingTask) || '') as BillingTask;
|
||||
if (!BillingTask.guard(task)) {
|
||||
// If the payment task are invalid, redirect to the summary page.
|
||||
return resp.redirect(getOriginUrl(req) + `/billing`);
|
||||
} else {
|
||||
return this._sendAppPage(req, resp, {path: 'billing.html', status: 200, config: {}});
|
||||
}
|
||||
}));
|
||||
|
||||
// New landing page for the new NEW_DEAL.
|
||||
this.app.get('/billing/create-team', ...middleware, expressWrap(async (req, resp, next) => {
|
||||
const planType = optStringParam(req.query.planType) || '';
|
||||
// Currently we have hardcoded support only for those two plans.
|
||||
const supportedPlans = [TEAM_PLAN, TEAM_FREE_PLAN];
|
||||
if (!supportedPlans.includes(planType)) {
|
||||
return this._sendAppPage(req, resp, {path: 'error.html', status: 404, config: {errPage: 'not-found'}});
|
||||
}
|
||||
// Redirect to home page with url params
|
||||
const url = `${getPrefix(req)}/?planType=${planType}#create-team`;
|
||||
return resp.redirect(url);
|
||||
}));
|
||||
this._getBilling();
|
||||
this._billing.addPages(this.app, middleware);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as express from 'express';
|
||||
import {GristServer} from 'app/server/lib/GristServer';
|
||||
|
||||
export interface IBilling {
|
||||
addEndpoints(app: express.Express, server: GristServer): void;
|
||||
addEndpoints(app: express.Express): void;
|
||||
addEventHandlers(): void;
|
||||
addWebhooks(app: express.Express): void;
|
||||
addMiddleware?(app: express.Express): Promise<void>;
|
||||
addPages(app: express.Express, middleware: express.RequestHandler[]): void;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {INotifier} from 'app/server/lib/INotifier';
|
||||
import {ISandbox, ISandboxCreationOptions} from 'app/server/lib/ISandbox';
|
||||
import {IShell} from 'app/server/lib/IShell';
|
||||
import {createSandbox} from 'app/server/lib/NSandbox';
|
||||
import * as express from 'express';
|
||||
|
||||
export interface ICreate {
|
||||
|
||||
@@ -48,35 +47,37 @@ export interface ICreateNotifierOptions {
|
||||
create(dbManager: HomeDBManager, gristConfig: GristServer): INotifier|undefined;
|
||||
}
|
||||
|
||||
export interface ICreateBillingOptions {
|
||||
create(dbManager: HomeDBManager, gristConfig: GristServer): IBilling|undefined;
|
||||
}
|
||||
|
||||
export function makeSimpleCreator(opts: {
|
||||
sessionSecret?: string,
|
||||
storage?: ICreateStorageOptions[],
|
||||
activationMiddleware?: (db: HomeDBManager, app: express.Express) => Promise<void>,
|
||||
billing?: ICreateBillingOptions,
|
||||
notifier?: ICreateNotifierOptions,
|
||||
}): ICreate {
|
||||
const {sessionSecret, storage, notifier, billing} = opts;
|
||||
return {
|
||||
Billing(db) {
|
||||
return {
|
||||
Billing(dbManager, gristConfig) {
|
||||
return billing?.create(dbManager, gristConfig) ?? {
|
||||
addEndpoints() { /* do nothing */ },
|
||||
addEventHandlers() { /* do nothing */ },
|
||||
addWebhooks() { /* do nothing */ },
|
||||
async addMiddleware(app) {
|
||||
// add activation middleware, if needed.
|
||||
return opts?.activationMiddleware?.(db, app);
|
||||
}
|
||||
async addMiddleware() { /* do nothing */ },
|
||||
addPages() { /* do nothing */ },
|
||||
};
|
||||
},
|
||||
Notifier(dbManager, gristConfig) {
|
||||
const {notifier} = opts;
|
||||
return notifier?.create(dbManager, gristConfig) ?? {
|
||||
get testPending() { return false; },
|
||||
deleteUser() { throw new Error('deleteUser unavailable'); },
|
||||
};
|
||||
},
|
||||
ExternalStorage(purpose, extraPrefix) {
|
||||
for (const storage of opts.storage || []) {
|
||||
if (storage.check()) {
|
||||
return storage.create(purpose, extraPrefix);
|
||||
for (const s of storage || []) {
|
||||
if (s.check()) {
|
||||
return s.create(purpose, extraPrefix);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -85,15 +86,15 @@ export function makeSimpleCreator(opts: {
|
||||
return createSandbox('unsandboxed', options);
|
||||
},
|
||||
sessionSecret() {
|
||||
const secret = process.env.GRIST_SESSION_SECRET || opts.sessionSecret;
|
||||
const secret = process.env.GRIST_SESSION_SECRET || sessionSecret;
|
||||
if (!secret) {
|
||||
throw new Error('need GRIST_SESSION_SECRET');
|
||||
}
|
||||
return secret;
|
||||
},
|
||||
async configure() {
|
||||
for (const storage of opts.storage || []) {
|
||||
if (storage.check()) { break; }
|
||||
for (const s of storage || []) {
|
||||
if (s.check()) { break; }
|
||||
}
|
||||
},
|
||||
getExtraHeadHtml() {
|
||||
|
||||
@@ -54,7 +54,7 @@ export function makeGristConfig(homeUrl: string|null, extra: Partial<GristLoadCo
|
||||
enableWidgetRepository: Boolean(process.env.GRIST_WIDGET_LIST_URL),
|
||||
survey: Boolean(process.env.DOC_ID_NEW_USER_INFO),
|
||||
tagManagerId: process.env.GOOGLE_TAG_MANAGER_ID,
|
||||
activation: (mreq as RequestWithLogin|undefined)?.activation,
|
||||
activation: getActivation(req as RequestWithLogin | undefined),
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
@@ -184,3 +184,11 @@ function getDocFromConfig(config: GristLoadConfig): Document | null {
|
||||
|
||||
return config.getDoc[config.assignmentId] ?? null;
|
||||
}
|
||||
|
||||
function getActivation(mreq: RequestWithLogin|undefined) {
|
||||
const defaultEmail = process.env.GRIST_DEFAULT_EMAIL;
|
||||
return {
|
||||
...mreq?.activation,
|
||||
isManager: Boolean(defaultEmail && defaultEmail === mreq?.user?.loginEmail),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user