(core) Adding fixSiteProducts that changes orgs from teamFree to Free product if it was set be default

Summary:
After release on 2024-06-12 (1.1.15) the GRIST_DEFAULT_PRODUCT env variable wasn't respected by the
method that started the server in single org mode. In all deployments (apart from saas), the default product
used for new sites is set to `Free`, but the code that starts the server enforced `teamFree` product.

This change adds a fix routine that fixes this issue by rewriting team sites from `teamFree` product to `Free`
product only if:
- The default product is set to `Free`
- The deployment type is something other then 'saas'.

Additionally there is a test that will fail after 2024.10.01, as this fix should be removed before this date.

Test Plan: Added test

Reviewers: paulfitz

Reviewed By: paulfitz

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D4272
This commit is contained in:
Jarosław Sadziński
2024-06-18 12:12:20 +02:00
parent 14a868c460
commit 76d94483ad
3 changed files with 232 additions and 0 deletions

View File

@@ -1,8 +1,10 @@
import { ApiError } from 'app/common/ApiError';
import { delay } from 'app/common/delay';
import { buildUrlId } from 'app/common/gristUrls';
import { BillingAccount } from 'app/gen-server/entity/BillingAccount';
import { Document } from 'app/gen-server/entity/Document';
import { Organization } from 'app/gen-server/entity/Organization';
import { Product } from 'app/gen-server/entity/Product';
import { Workspace } from 'app/gen-server/entity/Workspace';
import { HomeDBManager, Scope } from 'app/gen-server/lib/HomeDBManager';
import { fromNow } from 'app/gen-server/sqlUtils';
@@ -462,3 +464,79 @@ async function forEachWithBreaks<T>(logText: string, items: T[], callback: (item
}
log.rawInfo(logText, {itemsProcesssed, itemsTotal, timeMs: Date.now() - start});
}
/**
* For a brief moment file `stubs/app/server/server.ts` was ignoring the GRIST_DEFAULT_PRODUCT
* variable, which is currently set for all deployment types to 'Free' product. As a result orgs
* created after 2024-06-12 (1.1.15) were created with 'teamFree' product instead of 'Free'.
* It only affected deployments that were using:
* - GRIST_DEFAULT_PRODUCT variable set to 'Free'
* - GRIST_SINGLE_ORG set to enforce single org mode.
*
* This method fixes the product for all orgs created with 'teamFree' product, if the default
* product that should be used is 'Free' and the deployment type is not 'saas' ('saas' deployment
* isn't using GRIST_DEFAULT_PRODUCT variable). This method should be removed after 2024.10.01.
*
* There is a corresponding test that will fail if this method (and that test) are not removed.
*
* @returns true if the method was run, false otherwise.
*/
export async function fixSiteProducts(options: {
deploymentType: string,
db: HomeDBManager,
dry?: boolean,
}) {
const {deploymentType, dry, db} = options;
const hasDefaultProduct = () => Boolean(process.env.GRIST_DEFAULT_PRODUCT);
const defaultProductIsFree = () => process.env.GRIST_DEFAULT_PRODUCT === 'Free';
const notSaasDeployment = () => deploymentType !== 'saas';
const mustRun = hasDefaultProduct() && defaultProductIsFree() && notSaasDeployment();
if (!mustRun) {
return false;
}
const removeMeDate = new Date('2024-10-01');
const warningMessage = `WARNING: This method should be removed after ${removeMeDate.toDateString()}.`;
if (new Date() > removeMeDate) {
console.warn(warningMessage);
}
// Find all billing accounts on teamFree product and change them to the Free.
return await db.connection.transaction(async (t) => {
const freeProduct = await t.findOne(Product, {where: {name: 'Free'}});
const freeTeamProduct = await t.findOne(Product, {where: {name: 'teamFree'}});
if (!freeTeamProduct) {
console.warn('teamFree product not found.');
return false;
}
if (!freeProduct) {
console.warn('Free product not found.');
return false;
}
if (dry) {
await t.createQueryBuilder()
.select('ba')
.from(BillingAccount, 'ba')
.where('ba.product = :productId', {productId: freeTeamProduct.id})
.getMany()
.then((accounts) => {
accounts.forEach(a => {
console.log(`Would change account ${a.id} from ${a.product.id} to ${freeProduct.id}`);
});
});
} else {
await t.createQueryBuilder()
.update(BillingAccount)
.set({product: freeProduct.id})
.where({product: freeTeamProduct.id})
.execute();
}
return true;
});
}