Twenty standard and workspace custom applications 1/3 (#15625)
# Introduction related to https://github.com/twentyhq/core-team-issues/issues/1833 In this PR we're starting the sync-metadata and standardIds deprecation by introducing `twenty-standard` application that will regroup every standard object such as company and opportunities. But also the `custom-workspace-application` which is an app created at the same time as a workspace and that will regroup everything configure within the workspace ( custom objects fields etc ) ## What's done On both new workspace and seeded workspace creation: - Creating a custom workspace app - Creating a twenty standard app - Refactored the seed core schema and workspace creation to be run within a transaction in order to handle circular dependency foreignkey requirements ( which is deferred for app toward workspace ) - Updated workspace entity to have a custom workspace relation ( nullable for the moment until we implem an upgrade command to handle retro comp ) - Integration testing on user, workspace creation deletion and expected default apps creation - ~~Soft deleted user on `deleteUser`~~ Done by marie and rebased on it ## What's next - Update seeder to propagate the `twenty-standard` workspace `applicationId` to every standard synchronized entities ( cheap and fast iteration through the about to be deprecated sync-metadata as an easy way to synchronize standards metadata entities ). - Update seeder to propagate the `custom-workspace-application` workspace `applicationId` to anything custom ( `pets` and `rockets` ) - Prepend `custom-workspace-application` `applicationId` to every metadata API operations ( create a specific cache etc ) - Upgrade command on all existing workspace to create a custom app and associate its applicationId to any existing custom entities - Make `universalIdentifier` and `applicationId` required for any syncable entity
This commit is contained in:
+13
-7
@@ -1,13 +1,19 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
const tableName = 'billingCustomer';
|
||||
|
||||
export const seedBillingCustomers = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
type SeedBillingCustomersArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const seedBillingCustomers = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedBillingCustomersArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, ['workspaceId', 'stripeCustomerId'])
|
||||
|
||||
+13
-7
@@ -1,13 +1,19 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
const tableName = 'billingSubscription';
|
||||
|
||||
export const seedBillingSubscriptions = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
type SeedBillingSubscriptionsArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const seedBillingSubscriptions = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedBillingSubscriptionsArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, [
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
export const WORKSPACE_FIELDS_TO_SEED = [
|
||||
'id',
|
||||
'displayName',
|
||||
'subdomain',
|
||||
'inviteHash',
|
||||
'logo',
|
||||
'activationStatus',
|
||||
'isTwoFactorAuthenticationEnforced',
|
||||
'version',
|
||||
'workspaceCustomApplicationId',
|
||||
] as const satisfies (keyof WorkspaceEntity)[];
|
||||
|
||||
export type CreateWorkspaceInput = Pick<
|
||||
WorkspaceEntity,
|
||||
(typeof WORKSPACE_FIELDS_TO_SEED)[number]
|
||||
>;
|
||||
|
||||
export const SEED_APPLE_WORKSPACE_ID = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
export const SEED_YCOMBINATOR_WORKSPACE_ID =
|
||||
'3b8e6458-5fc1-4e63-8563-008ccddaa6db';
|
||||
|
||||
export type SeededWorkspacesIds =
|
||||
| typeof SEED_APPLE_WORKSPACE_ID
|
||||
| typeof SEED_YCOMBINATOR_WORKSPACE_ID;
|
||||
|
||||
export const SEEDER_CREATE_WORKSPACE_INPUT = {
|
||||
[SEED_APPLE_WORKSPACE_ID]: {
|
||||
id: SEED_APPLE_WORKSPACE_ID,
|
||||
displayName: 'Apple',
|
||||
subdomain: 'apple',
|
||||
inviteHash: 'apple.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/apple-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.PENDING_CREATION, // will be set to active after default role creation
|
||||
isTwoFactorAuthenticationEnforced: false,
|
||||
},
|
||||
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
|
||||
id: SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
displayName: 'YCombinator',
|
||||
subdomain: 'yc',
|
||||
inviteHash: 'yc.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/ycombinator-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.PENDING_CREATION, // will be set to active after default role creation
|
||||
isTwoFactorAuthenticationEnforced: false,
|
||||
},
|
||||
} as const satisfies Record<
|
||||
SeededWorkspacesIds,
|
||||
Omit<CreateWorkspaceInput, 'version' | 'workspaceCustomApplicationId'>
|
||||
>;
|
||||
+4
-4
@@ -12,14 +12,14 @@ import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import {
|
||||
RANDOM_USER_WORKSPACE_IDS,
|
||||
USER_WORKSPACE_DATA_SEED_IDS,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
import { API_KEY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/api-key-data-seeds.constant';
|
||||
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
|
||||
|
||||
|
||||
+53
-25
@@ -1,11 +1,11 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { AgentChatMessageRole } from 'src/engine/metadata-modules/agent/agent-chat-message.entity';
|
||||
import { USER_WORKSPACE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { USER_WORKSPACE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
|
||||
const agentChatThreadTableName = 'agentChatThread';
|
||||
const agentChatMessageTableName = 'agentChatMessage';
|
||||
@@ -43,11 +43,17 @@ export const AGENT_CHAT_MESSAGE_PART_DATA_SEED_IDS = {
|
||||
YCOMBINATOR_MESSAGE_4_PART_1: '20202020-0000-4000-8000-000000000054',
|
||||
};
|
||||
|
||||
const seedChatThreads = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
type SeedChatThreadsArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
const seedChatThreads = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedChatThreadsArgs) => {
|
||||
let threadId: string;
|
||||
let userWorkspaceId: string;
|
||||
|
||||
@@ -65,7 +71,7 @@ const seedChatThreads = async (
|
||||
|
||||
const now = new Date();
|
||||
|
||||
await dataSource
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${agentChatThreadTableName}`, [
|
||||
@@ -88,12 +94,19 @@ const seedChatThreads = async (
|
||||
return threadId;
|
||||
};
|
||||
|
||||
const seedChatMessages = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
threadId: string,
|
||||
) => {
|
||||
type SeedChatMessagesArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
threadId: string;
|
||||
};
|
||||
|
||||
const seedChatMessages = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
threadId,
|
||||
}: SeedChatMessagesArgs) => {
|
||||
let messageIds: string[];
|
||||
let partIds: string[];
|
||||
let messages: Array<{
|
||||
@@ -274,7 +287,7 @@ const seedChatMessages = async (
|
||||
);
|
||||
}
|
||||
|
||||
await dataSource
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${agentChatMessageTableName}`, [
|
||||
@@ -287,7 +300,7 @@ const seedChatMessages = async (
|
||||
.values(messages)
|
||||
.execute();
|
||||
|
||||
await dataSource
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${agentChatMessagePartTableName}`, [
|
||||
@@ -303,12 +316,27 @@ const seedChatMessages = async (
|
||||
.execute();
|
||||
};
|
||||
|
||||
export const seedAgents = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
const threadId = await seedChatThreads(dataSource, schemaName, workspaceId);
|
||||
|
||||
await seedChatMessages(dataSource, schemaName, workspaceId, threadId);
|
||||
type SeedAgentsArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const seedAgents = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedAgentsArgs) => {
|
||||
const threadId = await seedChatThreads({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await seedChatMessages({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
threadId,
|
||||
});
|
||||
};
|
||||
|
||||
+13
-7
@@ -1,15 +1,21 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { API_KEY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/api-key-data-seeds.constant';
|
||||
|
||||
const tableName = 'apiKey';
|
||||
|
||||
export const seedApiKeys = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
type SeedApiKeysArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const seedApiKeys = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedApiKeysArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, [
|
||||
|
||||
+71
-18
@@ -1,18 +1,26 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
|
||||
import { type ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { seedBillingCustomers } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-customers.util';
|
||||
import { seedBillingSubscriptions } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-subscriptions.util';
|
||||
import {
|
||||
type SeededWorkspacesIds,
|
||||
SEEDER_CREATE_WORKSPACE_INPUT,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { seedAgents } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util';
|
||||
import { seedApiKeys } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-api-keys.util';
|
||||
import { seedFeatureFlags } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util';
|
||||
import { seedUserWorkspaces } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
import { seedUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
import { seedWorkspaces } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
import { createWorkspace } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspace.util';
|
||||
import { computeWorkspaceCustomCreateApplicationInput } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-workspace-custom-create-application-input';
|
||||
import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch';
|
||||
|
||||
type SeedCoreSchemaArgs = {
|
||||
dataSource: DataSource;
|
||||
workspaceId: string;
|
||||
workspaceId: SeededWorkspacesIds;
|
||||
appVersion: string | undefined;
|
||||
applicationService: ApplicationService;
|
||||
seedBilling?: boolean;
|
||||
seedFeatureFlags?: boolean;
|
||||
};
|
||||
@@ -21,30 +29,75 @@ export const seedCoreSchema = async ({
|
||||
appVersion,
|
||||
dataSource,
|
||||
workspaceId,
|
||||
applicationService,
|
||||
seedBilling = true,
|
||||
seedFeatureFlags: shouldSeedFeatureFlags = true,
|
||||
}: SeedCoreSchemaArgs) => {
|
||||
const schemaName = 'core';
|
||||
|
||||
await seedWorkspaces({
|
||||
dataSource,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
appVersion,
|
||||
});
|
||||
await seedUsers(dataSource, schemaName);
|
||||
await seedUserWorkspaces(dataSource, schemaName, workspaceId);
|
||||
const createWorkspaceStaticInput = SEEDER_CREATE_WORKSPACE_INPUT[workspaceId];
|
||||
const workspaceCustomApplicationCreateInput =
|
||||
computeWorkspaceCustomCreateApplicationInput({
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
displayName: createWorkspaceStaticInput.displayName,
|
||||
},
|
||||
});
|
||||
|
||||
await seedAgents(dataSource, schemaName, workspaceId);
|
||||
const version = extractVersionMajorMinorPatch(appVersion);
|
||||
const queryRunner = dataSource.createQueryRunner();
|
||||
|
||||
await seedApiKeys(dataSource, schemaName, workspaceId);
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
if (shouldSeedFeatureFlags) {
|
||||
await seedFeatureFlags(dataSource, schemaName, workspaceId);
|
||||
}
|
||||
try {
|
||||
const customWorkspaceApplication = await applicationService.create(
|
||||
{
|
||||
...workspaceCustomApplicationCreateInput,
|
||||
serverlessFunctionLayerId: null,
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
if (seedBilling) {
|
||||
await seedBillingCustomers(dataSource, schemaName, workspaceId);
|
||||
await seedBillingSubscriptions(dataSource, schemaName, workspaceId);
|
||||
await createWorkspace({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
createWorkspaceInput: {
|
||||
...createWorkspaceStaticInput,
|
||||
version,
|
||||
workspaceCustomApplicationId: customWorkspaceApplication.id,
|
||||
},
|
||||
});
|
||||
|
||||
await seedUsers({ queryRunner, schemaName });
|
||||
|
||||
await seedUserWorkspaces({ queryRunner, schemaName, workspaceId });
|
||||
|
||||
await applicationService.createTwentyStandardApplication(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await seedAgents({ queryRunner, schemaName, workspaceId });
|
||||
|
||||
await seedApiKeys({ queryRunner, schemaName, workspaceId });
|
||||
|
||||
if (shouldSeedFeatureFlags) {
|
||||
await seedFeatureFlags({ queryRunner, schemaName, workspaceId });
|
||||
}
|
||||
|
||||
if (seedBilling) {
|
||||
await seedBillingCustomers({ queryRunner, schemaName, workspaceId });
|
||||
await seedBillingSubscriptions({ queryRunner, schemaName, workspaceId });
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
};
|
||||
|
||||
+26
-14
@@ -1,16 +1,22 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
|
||||
const tableName = 'featureFlag';
|
||||
|
||||
export const seedFeatureFlags = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
type SeedFeatureFlagsArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const seedFeatureFlags = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedFeatureFlagsArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
|
||||
@@ -90,12 +96,18 @@ export const seedFeatureFlags = async (
|
||||
.execute();
|
||||
};
|
||||
|
||||
export const deleteFeatureFlags = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
type DeleteFeatureFlagsArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const deleteFeatureFlags = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: DeleteFeatureFlagsArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(`${schemaName}.${tableName}`)
|
||||
|
||||
+28
-16
@@ -1,12 +1,12 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { type UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { generateRandomUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-random-users.util';
|
||||
import { USER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { generateRandomUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-random-users.util';
|
||||
import { USER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
|
||||
const tableName = 'userWorkspace';
|
||||
|
||||
@@ -28,11 +28,17 @@ const {
|
||||
|
||||
export const RANDOM_USER_WORKSPACE_IDS = randomUserWorkspaceIds;
|
||||
|
||||
export const seedUserWorkspaces = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
type SeedUserWorkspacesArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const seedUserWorkspaces = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: SeedUserWorkspacesArgs) => {
|
||||
let userWorkspaces: Pick<
|
||||
UserWorkspaceEntity,
|
||||
'id' | 'userId' | 'workspaceId'
|
||||
@@ -89,7 +95,7 @@ export const seedUserWorkspaces = async (
|
||||
},
|
||||
];
|
||||
}
|
||||
await dataSource
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, ['id', 'userId', 'workspaceId'])
|
||||
@@ -98,12 +104,18 @@ export const seedUserWorkspaces = async (
|
||||
.execute();
|
||||
};
|
||||
|
||||
export const deleteUserWorkspaces = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
type DeleteUserWorkspacesArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const deleteUserWorkspaces = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: DeleteUserWorkspacesArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(`${schemaName}.${tableName}`)
|
||||
|
||||
+8
-3
@@ -1,4 +1,4 @@
|
||||
import { type DataSource } from 'typeorm';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { generateRandomUsers } from './generate-random-users.util';
|
||||
|
||||
@@ -15,7 +15,12 @@ const { users: randomUsers, userIds: randomUserIds } = generateRandomUsers();
|
||||
|
||||
export const RANDOM_USER_IDS = randomUserIds;
|
||||
|
||||
export const seedUsers = async (dataSource: DataSource, schemaName: string) => {
|
||||
type SeedUsersArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
};
|
||||
|
||||
export const seedUsers = async ({ queryRunner, schemaName }: SeedUsersArgs) => {
|
||||
const originalUsers = [
|
||||
{
|
||||
id: USER_DATA_SEED_IDS.TIM,
|
||||
@@ -65,7 +70,7 @@ export const seedUsers = async (dataSource: DataSource, schemaName: string) => {
|
||||
|
||||
const allUsers = [...originalUsers, ...randomUsers];
|
||||
|
||||
await dataSource
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, [
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import {
|
||||
type CreateWorkspaceInput,
|
||||
WORKSPACE_FIELDS_TO_SEED,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
|
||||
const tableName = 'workspace';
|
||||
|
||||
export type SeedWorkspaceArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
createWorkspaceInput: CreateWorkspaceInput;
|
||||
};
|
||||
|
||||
export const createWorkspace = async ({
|
||||
schemaName,
|
||||
queryRunner,
|
||||
createWorkspaceInput,
|
||||
}: SeedWorkspaceArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, WORKSPACE_FIELDS_TO_SEED)
|
||||
.orIgnore()
|
||||
.values(createWorkspaceInput)
|
||||
.execute();
|
||||
};
|
||||
|
||||
type DeleteWorkspacesArgs = {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export const deleteWorkspaces = async ({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
}: DeleteWorkspacesArgs) => {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(`${schemaName}.${tableName}`)
|
||||
.where(`${tableName}."id" = :id`, { id: workspaceId })
|
||||
.execute();
|
||||
};
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { type DataSource } from 'typeorm';
|
||||
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch';
|
||||
|
||||
const tableName = 'workspace';
|
||||
|
||||
export const SEED_APPLE_WORKSPACE_ID = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
export const SEED_YCOMBINATOR_WORKSPACE_ID =
|
||||
'3b8e6458-5fc1-4e63-8563-008ccddaa6db';
|
||||
|
||||
export type SeedWorkspaceArgs = {
|
||||
dataSource: DataSource;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
appVersion: string | undefined;
|
||||
};
|
||||
|
||||
const workspaceSeederFields = [
|
||||
'id',
|
||||
'displayName',
|
||||
'subdomain',
|
||||
'inviteHash',
|
||||
'logo',
|
||||
'activationStatus',
|
||||
'version',
|
||||
'isTwoFactorAuthenticationEnforced',
|
||||
] as const satisfies (keyof WorkspaceEntity)[];
|
||||
|
||||
type WorkspaceSeederFields = Pick<
|
||||
WorkspaceEntity,
|
||||
(typeof workspaceSeederFields)[number]
|
||||
>;
|
||||
|
||||
export const seedWorkspaces = async ({
|
||||
schemaName,
|
||||
dataSource,
|
||||
workspaceId,
|
||||
appVersion,
|
||||
}: SeedWorkspaceArgs) => {
|
||||
const version = extractVersionMajorMinorPatch(appVersion);
|
||||
|
||||
const workspaces: Record<string, WorkspaceSeederFields> = {
|
||||
[SEED_APPLE_WORKSPACE_ID]: {
|
||||
id: SEED_APPLE_WORKSPACE_ID,
|
||||
displayName: 'Apple',
|
||||
subdomain: 'apple',
|
||||
inviteHash: 'apple.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/apple-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.PENDING_CREATION, // will be set to active after default role creation
|
||||
version: version,
|
||||
isTwoFactorAuthenticationEnforced: false,
|
||||
},
|
||||
[SEED_YCOMBINATOR_WORKSPACE_ID]: {
|
||||
id: SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
displayName: 'YCombinator',
|
||||
subdomain: 'yc',
|
||||
inviteHash: 'yc.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/ycombinator-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.PENDING_CREATION, // will be set to active after default role creation
|
||||
version: version,
|
||||
isTwoFactorAuthenticationEnforced: false,
|
||||
},
|
||||
};
|
||||
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, workspaceSeederFields)
|
||||
.orIgnore()
|
||||
.values(workspaces[workspaceId])
|
||||
.execute();
|
||||
};
|
||||
|
||||
export const deleteWorkspaces = async (
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(`${schemaName}.${tableName}`)
|
||||
.where(`${tableName}."id" = :id`, { id: workspaceId })
|
||||
.execute();
|
||||
};
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
import { generateRandomUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-random-users.util';
|
||||
import { USER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { generateRandomUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-random-users.util';
|
||||
import { USER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
|
||||
type WorkspaceMemberDataSeed = {
|
||||
id: string;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -36,6 +37,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
RoleModule,
|
||||
UserRoleModule,
|
||||
ApiKeyModule,
|
||||
ApplicationModule,
|
||||
FeatureFlagModule,
|
||||
FileStorageModule,
|
||||
WorkspaceSyncMetadataModule,
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works
|
||||
import {
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
SEED_YCOMBINATOR_WORKSPACE_ID,
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||
} from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { COMPANY_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/company-custom-field-seeds.constant';
|
||||
import { PERSON_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/person-custom-field-seeds.constant';
|
||||
import { PET_CUSTOM_FIELD_SEEDS } from 'src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-field-seeds.constant';
|
||||
|
||||
+19
-1
@@ -1,8 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -11,6 +13,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { SeededWorkspacesIds } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
|
||||
import { DevSeederPermissionsService } from 'src/engine/workspace-manager/dev-seeder/core/services/dev-seeder-permissions.service';
|
||||
import { seedCoreSchema } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-core-schema.util';
|
||||
import { seedPageLayoutTabs } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-tabs.util';
|
||||
@@ -18,6 +21,7 @@ import { seedPageLayoutWidgets } from 'src/engine/workspace-manager/dev-seeder/c
|
||||
import { seedPageLayouts } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util';
|
||||
import { DevSeederDataService } from 'src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service';
|
||||
import { DevSeederMetadataService } from 'src/engine/workspace-manager/dev-seeder/metadata/services/dev-seeder-metadata.service';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
|
||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -33,17 +37,19 @@ export class DevSeederService {
|
||||
private readonly devSeederPermissionsService: DevSeederPermissionsService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly devSeederDataService: DevSeederDataService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {}
|
||||
|
||||
public async seedDev(workspaceId: string): Promise<void> {
|
||||
public async seedDev(workspaceId: SeededWorkspacesIds): Promise<void> {
|
||||
const isBillingEnabled = this.twentyConfigService.get('IS_BILLING_ENABLED');
|
||||
const appVersion = this.twentyConfigService.get('APP_VERSION');
|
||||
|
||||
await seedCoreSchema({
|
||||
dataSource: this.coreDataSource,
|
||||
workspaceId,
|
||||
applicationService: this.applicationService,
|
||||
seedBilling: isBillingEnabled,
|
||||
appVersion,
|
||||
});
|
||||
@@ -62,6 +68,18 @@ export class DevSeederService {
|
||||
const featureFlags =
|
||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||
|
||||
const twentyStandardApplication =
|
||||
await this.applicationService.findByUniversalIdentifier({
|
||||
workspaceId,
|
||||
universalIdentifier: TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(twentyStandardApplication)) {
|
||||
throw new Error(
|
||||
'Seeder failed to find twenty standard application, should never occur',
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceSyncMetadataService.synchronize({
|
||||
workspaceId: workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
|
||||
Reference in New Issue
Block a user