mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
Merge pull request #8933 from ever-co/feat/alter-time-slot-minute-entity
[Feat] Alter "TimeSlot" Entity Migration
This commit is contained in:
@@ -14,29 +14,28 @@ import { isDatabaseType, isSqliteDB } from './../core/utils';
|
||||
* @description
|
||||
* Run pending database migrations. See [TypeORM migration docs](https://typeorm.io/#/migrations)
|
||||
*
|
||||
* @param pluginConfig
|
||||
* @param pluginConfig - Partial application plugin config
|
||||
*/
|
||||
export async function runDatabaseMigrations(pluginConfig: Partial<ApplicationPluginConfig>) {
|
||||
export async function runDatabaseMigrations(pluginConfig: Partial<ApplicationPluginConfig>): Promise<void> {
|
||||
const config = await registerPluginConfig(pluginConfig);
|
||||
const dataSource: DataSource = await establishDatabaseConnection(config);
|
||||
const dataSource = await initializeDatabaseConnection(config);
|
||||
|
||||
try {
|
||||
const migrations = await dataSource.runMigrations({ transaction: 'each' });
|
||||
|
||||
if (isNotEmpty(migrations)) {
|
||||
for (const migration of migrations) {
|
||||
migrations.forEach((migration) => {
|
||||
console.log(chalk.green(`Migration ${migration.name} has been run successfully!`));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(chalk.yellow(`There are no pending migrations to run.`));
|
||||
}
|
||||
} catch (error) {
|
||||
if (dataSource) await closeConnection(dataSource);
|
||||
|
||||
console.log(chalk.black.bgRed('Error during migration run:'));
|
||||
console.error(chalk.black.bgRed('Error during migration run:'));
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await closeConnection(dataSource);
|
||||
await shutdownDatabaseConnection(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,54 +43,45 @@ export async function runDatabaseMigrations(pluginConfig: Partial<ApplicationPlu
|
||||
* @description
|
||||
* Reverts last applied database migration. See [TypeORM migration docs](https://typeorm.io/#/migrations)
|
||||
*
|
||||
* @param pluginConfig
|
||||
* @param pluginConfig - Partial application plugin config
|
||||
*/
|
||||
export async function revertLastDatabaseMigration(pluginConfig: Partial<ApplicationPluginConfig>) {
|
||||
export async function revertLastDatabaseMigration(pluginConfig: Partial<ApplicationPluginConfig>): Promise<void> {
|
||||
const config = await registerPluginConfig(pluginConfig);
|
||||
const connection = await establishDatabaseConnection(config);
|
||||
const dataSource = await initializeDatabaseConnection(config);
|
||||
|
||||
try {
|
||||
await connection.undoLastMigration({ transaction: 'each' });
|
||||
console.log(chalk.green(`Migration has been reverted successfully!`));
|
||||
await dataSource.undoLastMigration({ transaction: 'each' });
|
||||
console.log(chalk.green('Last migration has been reverted successfully!'));
|
||||
} catch (error) {
|
||||
if (connection) await closeConnection(connection);
|
||||
|
||||
console.log(chalk.black.bgRed('Error during migration revert:'));
|
||||
console.error(chalk.black.bgRed('Error during migration revert:'));
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await closeConnection(connection);
|
||||
await shutdownDatabaseConnection(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Generates a new migration file with sql needs to be executed to update schema.
|
||||
* Generates a new migration file with SQL required to update the schema.
|
||||
*
|
||||
* @param pluginConfig
|
||||
* @param pluginConfig - Partial application plugin configuration
|
||||
* @param options - Migration generation options including name and output directory
|
||||
*/
|
||||
export async function generateMigration(pluginConfig: Partial<ApplicationPluginConfig>, options: IMigrationOptions) {
|
||||
export async function generateMigration(
|
||||
pluginConfig: Partial<ApplicationPluginConfig>,
|
||||
options: IMigrationOptions
|
||||
): Promise<void> {
|
||||
if (!options.name) {
|
||||
console.log(chalk.yellow('Migration name must be required.Please specify migration name!'));
|
||||
return;
|
||||
}
|
||||
const config = await registerPluginConfig(pluginConfig);
|
||||
const dataSource = await initializeDatabaseConnection(config);
|
||||
const directory = resolveMigrationDirectory(options, config);
|
||||
|
||||
let directory = options.dir;
|
||||
// if directory is not set then try to open plugin config and find default path there
|
||||
if (!directory) {
|
||||
try {
|
||||
directory = config.dbConnectionOptions['cli']
|
||||
? config.dbConnectionOptions['cli']['migrationsDir']
|
||||
: undefined;
|
||||
} catch (err) {
|
||||
console.log('Error while finding migration directory', err);
|
||||
}
|
||||
}
|
||||
|
||||
const connection = await establishDatabaseConnection(config);
|
||||
try {
|
||||
const sqlInMemory = await connection.driver.createSchemaBuilder().log();
|
||||
const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
|
||||
const upSqls: string[] = [];
|
||||
const downSqls: string[] = [];
|
||||
|
||||
@@ -119,7 +109,7 @@ export async function generateMigration(pluginConfig: Partial<ApplicationPluginC
|
||||
/**
|
||||
* Gets contents of the migration file.
|
||||
*/
|
||||
const fileContent = getTemplate(connection, options.name as any, timestamp, upSqls, downSqls.reverse());
|
||||
const fileContent = getTemplate(dataSource, options.name as any, timestamp, upSqls, downSqls.reverse());
|
||||
|
||||
const filename = timestamp + '-' + options.name + '.ts';
|
||||
const outputPath = directory ? path.join(directory, filename) : path.join(process.cwd(), filename);
|
||||
@@ -139,122 +129,131 @@ export async function generateMigration(pluginConfig: Partial<ApplicationPluginC
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (connection) await closeConnection(connection);
|
||||
|
||||
console.log(chalk.black.bgRed('Error during migration generation:'));
|
||||
console.error(chalk.red('❌ Error during migration generation:'));
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await closeConnection(connection);
|
||||
await shutdownDatabaseConnection(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the directory where migration files should be generated.
|
||||
*
|
||||
* @param options - Migration options that may include a `dir` path
|
||||
* @param config - Plugin configuration containing possible CLI migration settings
|
||||
* @returns The resolved directory path or `undefined` if not found
|
||||
*/
|
||||
export function resolveMigrationDirectory(
|
||||
options: IMigrationOptions,
|
||||
config: Partial<ApplicationPluginConfig>
|
||||
): string | undefined {
|
||||
if (options.dir) {
|
||||
return options.dir;
|
||||
}
|
||||
|
||||
try {
|
||||
return config.dbConnectionOptions && config.dbConnectionOptions['cli']
|
||||
? config.dbConnectionOptions['cli']['migrationsDir']
|
||||
: undefined;
|
||||
} catch (err) {
|
||||
console.error(chalk.red('❌ Error while resolving migration directory:'), err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Create a new blank migration file to be executed to create/update schema.
|
||||
* Creates a new blank migration file to be used for schema changes.
|
||||
*
|
||||
* @param pluginConfig
|
||||
* @param pluginConfig - Partial application plugin configuration
|
||||
* @param options - Migration creation options including name and optional directory
|
||||
*/
|
||||
export async function createMigration(pluginConfig: Partial<ApplicationPluginConfig>, options: IMigrationOptions) {
|
||||
export async function createMigration(
|
||||
pluginConfig: Partial<ApplicationPluginConfig>,
|
||||
options: IMigrationOptions
|
||||
): Promise<void> {
|
||||
if (!options.name) {
|
||||
console.log(chalk.yellow('Migration name must be required.Please specify migration name!'));
|
||||
console.log(chalk.yellow('Migration name is required. Please specify a name using "--name".'));
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await registerPluginConfig(pluginConfig);
|
||||
const dataSource = await initializeDatabaseConnection(config);
|
||||
const directory = resolveMigrationDirectory(options, config);
|
||||
|
||||
let directory = options.dir;
|
||||
// if directory is not set then try to open plugin config and find default path there
|
||||
if (!directory) {
|
||||
try {
|
||||
directory = config.dbConnectionOptions['cli']
|
||||
? config.dbConnectionOptions['cli']['migrationsDir']
|
||||
: undefined;
|
||||
} catch (err) {
|
||||
console.log('Error while finding migration directory', err);
|
||||
}
|
||||
}
|
||||
|
||||
const connection = await establishDatabaseConnection(config);
|
||||
try {
|
||||
const timestamp = new Date().getTime();
|
||||
/**
|
||||
* Gets contents of the migration file.
|
||||
*/
|
||||
const fileContent = getTemplate(connection, options.name as any, timestamp, [], []);
|
||||
|
||||
const filename = timestamp + '-' + options.name + '.ts';
|
||||
const timestamp = Date.now();
|
||||
const fileContent = getTemplate(dataSource, options.name, timestamp, [], []);
|
||||
const filename = `${timestamp}-${options.name}.ts`;
|
||||
const outputPath = directory ? path.join(directory, filename) : path.join(process.cwd(), filename);
|
||||
|
||||
try {
|
||||
await MigrationUtils.createFile(outputPath, fileContent);
|
||||
console.log(chalk.green(`Migration ${chalk.blue(outputPath)} has been created successfully.`));
|
||||
} catch (error) {
|
||||
console.log(chalk.black.bgRed('Error during migration creating files:'));
|
||||
console.error(error);
|
||||
}
|
||||
await MigrationUtils.createFile(outputPath, fileContent);
|
||||
console.log(chalk.green(`Migration ${chalk.blue(outputPath)} has been created successfully.`));
|
||||
} catch (error) {
|
||||
if (connection) await closeConnection(connection);
|
||||
|
||||
console.log(chalk.black.bgRed('Error during migration create:'));
|
||||
console.error(chalk.red('❌ Error while creating migration file:'));
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await closeConnection(connection);
|
||||
await shutdownDatabaseConnection(dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Establish new database connection, if not found any connection. See [TypeORM migration docs](https://typeorm.io/#/connection)
|
||||
* Initializes a new database connection. See [TypeORM migration docs](https://typeorm.io/#/connection)
|
||||
*
|
||||
* @param config
|
||||
* @param config - Partial application plugin configuration
|
||||
* @returns An initialized TypeORM DataSource instance
|
||||
*/
|
||||
export async function establishDatabaseConnection(config: Partial<ApplicationPluginConfig>): Promise<DataSource> {
|
||||
export async function initializeDatabaseConnection(config: Partial<ApplicationPluginConfig>): Promise<DataSource> {
|
||||
const { dbConnectionOptions } = config;
|
||||
const overrideDbConfig = {
|
||||
|
||||
if (!dbConnectionOptions) {
|
||||
throw new Error('❌ Missing database connection options in plugin config.');
|
||||
}
|
||||
|
||||
const dataSource = new DataSource({
|
||||
...dbConnectionOptions,
|
||||
subscribers: [],
|
||||
synchronize: false,
|
||||
migrationsRun: false,
|
||||
dropSchema: false,
|
||||
logging: ['all']
|
||||
};
|
||||
dropSchema: false
|
||||
} as DataSourceOptions);
|
||||
|
||||
console.log(chalk.yellow('NOTE: No existing database connection found. Creating a new one...'));
|
||||
|
||||
let dataSource: DataSource;
|
||||
try {
|
||||
console.log(chalk.yellow('NOTE: DATABASE CONNECTION DOES NOT EXIST YET. NEW ONE WILL BE CREATED!'));
|
||||
try {
|
||||
console.log(chalk.green(`CONNECTING TO DATABASE...`));
|
||||
dataSource = new DataSource({
|
||||
...dbConnectionOptions,
|
||||
...overrideDbConfig
|
||||
} as DataSourceOptions);
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize();
|
||||
console.log(chalk.green(`✅ CONNECTED TO DATABASE!`));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Unable to connect to database', error);
|
||||
if (!dataSource.isInitialized) {
|
||||
console.log(chalk.blue('Connecting to the database...'));
|
||||
await dataSource.initialize();
|
||||
console.log(chalk.green('✅ Successfully connected to the database.'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error while connecting to the database', error);
|
||||
console.error(chalk.red('❌ Failed to connect to the database:'), error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Close database connection, after complete or failed process
|
||||
* Gracefully shuts down the database connection after use.
|
||||
*
|
||||
* @param connection
|
||||
* @param dataSource - An initialized TypeORM DataSource
|
||||
*/
|
||||
async function closeConnection(dataSource: DataSource) {
|
||||
export async function shutdownDatabaseConnection(dataSource: DataSource): Promise<void> {
|
||||
if (!dataSource || !dataSource.isInitialized) {
|
||||
console.log(chalk.yellow('⚠️ Database connection is not initialized or already closed.'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (dataSource && dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
console.log(chalk.green(`✅ DISCONNECTED TO DATABASE!`));
|
||||
}
|
||||
await dataSource.destroy();
|
||||
console.log(chalk.green('✅ Database connection closed successfully.'));
|
||||
} catch (error) {
|
||||
console.log('Error while disconnecting to the database', error);
|
||||
console.error(chalk.red('❌ Failed to close the database connection:'), error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +278,11 @@ function getTemplate(
|
||||
upSqls: string[],
|
||||
downSqls: string[]
|
||||
): string {
|
||||
return `
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
return `import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import * as chalk from 'chalk';
|
||||
import { DatabaseTypeEnum } from "@gauzy/config";
|
||||
import { DatabaseTypeEnum } from '@gauzy/config';
|
||||
|
||||
export class ${camelCase(name, true)}${timestamp} implements MigrationInterface {
|
||||
|
||||
name = '${camelCase(name, true)}${timestamp}';
|
||||
|
||||
/**
|
||||
@@ -318,6 +315,8 @@ export class ${camelCase(name, true)}${timestamp} implements MigrationInterface
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
console.log(chalk.yellow(this.name + ' reverting changes!'));
|
||||
|
||||
switch (queryRunner.connection.options.type) {
|
||||
case DatabaseTypeEnum.sqlite:
|
||||
case DatabaseTypeEnum.betterSqlite3:
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import * as chalk from 'chalk';
|
||||
import { DatabaseTypeEnum } from '@gauzy/config';
|
||||
|
||||
export class AlterTimeSlotEntityTable1746169515410 implements MigrationInterface {
|
||||
name = 'AlterTimeSlotEntityTable1746169515410';
|
||||
|
||||
/**
|
||||
* Up Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
console.log(chalk.yellow(this.name + ' start running!'));
|
||||
|
||||
switch (queryRunner.connection.options.type) {
|
||||
case DatabaseTypeEnum.sqlite:
|
||||
case DatabaseTypeEnum.betterSqlite3:
|
||||
await this.sqliteUpQueryRunner(queryRunner);
|
||||
break;
|
||||
case DatabaseTypeEnum.postgres:
|
||||
await this.postgresUpQueryRunner(queryRunner);
|
||||
break;
|
||||
case DatabaseTypeEnum.mysql:
|
||||
await this.mysqlUpQueryRunner(queryRunner);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unsupported database: ${queryRunner.connection.options.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Down Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
console.log(chalk.yellow(this.name + ' reverting changes!'));
|
||||
|
||||
switch (queryRunner.connection.options.type) {
|
||||
case DatabaseTypeEnum.sqlite:
|
||||
case DatabaseTypeEnum.betterSqlite3:
|
||||
await this.sqliteDownQueryRunner(queryRunner);
|
||||
break;
|
||||
case DatabaseTypeEnum.postgres:
|
||||
await this.postgresDownQueryRunner(queryRunner);
|
||||
break;
|
||||
case DatabaseTypeEnum.mysql:
|
||||
await this.mysqlDownQueryRunner(queryRunner);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unsupported database: ${queryRunner.connection.options.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgresDB Up Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async postgresUpQueryRunner(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" ADD "location" integer NOT NULL DEFAULT 0`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" ADD "kbMouseActivity" jsonb`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" ADD "locationActivity" jsonb`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" ADD "customActivity" jsonb`);
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgresDB Down Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" DROP COLUMN "customActivity"`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" DROP COLUMN "locationActivity"`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" DROP COLUMN "kbMouseActivity"`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" DROP COLUMN "location"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* SqliteDB and BetterSQlite3DB Up Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_9445b0316f60bd53c29661247a"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_d40a78a0d0a6e64e8d15f32db6"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_7913305b850c7afc89b6ed96a3"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_c6e7d1075bfd97eea6643b1479"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_f44e721669d5c6bed32cd6a3bf"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0c707825a7c2ecc4e186b07ebf"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b8284109257b5137256b5b3e84"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b407841271245501dd1a8c7551"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_645a6bc3f1141d4a111a3166d8"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_81060c5dbe69efa1f3b6e1a2e5"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_596e0e849822aa09ec0d4a1052"`);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_time_slot" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "duration" integer NOT NULL DEFAULT (0), "keyboard" integer NOT NULL DEFAULT (0), "mouse" integer NOT NULL DEFAULT (0), "overall" integer NOT NULL DEFAULT (0), "startedAt" datetime NOT NULL, "employeeId" varchar NOT NULL, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "deletedAt" datetime, "archivedAt" datetime, "createdByUserId" varchar, "updatedByUserId" varchar, "deletedByUserId" varchar, "location" integer NOT NULL DEFAULT (0), "kbMouseActivity" text, "locationActivity" text, "customActivity" text, CONSTRAINT "FK_9445b0316f60bd53c29661247a1" FOREIGN KEY ("deletedByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_d40a78a0d0a6e64e8d15f32db63" FOREIGN KEY ("createdByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_7913305b850c7afc89b6ed96a30" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_b8284109257b5137256b5b3e848" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_b407841271245501dd1a8c75513" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_596e0e849822aa09ec0d4a10521" FOREIGN KEY ("updatedByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "temporary_time_slot"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "employeeId", "isActive", "isArchived", "deletedAt", "archivedAt", "createdByUserId", "updatedByUserId", "deletedByUserId") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "employeeId", "isActive", "isArchived", "deletedAt", "archivedAt", "createdByUserId", "updatedByUserId", "deletedByUserId" FROM "time_slot"`
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "time_slot"`);
|
||||
await queryRunner.query(`ALTER TABLE "temporary_time_slot" RENAME TO "time_slot"`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_9445b0316f60bd53c29661247a" ON "time_slot" ("deletedByUserId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_d40a78a0d0a6e64e8d15f32db6" ON "time_slot" ("createdByUserId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_7913305b850c7afc89b6ed96a3" ON "time_slot" ("employeeId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_c6e7d1075bfd97eea6643b1479" ON "time_slot" ("startedAt") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f44e721669d5c6bed32cd6a3bf" ON "time_slot" ("overall") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_0c707825a7c2ecc4e186b07ebf" ON "time_slot" ("duration") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b8284109257b5137256b5b3e84" ON "time_slot" ("tenantId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b407841271245501dd1a8c7551" ON "time_slot" ("organizationId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_645a6bc3f1141d4a111a3166d8" ON "time_slot" ("isActive") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_81060c5dbe69efa1f3b6e1a2e5" ON "time_slot" ("isArchived") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_596e0e849822aa09ec0d4a1052" ON "time_slot" ("updatedByUserId") `);
|
||||
}
|
||||
|
||||
/**
|
||||
* SqliteDB and BetterSQlite3DB Down Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_596e0e849822aa09ec0d4a1052"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_81060c5dbe69efa1f3b6e1a2e5"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_645a6bc3f1141d4a111a3166d8"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b407841271245501dd1a8c7551"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b8284109257b5137256b5b3e84"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0c707825a7c2ecc4e186b07ebf"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_f44e721669d5c6bed32cd6a3bf"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_c6e7d1075bfd97eea6643b1479"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_7913305b850c7afc89b6ed96a3"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_d40a78a0d0a6e64e8d15f32db6"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_9445b0316f60bd53c29661247a"`);
|
||||
await queryRunner.query(`ALTER TABLE "time_slot" RENAME TO "temporary_time_slot"`);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "time_slot" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "duration" integer NOT NULL DEFAULT (0), "keyboard" integer NOT NULL DEFAULT (0), "mouse" integer NOT NULL DEFAULT (0), "overall" integer NOT NULL DEFAULT (0), "startedAt" datetime NOT NULL, "employeeId" varchar NOT NULL, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "deletedAt" datetime, "archivedAt" datetime, "createdByUserId" varchar, "updatedByUserId" varchar, "deletedByUserId" varchar, CONSTRAINT "FK_9445b0316f60bd53c29661247a1" FOREIGN KEY ("deletedByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_d40a78a0d0a6e64e8d15f32db63" FOREIGN KEY ("createdByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_7913305b850c7afc89b6ed96a30" FOREIGN KEY ("employeeId") REFERENCES "employee" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_b8284109257b5137256b5b3e848" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_b407841271245501dd1a8c75513" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_596e0e849822aa09ec0d4a10521" FOREIGN KEY ("updatedByUserId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "time_slot"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "employeeId", "isActive", "isArchived", "deletedAt", "archivedAt", "createdByUserId", "updatedByUserId", "deletedByUserId") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "duration", "keyboard", "mouse", "overall", "startedAt", "employeeId", "isActive", "isArchived", "deletedAt", "archivedAt", "createdByUserId", "updatedByUserId", "deletedByUserId" FROM "temporary_time_slot"`
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "temporary_time_slot"`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_596e0e849822aa09ec0d4a1052" ON "time_slot" ("updatedByUserId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_81060c5dbe69efa1f3b6e1a2e5" ON "time_slot" ("isArchived") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_645a6bc3f1141d4a111a3166d8" ON "time_slot" ("isActive") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b407841271245501dd1a8c7551" ON "time_slot" ("organizationId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b8284109257b5137256b5b3e84" ON "time_slot" ("tenantId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_0c707825a7c2ecc4e186b07ebf" ON "time_slot" ("duration") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f44e721669d5c6bed32cd6a3bf" ON "time_slot" ("overall") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_c6e7d1075bfd97eea6643b1479" ON "time_slot" ("startedAt") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_7913305b850c7afc89b6ed96a3" ON "time_slot" ("employeeId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_d40a78a0d0a6e64e8d15f32db6" ON "time_slot" ("createdByUserId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_9445b0316f60bd53c29661247a" ON "time_slot" ("deletedByUserId") `);
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL Up Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async mysqlUpQueryRunner(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` ADD \`location\` int NOT NULL DEFAULT '0'`);
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` ADD \`kbMouseActivity\` json NULL`);
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` ADD \`locationActivity\` json NULL`);
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` ADD \`customActivity\` json NULL`);
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL Down Migration
|
||||
*
|
||||
* @param queryRunner
|
||||
*/
|
||||
public async mysqlDownQueryRunner(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` DROP COLUMN \`customActivity\``);
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` DROP COLUMN \`locationActivity\``);
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` DROP COLUMN \`kbMouseActivity\``);
|
||||
await queryRunner.query(`ALTER TABLE \`time_slot\` DROP COLUMN \`location\``);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -15,7 +15,7 @@ export class TimeSlotMinute extends TenantOrganizationBaseEntity implements ITim
|
||||
* The number of keyboard interactions in the given time slot minute.
|
||||
* Defaults to 0 if not provided.
|
||||
*/
|
||||
@ApiProperty({
|
||||
@ApiPropertyOptional({
|
||||
type: () => Number,
|
||||
description: 'Number of keyboard interactions in the given time slot minute.',
|
||||
example: 42,
|
||||
@@ -30,7 +30,7 @@ export class TimeSlotMinute extends TenantOrganizationBaseEntity implements ITim
|
||||
* The number of mouse interactions in the given time slot minute.
|
||||
* Defaults to 0 if not provided.
|
||||
*/
|
||||
@ApiProperty({
|
||||
@ApiPropertyOptional({
|
||||
type: () => Number,
|
||||
description: 'Number of mouse interactions in the given time slot minute.',
|
||||
example: 42,
|
||||
@@ -45,7 +45,7 @@ export class TimeSlotMinute extends TenantOrganizationBaseEntity implements ITim
|
||||
* Number of movements (e.g., mouse or device movements) detected within one minute.
|
||||
* Used to track activity levels during time tracking sessions.
|
||||
*/
|
||||
@ApiProperty({
|
||||
@ApiPropertyOptional({
|
||||
type: Number,
|
||||
description: 'Number of movements detected in 1 minute',
|
||||
example: 42,
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { RelationId, JoinTable } from 'typeorm';
|
||||
import { IsNumber, IsDateString, IsUUID, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { ITimeSlot, ITimeSlotMinute, IActivity, IScreenshot, IEmployee, ITimeLog, ID } from '@gauzy/contracts';
|
||||
import { IsNumber, IsDateString, IsUUID, IsNotEmpty, IsOptional, IsObject } from 'class-validator';
|
||||
import {
|
||||
ITimeSlot,
|
||||
ITimeSlotMinute,
|
||||
IActivity,
|
||||
IScreenshot,
|
||||
IEmployee,
|
||||
ITimeLog,
|
||||
ID,
|
||||
JsonData
|
||||
} from '@gauzy/contracts';
|
||||
import { isMySQL, isPostgres } from '@gauzy/config';
|
||||
import { Activity, Employee, Screenshot, TenantOrganizationBaseEntity, TimeLog } from './../../core/entities/internal';
|
||||
import {
|
||||
ColumnIndex,
|
||||
@@ -17,25 +27,69 @@ import { MikroOrmTimeSlotRepository } from './repository/mikro-orm-time-slot.rep
|
||||
|
||||
@MultiORMEntity('time_slot', { mikroOrmRepository: () => MikroOrmTimeSlotRepository })
|
||||
export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot {
|
||||
@ApiPropertyOptional({ type: () => Number, default: 0 })
|
||||
/**
|
||||
* The number of seconds employee spent in the given time slot.
|
||||
* Defaults to 0 if not provided.
|
||||
*/
|
||||
@ApiPropertyOptional({
|
||||
type: () => Number,
|
||||
description: 'The number of seconds employee spent in the given time slot',
|
||||
default: 0
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ColumnIndex()
|
||||
@MultiORMColumn({ default: 0 })
|
||||
duration?: number;
|
||||
|
||||
@ApiPropertyOptional({ type: () => Number, default: 0 })
|
||||
/**
|
||||
* The number of keyboard interactions in the given time slot minute.
|
||||
* Defaults to 0 if not provided.
|
||||
*/
|
||||
@ApiPropertyOptional({
|
||||
type: () => Number,
|
||||
description: 'Number of keyboard interactions in the given time slot.',
|
||||
example: 42,
|
||||
default: 0
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@MultiORMColumn({ default: 0 })
|
||||
keyboard?: number;
|
||||
|
||||
@ApiPropertyOptional({ type: () => Number, default: 0 })
|
||||
/**
|
||||
* The number of mouse interactions in the given time slot minute.
|
||||
* Defaults to 0 if not provided.
|
||||
*/
|
||||
@ApiPropertyOptional({
|
||||
type: () => Number,
|
||||
description: 'Number of mouse interactions in the given time slot.',
|
||||
example: 42,
|
||||
default: 0
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@MultiORMColumn({ default: 0 })
|
||||
mouse?: number;
|
||||
|
||||
/**
|
||||
* Number of movements (e.g., mouse or device movements) detected within 10 minutes.
|
||||
* Used to track activity levels during time tracking sessions.
|
||||
*/
|
||||
@ApiPropertyOptional({
|
||||
type: Number,
|
||||
description: 'Number of movements detected in 10 minutes',
|
||||
example: 42,
|
||||
default: 0
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@MultiORMColumn({ default: 0 })
|
||||
location?: number;
|
||||
|
||||
/**
|
||||
* The overall activity time of the time slot.
|
||||
*/
|
||||
@ApiPropertyOptional({ type: () => Number, default: 0 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@@ -43,6 +97,9 @@ export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot
|
||||
@MultiORMColumn({ default: 0 })
|
||||
overall?: number;
|
||||
|
||||
/**
|
||||
* The start time of the time slot.
|
||||
*/
|
||||
@ApiProperty({ type: () => 'timestamptz' })
|
||||
@IsNotEmpty()
|
||||
@IsDateString()
|
||||
@@ -51,17 +108,54 @@ export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot
|
||||
startedAt: Date;
|
||||
|
||||
/**
|
||||
* Additional virtual columns
|
||||
* Raw keyboard and mouse activity data (e.g., event logs or durations).
|
||||
*/
|
||||
@ApiPropertyOptional({ type: () => Object })
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@MultiORMColumn({
|
||||
type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text',
|
||||
nullable: true
|
||||
})
|
||||
kbMouseActivity?: JsonData;
|
||||
|
||||
/**
|
||||
* Raw location activity data (e.g., coordinates or movement patterns).
|
||||
*/
|
||||
@ApiPropertyOptional({ type: () => Object })
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@MultiORMColumn({
|
||||
type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text',
|
||||
nullable: true
|
||||
})
|
||||
locationActivity?: JsonData;
|
||||
|
||||
/**
|
||||
* Custom-defined activity data (e.g., domain-specific or extension usage).
|
||||
*/
|
||||
@ApiPropertyOptional({ type: () => Object })
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
@MultiORMColumn({
|
||||
type: isPostgres() ? 'jsonb' : isMySQL() ? 'json' : 'text',
|
||||
nullable: true
|
||||
})
|
||||
customActivity?: JsonData;
|
||||
|
||||
// The stopped time of the time slot.
|
||||
@VirtualMultiOrmColumn()
|
||||
stoppedAt?: Date;
|
||||
|
||||
// The percentage of interactions in the given time slot.
|
||||
@VirtualMultiOrmColumn()
|
||||
percentage?: number;
|
||||
|
||||
// The percentage of keyboard interactions in the given time slot.
|
||||
@VirtualMultiOrmColumn()
|
||||
keyboardPercentage?: number;
|
||||
|
||||
// The percentage of mouse interactions in the given time slot.
|
||||
@VirtualMultiOrmColumn()
|
||||
mousePercentage?: number;
|
||||
|
||||
@@ -70,8 +164,9 @@ export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot
|
||||
| @ManyToOne
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Employee
|
||||
* The reference to the `Employee` entity to which this time slot belongs.
|
||||
*/
|
||||
@MultiORMManyToOne(() => Employee, (it) => it.timeSlots, {
|
||||
/** Database cascade action on delete. */
|
||||
@@ -79,6 +174,9 @@ export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot
|
||||
})
|
||||
employee?: IEmployee;
|
||||
|
||||
/**
|
||||
* The ID of the related `Employee` entity, stored as a UUID.
|
||||
*/
|
||||
@ApiProperty({ type: () => String })
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
@@ -94,27 +192,21 @@ export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot
|
||||
*/
|
||||
|
||||
/**
|
||||
* Screenshot
|
||||
* The reference to the `Screenshot` entity to which this time slot belongs.
|
||||
*/
|
||||
@MultiORMOneToMany(() => Screenshot, (it) => it.timeSlot, {
|
||||
cascade: true
|
||||
})
|
||||
@MultiORMOneToMany(() => Screenshot, (it) => it.timeSlot, { cascade: true })
|
||||
screenshots?: IScreenshot[];
|
||||
|
||||
/**
|
||||
* Activity
|
||||
* The reference to the `Activity` entity to which this time slot belongs.
|
||||
*/
|
||||
@MultiORMOneToMany(() => Activity, (it) => it.timeSlot, {
|
||||
cascade: true
|
||||
})
|
||||
@MultiORMOneToMany(() => Activity, (it) => it.timeSlot, { cascade: true })
|
||||
activities?: IActivity[];
|
||||
|
||||
/**
|
||||
* TimeSlotMinute
|
||||
* The reference to the `TimeSlotMinute` entity to which this time slot belongs.
|
||||
*/
|
||||
@MultiORMOneToMany(() => TimeSlotMinute, (it) => it.timeSlot, {
|
||||
cascade: true
|
||||
})
|
||||
@MultiORMOneToMany(() => TimeSlotMinute, (it) => it.timeSlot, { cascade: true })
|
||||
timeSlotMinutes?: ITimeSlotMinute[];
|
||||
|
||||
/*
|
||||
@@ -122,8 +214,9 @@ export class TimeSlot extends TenantOrganizationBaseEntity implements ITimeSlot
|
||||
| @ManyToMany
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* TimeLog
|
||||
* The reference to the `TimeLog` entity to which this time slot belongs.
|
||||
*/
|
||||
@MultiORMManyToMany(() => TimeLog, (it) => it.timeSlots, {
|
||||
/** Database cascade action on update. */
|
||||
|
||||
Reference in New Issue
Block a user