mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) add appsumo endpoints with stub implementations
Summary: This adds appsumo /token and /notification endpoints, with some tests. The stub implementation is sufficient for AppSumo activation to succeed (when exposed via port forwarding for testing). It needs fleshing out: * Implement upgrade/downgrade/refund and stripe subscription. * Implement custom landing page and flow. Test Plan: added tests Reviewers: dsagal, georgegevoian Reviewed By: dsagal Subscribers: alexmojaki Differential Revision: https://phab.getgrist.com/D2864
This commit is contained in:
@@ -13,7 +13,7 @@ import {ANONYMOUS_USER_EMAIL, DocumentProperties, EVERYONE_EMAIL,
|
||||
WorkspaceProperties} from "app/common/UserAPI";
|
||||
import {AclRule, AclRuleDoc, AclRuleOrg, AclRuleWs} from "app/gen-server/entity/AclRule";
|
||||
import {Alias} from "app/gen-server/entity/Alias";
|
||||
import {BillingAccount} from "app/gen-server/entity/BillingAccount";
|
||||
import {BillingAccount, ExternalBillingOptions} from "app/gen-server/entity/BillingAccount";
|
||||
import {BillingAccountManager} from "app/gen-server/entity/BillingAccountManager";
|
||||
import {Document} from "app/gen-server/entity/Document";
|
||||
import {Group} from "app/gen-server/entity/Group";
|
||||
@@ -522,7 +522,10 @@ export class HomeDBManager extends EventEmitter {
|
||||
// Add a personal organization for this user.
|
||||
// We don't add a personal org for anonymous/everyone/previewer "users" as it could
|
||||
// get a bit confusing.
|
||||
const result = await this.addOrg(user, {name: "Personal"}, true, true, manager);
|
||||
const result = await this.addOrg(user, {name: "Personal"}, {
|
||||
setUserAsOwner: true,
|
||||
useNewPlan: true
|
||||
}, manager);
|
||||
if (result.status !== 200) {
|
||||
throw new Error(result.errMessage);
|
||||
}
|
||||
@@ -744,6 +747,18 @@ export class HomeDBManager extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up an org by an external id. External IDs are used in integrations, and
|
||||
* simply offer an alternate way to identify an org.
|
||||
*/
|
||||
public async getOrgByExternalId(externalId: string): Promise<Organization|undefined> {
|
||||
const query = this._orgs()
|
||||
.leftJoinAndSelect('orgs.billingAccount', 'billing_accounts')
|
||||
.leftJoinAndSelect('billing_accounts.product', 'products')
|
||||
.where('external_id = :externalId', {externalId});
|
||||
return query.getOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a QueryResult for an organization with nested workspaces.
|
||||
*/
|
||||
@@ -1077,7 +1092,10 @@ export class HomeDBManager extends EventEmitter {
|
||||
*
|
||||
*/
|
||||
public async addOrg(user: User, props: Partial<OrganizationProperties>,
|
||||
setUserAsOwner: boolean, useNewPlan: boolean,
|
||||
options: { setUserAsOwner: boolean,
|
||||
useNewPlan: boolean,
|
||||
externalId?: string,
|
||||
externalOptions?: ExternalBillingOptions },
|
||||
transaction?: EntityManager): Promise<QueryResult<number>> {
|
||||
const notifications: Array<() => void> = [];
|
||||
const name = props.name;
|
||||
@@ -1102,18 +1120,18 @@ export class HomeDBManager extends EventEmitter {
|
||||
// Create or find a billing account to associate with this org.
|
||||
const billingAccountEntities = [];
|
||||
let billingAccount;
|
||||
if (useNewPlan) {
|
||||
if (options.useNewPlan) {
|
||||
const productNames = getDefaultProductNames();
|
||||
let productName = setUserAsOwner ? productNames.personal : productNames.teamInitial;
|
||||
let productName = options.setUserAsOwner ? productNames.personal : productNames.teamInitial;
|
||||
// A bit fragile: this is called during creation of support@ user, before
|
||||
// getSupportUserId() is available, but with setUserAsOwner of true.
|
||||
if (!setUserAsOwner && user.id === this.getSupportUserId()) {
|
||||
if (!options.setUserAsOwner && user.id === this.getSupportUserId()) {
|
||||
// For teams created by support@getgrist.com, set the product to something
|
||||
// good so payment not needed. This is useful for testing.
|
||||
productName = productNames.team;
|
||||
}
|
||||
billingAccount = new BillingAccount();
|
||||
billingAccount.individual = setUserAsOwner;
|
||||
billingAccount.individual = options.setUserAsOwner;
|
||||
const dbProduct = await manager.findOne(Product, {name: productName});
|
||||
if (!dbProduct) {
|
||||
throw new Error('Cannot find product for new organization');
|
||||
@@ -1124,6 +1142,13 @@ export class HomeDBManager extends EventEmitter {
|
||||
billingAccountManager.user = user;
|
||||
billingAccountManager.billingAccount = billingAccount;
|
||||
billingAccountEntities.push(billingAccountManager);
|
||||
if (options.externalId) {
|
||||
// save will fail if externalId is a duplicate.
|
||||
billingAccount.externalId = options.externalId;
|
||||
}
|
||||
if (options.externalOptions) {
|
||||
billingAccount.externalOptions = options.externalOptions;
|
||||
}
|
||||
} else {
|
||||
// Use the billing account from the user's personal org to start with.
|
||||
billingAccount = await manager.createQueryBuilder()
|
||||
@@ -1132,6 +1157,9 @@ export class HomeDBManager extends EventEmitter {
|
||||
.leftJoinAndSelect('billing_accounts.orgs', 'orgs')
|
||||
.where('orgs.owner_id = :userId', {userId: user.id})
|
||||
.getOne();
|
||||
if (options.externalId && billingAccount?.externalId !== options.externalId) {
|
||||
throw new ApiError('Conflicting external identifier', 400);
|
||||
}
|
||||
if (!billingAccount) {
|
||||
throw new ApiError('Cannot find an initial plan for organization', 500);
|
||||
}
|
||||
@@ -1144,7 +1172,7 @@ export class HomeDBManager extends EventEmitter {
|
||||
if (domain) {
|
||||
org.domain = domain;
|
||||
}
|
||||
if (setUserAsOwner) {
|
||||
if (options.setUserAsOwner) {
|
||||
org.owner = user;
|
||||
}
|
||||
// Create the special initial permission groups for the new org.
|
||||
@@ -1180,7 +1208,7 @@ export class HomeDBManager extends EventEmitter {
|
||||
// count are not checked, this will succeed unconditionally.
|
||||
await this._doAddWorkspace(savedOrg, {name: 'Home'}, manager);
|
||||
|
||||
if (!setUserAsOwner) {
|
||||
if (!options.setUserAsOwner) {
|
||||
// This user just made a team site (once this transaction is applied).
|
||||
// Emit a notification.
|
||||
notifications.push(this._teamCreatorNotification(user.id));
|
||||
@@ -1248,6 +1276,14 @@ export class HomeDBManager extends EventEmitter {
|
||||
if (org.owner) {
|
||||
throw new ApiError('Cannot set a domain for a personal organization', 400);
|
||||
}
|
||||
try {
|
||||
checkSubdomainValidity(props.domain);
|
||||
} catch (e) {
|
||||
return {
|
||||
status: 400,
|
||||
errMessage: `Domain is not permitted: ${e.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
org.updateFromProperties(props);
|
||||
await manager.save(org);
|
||||
|
||||
Reference in New Issue
Block a user