From 05ae5235eee55cd0487cd1b6d766136dc57952ca Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 9 Jun 2026 11:15:03 +0000 Subject: [PATCH] Build coaching SaaS workspace foundation --- backend/src/config.js | 6 +- backend/src/db/api/users.js | 25 +- backend/src/db/migrations/1780999621284.js | 2882 +--------- backend/src/db/models/action_items.js | 27 + backend/src/db/models/clients.js | 37 + backend/src/db/models/intake_leads.js | 31 + backend/src/db/models/packages.js | 30 + backend/src/db/models/prep_briefs.js | 29 + backend/src/db/models/resources.js | 28 + backend/src/db/models/sessions.js | 40 + backend/src/db/models/testimonials.js | 26 + backend/src/db/models/users.js | 43 +- .../db/seeders/20200430130760-user-roles.js | 1074 +--- .../db/seeders/20231127130745-sample-data.js | 4907 +---------------- backend/src/index.js | 41 +- backend/src/routes/coaching.js | 248 + frontend/src/config.ts | 2 +- frontend/src/layouts/Authenticated.tsx | 2 +- frontend/src/menuAside.ts | 103 +- frontend/src/pages/_app.tsx | 4 +- frontend/src/pages/client-portal.tsx | 128 + frontend/src/pages/clients.tsx | 147 + frontend/src/pages/dashboard.tsx | 512 +- frontend/src/pages/index.tsx | 225 +- frontend/src/pages/session-memory.tsx | 149 + 25 files changed, 1754 insertions(+), 8992 deletions(-) create mode 100644 backend/src/db/models/action_items.js create mode 100644 backend/src/db/models/clients.js create mode 100644 backend/src/db/models/intake_leads.js create mode 100644 backend/src/db/models/packages.js create mode 100644 backend/src/db/models/prep_briefs.js create mode 100644 backend/src/db/models/resources.js create mode 100644 backend/src/db/models/sessions.js create mode 100644 backend/src/db/models/testimonials.js create mode 100644 backend/src/routes/coaching.js create mode 100644 frontend/src/pages/client-portal.tsx create mode 100644 frontend/src/pages/clients.tsx create mode 100644 frontend/src/pages/session-memory.tsx diff --git a/backend/src/config.js b/backend/src/config.js index b0e6eae..8005535 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -39,7 +39,7 @@ const config = { }, uploadDir: os.tmpdir(), email: { - from: 'Sales Pipeline CRM ', + from: 'Coaching SaaS Workspace ', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, auth: { @@ -56,7 +56,7 @@ const config = { - user: 'Sales Ops', + user: 'Coach', }, @@ -69,7 +69,7 @@ const config = { config.pexelsKey = process.env.PEXELS_KEY || ''; -config.pexelsQuery = 'Climbers ascending a mountain ridge'; +config.pexelsQuery = 'executive coaching workspace'; config.host = process.env.NODE_ENV === "production" ? config.remote : "http://localhost"; config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`; config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`; diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js index 72fff73..f576eed 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -403,29 +403,7 @@ module.exports = class UsersDBApi { - output.accounts_owner = await users.getAccounts_owner({ - transaction - }); - - - output.contacts_owner = await users.getContacts_owner({ - transaction - }); - - - - output.leads_owner = await users.getLeads_owner({ - transaction - }); - - - - output.deals_owner = await users.getDeals_owner({ - transaction - }); - - - output.activities_owner = await users.getActivities_owner({ + output.clients_owner = await users.getClients_owner({ transaction }); @@ -951,4 +929,3 @@ module.exports = class UsersDBApi { }; - diff --git a/backend/src/db/migrations/1780999621284.js b/backend/src/db/migrations/1780999621284.js index 2ea608b..aeff612 100644 --- a/backend/src/db/migrations/1780999621284.js +++ b/backend/src/db/migrations/1780999621284.js @@ -1,2610 +1,278 @@ module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - - - await queryInterface.createTable('users', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('roles', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('permissions', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('accounts', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('contacts', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('lead_sources', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('leads', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('pipeline_stages', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('deals', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('activities', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - - await queryInterface.addColumn( - 'users', - 'firstName', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'lastName', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'phoneNumber', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'email', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'disabled', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'users', - 'password', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'emailVerified', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'emailVerificationToken', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'emailVerificationTokenExpiresAt', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'passwordResetToken', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'passwordResetTokenExpiresAt', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'provider', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'app_roleId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'roles', - key: 'id', - }, - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'roles', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'roles', - 'role_customization', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'permissions', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'domain', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'industry', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'employee_count', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'annual_revenue', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'phone', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'website', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'address', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'ownerId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'account_status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['prospect','active_customer','former_customer','partner'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'last_contacted_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'next_follow_up_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'accounts', - 'notes', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'full_name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'email', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'phone', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'job_title', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'accountId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'accounts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'ownerId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'contact_status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['active','do_not_contact','bounced','unsubscribed'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'linkedin_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'notes', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'last_contacted_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'contacts', - 'next_follow_up_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'lead_sources', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'lead_sources', - 'is_active', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'lead_sources', - 'description', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'lead_name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'company_name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'email', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'phone', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'ownerId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'sourceId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'lead_sources', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'lead_status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['new','contacted','qualified','unqualified','converted'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'lead_rating', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['cold','warm','hot'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'notes', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'last_contacted_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'next_follow_up_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'accountId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'accounts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'leads', - 'primary_contactId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'contacts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pipeline_stages', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pipeline_stages', - 'sort_order', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pipeline_stages', - 'win_probability', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pipeline_stages', - 'is_won', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pipeline_stages', - 'is_lost', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pipeline_stages', - 'is_active', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'stageId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'pipeline_stages', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'ownerId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'accountId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'accounts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'primary_contactId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'contacts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'originating_leadId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'leads', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'amount', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'currency', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'expected_close_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'closed_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'deal_status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['open','won','lost'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'loss_reason', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'last_activity_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'next_follow_up_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'deals', - 'description', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'activity_type', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['call','email','meeting','task','note'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'subject', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'details', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'ownerId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'leadId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'leads', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'dealId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'deals', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'contactId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'contacts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'accountId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'accounts', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'activity_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'due_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'completed_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['planned','completed','canceled'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'activities', - 'priority', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['low','medium','high'], - - - }, - { transaction } - ); - - - - - - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - - - - - await queryInterface.removeColumn( - 'activities', - 'priority', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'completed_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'due_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'activity_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'accountId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'contactId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'dealId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'leadId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'ownerId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'details', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'subject', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'activities', - 'activity_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'description', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'next_follow_up_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'last_activity_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'loss_reason', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'deal_status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'closed_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'expected_close_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'currency', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'amount', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'originating_leadId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'primary_contactId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'accountId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'ownerId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'stageId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'deals', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pipeline_stages', - 'is_active', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pipeline_stages', - 'is_lost', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pipeline_stages', - 'is_won', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pipeline_stages', - 'win_probability', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pipeline_stages', - 'sort_order', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pipeline_stages', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'primary_contactId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'accountId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'next_follow_up_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'last_contacted_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'notes', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'lead_rating', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'lead_status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'sourceId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'ownerId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'phone', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'email', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'company_name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'leads', - 'lead_name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lead_sources', - 'description', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lead_sources', - 'is_active', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lead_sources', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'next_follow_up_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'last_contacted_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'notes', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'linkedin_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'contact_status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'ownerId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'accountId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'job_title', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'phone', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'email', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'contacts', - 'full_name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'notes', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'next_follow_up_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'last_contacted_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'account_status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'ownerId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'address', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'website', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'phone', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'annual_revenue', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'employee_count', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'industry', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'domain', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'accounts', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'permissions', - 'name', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'roles', - 'role_customization', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'roles', - 'name', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'users', - 'app_roleId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'provider', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'passwordResetTokenExpiresAt', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'passwordResetToken', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'emailVerificationTokenExpiresAt', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'emailVerificationToken', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'emailVerified', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'password', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'users', - 'disabled', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'email', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'phoneNumber', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'lastName', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'firstName', - { transaction } - ); - - - - await queryInterface.dropTable('activities', { transaction }); - - - - await queryInterface.dropTable('deals', { transaction }); - - - - await queryInterface.dropTable('pipeline_stages', { transaction }); - - - - await queryInterface.dropTable('leads', { transaction }); - - - - await queryInterface.dropTable('lead_sources', { transaction }); - - - - await queryInterface.dropTable('contacts', { transaction }); - - - - await queryInterface.dropTable('accounts', { transaction }); - - - - await queryInterface.dropTable('permissions', { transaction }); - - - - await queryInterface.dropTable('roles', { transaction }); - - - - await queryInterface.dropTable('users', { transaction }); - - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } + async up(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + + try { + await queryInterface.createTable('users', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + firstName: { type: Sequelize.DataTypes.TEXT }, + lastName: { type: Sequelize.DataTypes.TEXT }, + phoneNumber: { type: Sequelize.DataTypes.TEXT }, + email: { type: Sequelize.DataTypes.TEXT }, + disabled: { type: Sequelize.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, + password: { type: Sequelize.DataTypes.TEXT }, + emailVerified: { type: Sequelize.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, + emailVerificationToken: { type: Sequelize.DataTypes.TEXT }, + emailVerificationTokenExpiresAt: { type: Sequelize.DataTypes.DATE }, + passwordResetToken: { type: Sequelize.DataTypes.TEXT }, + passwordResetTokenExpiresAt: { type: Sequelize.DataTypes.DATE }, + provider: { type: Sequelize.DataTypes.TEXT }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('roles', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + name: { type: Sequelize.DataTypes.TEXT }, + role_customization: { type: Sequelize.DataTypes.TEXT }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('permissions', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + name: { type: Sequelize.DataTypes.TEXT }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.addColumn('users', 'app_roleId', { + type: Sequelize.DataTypes.UUID, + references: { model: 'roles', key: 'id' }, + }, { transaction }); + + await queryInterface.createTable('rolesPermissionsPermissions', { + createdAt: { type: Sequelize.DataTypes.DATE, allowNull: false }, + updatedAt: { type: Sequelize.DataTypes.DATE, allowNull: false }, + roles_permissionsId: { + type: Sequelize.DataTypes.UUID, + allowNull: false, + references: { model: 'roles', key: 'id' }, + primaryKey: true, + }, + permissionId: { + type: Sequelize.DataTypes.UUID, + allowNull: false, + references: { model: 'permissions', key: 'id' }, + primaryKey: true, + }, + }, { transaction }); + + await queryInterface.createTable('usersCustom_permissionsPermissions', { + createdAt: { type: Sequelize.DataTypes.DATE, allowNull: false }, + updatedAt: { type: Sequelize.DataTypes.DATE, allowNull: false }, + users_custom_permissionsId: { + type: Sequelize.DataTypes.UUID, + allowNull: false, + references: { model: 'users', key: 'id' }, + primaryKey: true, + }, + permissionId: { + type: Sequelize.DataTypes.UUID, + allowNull: false, + references: { model: 'permissions', key: 'id' }, + primaryKey: true, + }, + }, { transaction }); + + await queryInterface.createTable('packages', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + title: { type: Sequelize.DataTypes.TEXT }, + description: { type: Sequelize.DataTypes.TEXT }, + price: { type: Sequelize.DataTypes.TEXT }, + duration: { type: Sequelize.DataTypes.TEXT }, + cta: { type: Sequelize.DataTypes.TEXT }, + included_sessions: { type: Sequelize.DataTypes.INTEGER }, + is_active: { type: Sequelize.DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('clients', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + name: { type: Sequelize.DataTypes.TEXT }, + email: { type: Sequelize.DataTypes.TEXT }, + status: { type: Sequelize.DataTypes.ENUM('lead', 'active', 'paused', 'completed'), allowNull: false, defaultValue: 'active' }, + goals: { type: Sequelize.DataTypes.TEXT }, + notes: { type: Sequelize.DataTypes.TEXT }, + company: { type: Sequelize.DataTypes.TEXT }, + role_title: { type: Sequelize.DataTypes.TEXT }, + tags: { type: Sequelize.DataTypes.TEXT }, + next_session_at: { type: Sequelize.DataTypes.DATE }, + last_session_at: { type: Sequelize.DataTypes.DATE }, + packageId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'packages' } }, + ownerId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('intake_leads', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + name: { type: Sequelize.DataTypes.TEXT }, + email: { type: Sequelize.DataTypes.TEXT }, + company: { type: Sequelize.DataTypes.TEXT }, + role_title: { type: Sequelize.DataTypes.TEXT }, + goal: { type: Sequelize.DataTypes.TEXT }, + challenge: { type: Sequelize.DataTypes.TEXT }, + desired_outcome: { type: Sequelize.DataTypes.TEXT }, + source: { type: Sequelize.DataTypes.TEXT }, + status: { type: Sequelize.DataTypes.ENUM('new', 'reviewed', 'invited', 'converted', 'archived'), allowNull: false, defaultValue: 'new' }, + consent_ai_notes: { type: Sequelize.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('sessions', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + clientId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'clients' } }, + title: { type: Sequelize.DataTypes.TEXT }, + session_at: { type: Sequelize.DataTypes.DATE }, + status: { type: Sequelize.DataTypes.ENUM('planned', 'completed', 'draft_review', 'shared'), allowNull: false, defaultValue: 'completed' }, + transcript_notes: { type: Sequelize.DataTypes.TEXT }, + ai_summary: { type: Sequelize.DataTypes.TEXT }, + key_topics: { type: Sequelize.DataTypes.TEXT }, + goals_discussed: { type: Sequelize.DataTypes.TEXT }, + blockers: { type: Sequelize.DataTypes.TEXT }, + commitments: { type: Sequelize.DataTypes.TEXT }, + homework: { type: Sequelize.DataTypes.TEXT }, + emotional_themes: { type: Sequelize.DataTypes.TEXT }, + important_quotes: { type: Sequelize.DataTypes.TEXT }, + follow_up_email: { type: Sequelize.DataTypes.TEXT }, + next_session_prep: { type: Sequelize.DataTypes.TEXT }, + private_coach_notes: { type: Sequelize.DataTypes.TEXT }, + shared_client_notes: { type: Sequelize.DataTypes.TEXT }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('action_items', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + clientId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'clients' } }, + sessionId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'sessions' } }, + title: { type: Sequelize.DataTypes.TEXT }, + due_at: { type: Sequelize.DataTypes.DATE }, + status: { type: Sequelize.DataTypes.ENUM('not_started', 'in_progress', 'done'), allowNull: false, defaultValue: 'not_started' }, + notes: { type: Sequelize.DataTypes.TEXT }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('resources', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + clientId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'clients' } }, + packageId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'packages' } }, + title: { type: Sequelize.DataTypes.TEXT }, + description: { type: Sequelize.DataTypes.TEXT }, + url: { type: Sequelize.DataTypes.TEXT }, + resource_type: { type: Sequelize.DataTypes.ENUM('link', 'worksheet', 'pdf', 'video'), allowNull: false, defaultValue: 'link' }, + is_shared: { type: Sequelize.DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('testimonials', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + name: { type: Sequelize.DataTypes.TEXT }, + role_company: { type: Sequelize.DataTypes.TEXT }, + quote: { type: Sequelize.DataTypes.TEXT }, + photo_url: { type: Sequelize.DataTypes.TEXT }, + visible_on_site: { type: Sequelize.DataTypes.BOOLEAN, allowNull: false, defaultValue: true }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await queryInterface.createTable('prep_briefs', { + id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, primaryKey: true }, + clientId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'clients' } }, + sessionId: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'sessions' } }, + next_session_at: { type: Sequelize.DataTypes.DATE }, + previous_summary: { type: Sequelize.DataTypes.TEXT }, + open_commitments: { type: Sequelize.DataTypes.TEXT }, + suggested_questions: { type: Sequelize.DataTypes.TEXT }, + sensitive_topics: { type: Sequelize.DataTypes.TEXT }, + status: { type: Sequelize.DataTypes.ENUM('draft', 'ready', 'archived'), allowNull: false, defaultValue: 'ready' }, + importHash: { type: Sequelize.DataTypes.STRING(255), allowNull: true, unique: true }, + createdById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + updatedById: { type: Sequelize.DataTypes.UUID, references: { key: 'id', model: 'users' } }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + }, { transaction }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; } + }, + + async down(queryInterface) { + const transaction = await queryInterface.sequelize.transaction(); + try { + const tables = [ + 'prep_briefs', + 'testimonials', + 'resources', + 'action_items', + 'sessions', + 'intake_leads', + 'clients', + 'packages', + 'usersCustom_permissionsPermissions', + 'rolesPermissionsPermissions', + 'permissions', + 'roles', + 'users', + ]; + + for (const table of tables) { + await queryInterface.dropTable(table, { transaction }); + } + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + }, }; diff --git a/backend/src/db/models/action_items.js b/backend/src/db/models/action_items.js new file mode 100644 index 0000000..8247a7b --- /dev/null +++ b/backend/src/db/models/action_items.js @@ -0,0 +1,27 @@ +module.exports = function(sequelize, DataTypes) { + const action_items = sequelize.define( + "action_items", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + title: { type: DataTypes.TEXT }, + due_at: { type: DataTypes.DATE }, + status: { type: DataTypes.ENUM("not_started", "in_progress", "done"), defaultValue: "not_started" }, + notes: { type: DataTypes.TEXT }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + action_items.associate = (db) => { + db.action_items.belongsTo(db.clients, { as: "client", foreignKey: { name: "clientId" }, constraints: false }); + db.action_items.belongsTo(db.sessions, { as: "session", foreignKey: { name: "sessionId" }, constraints: false }); + db.action_items.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.action_items.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return action_items; +}; diff --git a/backend/src/db/models/clients.js b/backend/src/db/models/clients.js new file mode 100644 index 0000000..1143080 --- /dev/null +++ b/backend/src/db/models/clients.js @@ -0,0 +1,37 @@ +module.exports = function(sequelize, DataTypes) { + const clients = sequelize.define( + "clients", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + name: { type: DataTypes.TEXT }, + email: { type: DataTypes.TEXT }, + status: { type: DataTypes.ENUM("lead", "active", "paused", "completed"), defaultValue: "active" }, + goals: { type: DataTypes.TEXT }, + notes: { type: DataTypes.TEXT }, + company: { type: DataTypes.TEXT }, + role_title: { type: DataTypes.TEXT }, + tags: { type: DataTypes.TEXT }, + next_session_at: { type: DataTypes.DATE }, + last_session_at: { type: DataTypes.DATE }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + clients.associate = (db) => { + db.clients.belongsTo(db.packages, { as: "package", foreignKey: { name: "packageId" }, constraints: false }); + db.clients.belongsTo(db.users, { as: "owner", foreignKey: { name: "ownerId" }, constraints: false }); + db.clients.hasMany(db.sessions, { as: "sessions", foreignKey: { name: "clientId" }, constraints: false }); + db.clients.hasMany(db.action_items, { as: "action_items", foreignKey: { name: "clientId" }, constraints: false }); + db.clients.hasMany(db.resources, { as: "resources", foreignKey: { name: "clientId" }, constraints: false }); + db.clients.hasMany(db.prep_briefs, { as: "prep_briefs", foreignKey: { name: "clientId" }, constraints: false }); + db.clients.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.clients.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return clients; +}; diff --git a/backend/src/db/models/intake_leads.js b/backend/src/db/models/intake_leads.js new file mode 100644 index 0000000..1677a72 --- /dev/null +++ b/backend/src/db/models/intake_leads.js @@ -0,0 +1,31 @@ +module.exports = function(sequelize, DataTypes) { + const intake_leads = sequelize.define( + "intake_leads", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + name: { type: DataTypes.TEXT }, + email: { type: DataTypes.TEXT }, + company: { type: DataTypes.TEXT }, + role_title: { type: DataTypes.TEXT }, + goal: { type: DataTypes.TEXT }, + challenge: { type: DataTypes.TEXT }, + desired_outcome: { type: DataTypes.TEXT }, + source: { type: DataTypes.TEXT }, + status: { type: DataTypes.ENUM("new", "reviewed", "invited", "converted", "archived"), defaultValue: "new" }, + consent_ai_notes: { type: DataTypes.BOOLEAN, defaultValue: false }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + intake_leads.associate = (db) => { + db.intake_leads.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.intake_leads.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return intake_leads; +}; diff --git a/backend/src/db/models/packages.js b/backend/src/db/models/packages.js new file mode 100644 index 0000000..dfed89c --- /dev/null +++ b/backend/src/db/models/packages.js @@ -0,0 +1,30 @@ +module.exports = function(sequelize, DataTypes) { + const packages = sequelize.define( + "packages", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + title: { type: DataTypes.TEXT }, + description: { type: DataTypes.TEXT }, + price: { type: DataTypes.TEXT }, + duration: { type: DataTypes.TEXT }, + cta: { type: DataTypes.TEXT }, + included_sessions: { type: DataTypes.INTEGER }, + is_active: { type: DataTypes.BOOLEAN, defaultValue: true }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + packages.associate = (db) => { + db.packages.hasMany(db.clients, { as: "clients", foreignKey: { name: "packageId" }, constraints: false }); + db.packages.hasMany(db.resources, { as: "resources", foreignKey: { name: "packageId" }, constraints: false }); + db.packages.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.packages.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return packages; +}; diff --git a/backend/src/db/models/prep_briefs.js b/backend/src/db/models/prep_briefs.js new file mode 100644 index 0000000..aff8d4a --- /dev/null +++ b/backend/src/db/models/prep_briefs.js @@ -0,0 +1,29 @@ +module.exports = function(sequelize, DataTypes) { + const prep_briefs = sequelize.define( + "prep_briefs", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + next_session_at: { type: DataTypes.DATE }, + previous_summary: { type: DataTypes.TEXT }, + open_commitments: { type: DataTypes.TEXT }, + suggested_questions: { type: DataTypes.TEXT }, + sensitive_topics: { type: DataTypes.TEXT }, + status: { type: DataTypes.ENUM("draft", "ready", "archived"), defaultValue: "ready" }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + prep_briefs.associate = (db) => { + db.prep_briefs.belongsTo(db.clients, { as: "client", foreignKey: { name: "clientId" }, constraints: false }); + db.prep_briefs.belongsTo(db.sessions, { as: "session", foreignKey: { name: "sessionId" }, constraints: false }); + db.prep_briefs.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.prep_briefs.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return prep_briefs; +}; diff --git a/backend/src/db/models/resources.js b/backend/src/db/models/resources.js new file mode 100644 index 0000000..595ac5a --- /dev/null +++ b/backend/src/db/models/resources.js @@ -0,0 +1,28 @@ +module.exports = function(sequelize, DataTypes) { + const resources = sequelize.define( + "resources", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + title: { type: DataTypes.TEXT }, + description: { type: DataTypes.TEXT }, + url: { type: DataTypes.TEXT }, + resource_type: { type: DataTypes.ENUM("link", "worksheet", "pdf", "video"), defaultValue: "link" }, + is_shared: { type: DataTypes.BOOLEAN, defaultValue: true }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + resources.associate = (db) => { + db.resources.belongsTo(db.clients, { as: "client", foreignKey: { name: "clientId" }, constraints: false }); + db.resources.belongsTo(db.packages, { as: "package", foreignKey: { name: "packageId" }, constraints: false }); + db.resources.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.resources.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return resources; +}; diff --git a/backend/src/db/models/sessions.js b/backend/src/db/models/sessions.js new file mode 100644 index 0000000..a5de32e --- /dev/null +++ b/backend/src/db/models/sessions.js @@ -0,0 +1,40 @@ +module.exports = function(sequelize, DataTypes) { + const sessions = sequelize.define( + "sessions", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + title: { type: DataTypes.TEXT }, + session_at: { type: DataTypes.DATE }, + status: { type: DataTypes.ENUM("planned", "completed", "draft_review", "shared"), defaultValue: "completed" }, + transcript_notes: { type: DataTypes.TEXT }, + ai_summary: { type: DataTypes.TEXT }, + key_topics: { type: DataTypes.TEXT }, + goals_discussed: { type: DataTypes.TEXT }, + blockers: { type: DataTypes.TEXT }, + commitments: { type: DataTypes.TEXT }, + homework: { type: DataTypes.TEXT }, + emotional_themes: { type: DataTypes.TEXT }, + important_quotes: { type: DataTypes.TEXT }, + follow_up_email: { type: DataTypes.TEXT }, + next_session_prep: { type: DataTypes.TEXT }, + private_coach_notes: { type: DataTypes.TEXT }, + shared_client_notes: { type: DataTypes.TEXT }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + sessions.associate = (db) => { + db.sessions.belongsTo(db.clients, { as: "client", foreignKey: { name: "clientId" }, constraints: false }); + db.sessions.hasMany(db.action_items, { as: "action_items", foreignKey: { name: "sessionId" }, constraints: false }); + db.sessions.hasMany(db.prep_briefs, { as: "prep_briefs", foreignKey: { name: "sessionId" }, constraints: false }); + db.sessions.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.sessions.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return sessions; +}; diff --git a/backend/src/db/models/testimonials.js b/backend/src/db/models/testimonials.js new file mode 100644 index 0000000..ec428ba --- /dev/null +++ b/backend/src/db/models/testimonials.js @@ -0,0 +1,26 @@ +module.exports = function(sequelize, DataTypes) { + const testimonials = sequelize.define( + "testimonials", + { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + name: { type: DataTypes.TEXT }, + role_company: { type: DataTypes.TEXT }, + quote: { type: DataTypes.TEXT }, + photo_url: { type: DataTypes.TEXT }, + visible_on_site: { type: DataTypes.BOOLEAN, defaultValue: true }, + importHash: { type: DataTypes.STRING(255), allowNull: true, unique: true }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + testimonials.associate = (db) => { + db.testimonials.belongsTo(db.users, { as: "createdBy", constraints: false }); + db.testimonials.belongsTo(db.users, { as: "updatedBy", constraints: false }); + }; + + return testimonials; +}; diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index b781ab6..cdf40ac 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -144,46 +144,8 @@ provider: { - db.users.hasMany(db.accounts, { - as: 'accounts_owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - - db.users.hasMany(db.contacts, { - as: 'contacts_owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - - - db.users.hasMany(db.leads, { - as: 'leads_owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - - - db.users.hasMany(db.deals, { - as: 'deals_owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - - db.users.hasMany(db.activities, { - as: 'activities_owner', + db.users.hasMany(db.clients, { + as: 'clients_owner', foreignKey: { name: 'ownerId', }, @@ -270,4 +232,3 @@ function trimStringFields(users) { return users; } - diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index 1f15452..90b015d 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -1,986 +1,140 @@ - const { v4: uuid } = require("uuid"); module.exports = { - /** - * @param{import("sequelize").QueryInterface} queryInterface - * @return {Promise} - */ async up(queryInterface) { const createdAt = new Date(); const updatedAt = new Date(); + const ids = new Map(); - /** @type {Map} */ - const idMap = new Map(); - - /** - * @param {string} key - * @return {string} - */ function getId(key) { - if (idMap.has(key)) { - return idMap.get(key); + if (!ids.has(key)) { + ids.set(key, uuid()); } - const id = uuid(); - idMap.set(key, id); - return id; + + return ids.get(key); } - await queryInterface.bulkInsert("roles", [ - - - { id: getId("Administrator"), name: "Administrator", createdAt, updatedAt }, - - - - { id: getId("CRMOwner"), name: "CRM Owner", createdAt, updatedAt }, - - { id: getId("SalesManager"), name: "Sales Manager", createdAt, updatedAt }, - - { id: getId("AccountExecutive"), name: "Account Executive", createdAt, updatedAt }, - - { id: getId("SalesDevelopmentRep"), name: "Sales Development Rep", createdAt, updatedAt }, - - { id: getId("SalesOps"), name: "Sales Ops", createdAt, updatedAt }, - - - - { id: getId("Public"), name: "Public", createdAt, updatedAt }, - ]); + function permissionRows(entity) { + const name = entity.toUpperCase(); - /** - * @param {string} name - */ - function createPermissions(name) { return [ - { id: getId(`CREATE_${name.toUpperCase()}`), createdAt, updatedAt, name: `CREATE_${name.toUpperCase()}` }, - { id: getId(`READ_${name.toUpperCase()}`), createdAt, updatedAt, name: `READ_${name.toUpperCase()}` }, - { id: getId(`UPDATE_${name.toUpperCase()}`), createdAt, updatedAt, name: `UPDATE_${name.toUpperCase()}` }, - { id: getId(`DELETE_${name.toUpperCase()}`), createdAt, updatedAt, name: `DELETE_${name.toUpperCase()}` } + { id: getId(`CREATE_${name}`), name: `CREATE_${name}`, createdAt, updatedAt }, + { id: getId(`READ_${name}`), name: `READ_${name}`, createdAt, updatedAt }, + { id: getId(`UPDATE_${name}`), name: `UPDATE_${name}`, createdAt, updatedAt }, + { id: getId(`DELETE_${name}`), name: `DELETE_${name}`, createdAt, updatedAt }, ]; } - const entities = [ - "users","roles","permissions","accounts","contacts","lead_sources","leads","pipeline_stages","deals","activities",, + const roles = [ + { key: "Administrator", name: "Administrator" }, + { key: "WorkspaceOwner", name: "Workspace Owner" }, + { key: "Coach", name: "Coach" }, + { key: "Assistant", name: "Assistant" }, + { key: "Client", name: "Client" }, + { key: "Public", name: "Public" }, ]; -await queryInterface.bulkInsert("permissions", entities.flatMap(createPermissions)); -await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]); -await queryInterface.bulkInsert("permissions", [{ id: getId(`CREATE_SEARCH`), createdAt, updatedAt, name: `CREATE_SEARCH`}]); + const entities = [ + "users", + "roles", + "permissions", + "clients", + "sessions", + "action_items", + "resources", + "packages", + "testimonials", + "prep_briefs", + "intake_leads", + "coaching", + ]; -await queryInterface.sequelize.query(`create table "rolesPermissionsPermissions" -( -"createdAt" timestamp with time zone not null, -"updatedAt" timestamp with time zone not null, -"roles_permissionsId" uuid not null, -"permissionId" uuid not null, -primary key ("roles_permissionsId", "permissionId") -);`); + const permissions = entities.flatMap(permissionRows); + permissions.push({ id: getId("READ_API_DOCS"), name: "READ_API_DOCS", createdAt, updatedAt }); + permissions.push({ id: getId("CREATE_SEARCH"), name: "CREATE_SEARCH", createdAt, updatedAt }); + await queryInterface.bulkInsert( + "roles", + roles.map((role) => ({ + id: getId(role.key), + name: role.name, + createdAt, + updatedAt, + })), + ); -await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_USERS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_USERS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_USERS') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_USERS') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_USERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_USERS') }, - - - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_ACCOUNTS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_ACCOUNTS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('CREATE_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('UPDATE_ACCOUNTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('UPDATE_ACCOUNTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_ACCOUNTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_ACCOUNTS') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_CONTACTS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_CONTACTS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('CREATE_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('UPDATE_CONTACTS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('CREATE_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('UPDATE_CONTACTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_CONTACTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_CONTACTS') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_LEAD_SOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_LEAD_SOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_LEAD_SOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_LEAD_SOURCES') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_LEAD_SOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_LEAD_SOURCES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_LEAD_SOURCES') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_LEAD_SOURCES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('CREATE_LEAD_SOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_LEAD_SOURCES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_LEAD_SOURCES') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_LEADS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_LEADS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('CREATE_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('UPDATE_LEADS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('CREATE_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('UPDATE_LEADS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_LEADS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_LEADS') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_PIPELINE_STAGES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_PIPELINE_STAGES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_PIPELINE_STAGES') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_PIPELINE_STAGES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_PIPELINE_STAGES') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_PIPELINE_STAGES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('CREATE_PIPELINE_STAGES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_PIPELINE_STAGES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_DEALS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_DEALS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('CREATE_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('UPDATE_DEALS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('CREATE_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_DEALS') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_DEALS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_DEALS') }, - - - - - - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('READ_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('UPDATE_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('DELETE_ACTIVITIES') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_ACTIVITIES') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('CREATE_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('READ_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('UPDATE_ACTIVITIES') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('CREATE_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('READ_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('UPDATE_ACTIVITIES') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('READ_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('UPDATE_ACTIVITIES') }, - - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("CRMOwner"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("AccountExecutive"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("SalesDevelopmentRep"), permissionId: getId('CREATE_SEARCH') }, - - { createdAt, updatedAt, roles_permissionsId: getId("SalesOps"), permissionId: getId('CREATE_SEARCH') }, - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_USERS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ROLES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ROLES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ROLES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ROLES') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PERMISSIONS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ACCOUNTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ACCOUNTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ACCOUNTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ACCOUNTS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_CONTACTS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_LEAD_SOURCES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_LEAD_SOURCES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_LEAD_SOURCES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_LEAD_SOURCES') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_LEADS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PIPELINE_STAGES') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_DEALS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_DEALS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_DEALS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_DEALS') }, - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ACTIVITIES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_API_DOCS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_SEARCH') }, + await queryInterface.bulkInsert("permissions", permissions); + + const rolePermissions = []; + + function grant(roleKey, permissionNames) { + for (const permissionName of permissionNames) { + rolePermissions.push({ + createdAt, + updatedAt, + roles_permissionsId: getId(roleKey), + permissionId: getId(permissionName), + }); + } + } + + const allPermissionNames = permissions.map((permission) => permission.name); + grant("Administrator", allPermissionNames); + grant("WorkspaceOwner", allPermissionNames); + + const coachPermissionNames = allPermissionNames.filter((name) => { + return !name.includes("_ROLES") && !name.includes("_PERMISSIONS"); + }); + grant("Coach", coachPermissionNames); + + grant("Assistant", [ + "READ_CLIENTS", + "UPDATE_CLIENTS", + "READ_SESSIONS", + "CREATE_SESSIONS", + "UPDATE_SESSIONS", + "READ_ACTION_ITEMS", + "UPDATE_ACTION_ITEMS", + "READ_RESOURCES", + "CREATE_RESOURCES", + "UPDATE_RESOURCES", + "READ_PACKAGES", + "READ_PREP_BRIEFS", + "READ_INTAKE_LEADS", + "UPDATE_INTAKE_LEADS", + "READ_COACHING", + "CREATE_COACHING", + "CREATE_SEARCH", ]); + grant("Client", [ + "READ_CLIENTS", + "READ_SESSIONS", + "READ_ACTION_ITEMS", + "UPDATE_ACTION_ITEMS", + "READ_RESOURCES", + "READ_PACKAGES", + "READ_COACHING", + ]); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("SuperAdmin")}' WHERE "email"='super_admin@flatlogic.com'`); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("Administrator")}' WHERE "email"='admin@flatlogic.com'`); - - - - - - - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("CRMOwner")}' WHERE "email"='client@hello.com'`); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("SalesManager")}' WHERE "email"='john@doe.com'`); - - - + grant("Public", ["CREATE_INTAKE_LEADS"]); -} + await queryInterface.bulkInsert("rolesPermissionsPermissions", rolePermissions); + + await queryInterface.sequelize.query( + `UPDATE users SET "app_roleId" = '${getId("Administrator")}' WHERE email = 'admin@flatlogic.com';`, + ); + await queryInterface.sequelize.query( + `UPDATE users SET "app_roleId" = '${getId("Coach")}' WHERE email = 'john@doe.com';`, + ); + await queryInterface.sequelize.query( + `UPDATE users SET "app_roleId" = '${getId("Client")}' WHERE email = 'client@hello.com';`, + ); + }, + + async down(queryInterface) { + await queryInterface.bulkDelete("rolesPermissionsPermissions", null, {}); + await queryInterface.bulkDelete("permissions", null, {}); + await queryInterface.bulkDelete("roles", null, {}); + }, }; - diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 445830c..aa61dd9 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -1,4687 +1,226 @@ - - - - - - - - - - - - - - - - - - - - - - -const db = require('../models'); -const Users = db.users; - - - - - - -const Accounts = db.accounts; - -const Contacts = db.contacts; - -const LeadSources = db.lead_sources; - -const Leads = db.leads; - -const PipelineStages = db.pipeline_stages; - -const Deals = db.deals; - -const Activities = db.activities; - - - - - - - -const AccountsData = [ - - { - - - - - "name": "Ada Lovelace", - - - - - - - "domain": "Alan Turing", - - - - - - - "industry": "Alan Turing", - - - - - - - "employee_count": 3, - - - - - - - "annual_revenue": 7.6, - - - - - - - "phone": "Alan Turing", - - - - - - - "website": "Alan Turing", - - - - - - - "address": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - "account_status": "partner", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "notes": "Marie Curie", - - - - }, - - { - - - - - "name": "Ada Lovelace", - - - - - - - "domain": "Alan Turing", - - - - - - - "industry": "Grace Hopper", - - - - - - - "employee_count": 6, - - - - - - - "annual_revenue": 2.06, - - - - - - - "phone": "Alan Turing", - - - - - - - "website": "Marie Curie", - - - - - - - "address": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - "account_status": "former_customer", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "notes": "Marie Curie", - - - - }, - - { - - - - - "name": "Ada Lovelace", - - - - - - - "domain": "Marie Curie", - - - - - - - "industry": "Alan Turing", - - - - - - - "employee_count": 4, - - - - - - - "annual_revenue": 2.64, - - - - - - - "phone": "Ada Lovelace", - - - - - - - "website": "Alan Turing", - - - - - - - "address": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - "account_status": "active_customer", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "notes": "Grace Hopper", - - - - }, - - { - - - - - "name": "Alan Turing", - - - - - - - "domain": "Ada Lovelace", - - - - - - - "industry": "Marie Curie", - - - - - - - "employee_count": 8, - - - - - - - "annual_revenue": 7.71, - - - - - - - "phone": "Alan Turing", - - - - - - - "website": "Grace Hopper", - - - - - - - "address": "Grace Hopper", - - - - - - - // type code here for "relation_one" field - - - - - - - "account_status": "active_customer", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "notes": "Marie Curie", - - - - }, - - { - - - - - "name": "Alan Turing", - - - - - - - "domain": "Marie Curie", - - - - - - - "industry": "Marie Curie", - - - - - - - "employee_count": 8, - - - - - - - "annual_revenue": 2.69, - - - - - - - "phone": "Alan Turing", - - - - - - - "website": "Marie Curie", - - - - - - - "address": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - "account_status": "partner", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "notes": "Alan Turing", - - - - }, - -]; - - - -const ContactsData = [ - - { - - - - - "full_name": "Alan Turing", - - - - - - - "email": "Marie Curie", - - - - - - - "phone": "Alan Turing", - - - - - - - "job_title": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "contact_status": "unsubscribed", - - - - - - - "linkedin_url": "Ada Lovelace", - - - - - - - "notes": "Alan Turing", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - }, - - { - - - - - "full_name": "Ada Lovelace", - - - - - - - "email": "Ada Lovelace", - - - - - - - "phone": "Ada Lovelace", - - - - - - - "job_title": "Grace Hopper", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "contact_status": "active", - - - - - - - "linkedin_url": "Grace Hopper", - - - - - - - "notes": "Ada Lovelace", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - }, - - { - - - - - "full_name": "Grace Hopper", - - - - - - - "email": "Ada Lovelace", - - - - - - - "phone": "Marie Curie", - - - - - - - "job_title": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "contact_status": "active", - - - - - - - "linkedin_url": "Ada Lovelace", - - - - - - - "notes": "Marie Curie", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - }, - - { - - - - - "full_name": "Grace Hopper", - - - - - - - "email": "Alan Turing", - - - - - - - "phone": "Marie Curie", - - - - - - - "job_title": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "contact_status": "unsubscribed", - - - - - - - "linkedin_url": "Ada Lovelace", - - - - - - - "notes": "Marie Curie", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - }, - - { - - - - - "full_name": "Grace Hopper", - - - - - - - "email": "Grace Hopper", - - - - - - - "phone": "Ada Lovelace", - - - - - - - "job_title": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "contact_status": "active", - - - - - - - "linkedin_url": "Marie Curie", - - - - - - - "notes": "Alan Turing", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - }, - -]; - - - -const LeadSourcesData = [ - - { - - - - - "name": "Marie Curie", - - - - - - - "is_active": false, - - - - - - - "description": "Marie Curie", - - - - }, - - { - - - - - "name": "Grace Hopper", - - - - - - - "is_active": true, - - - - - - - "description": "Ada Lovelace", - - - - }, - - { - - - - - "name": "Grace Hopper", - - - - - - - "is_active": false, - - - - - - - "description": "Ada Lovelace", - - - - }, - - { - - - - - "name": "Marie Curie", - - - - - - - "is_active": true, - - - - - - - "description": "Grace Hopper", - - - - }, - - { - - - - - "name": "Ada Lovelace", - - - - - - - "is_active": true, - - - - - - - "description": "Marie Curie", - - - - }, - -]; - - - -const LeadsData = [ - - { - - - - - "lead_name": "Ada Lovelace", - - - - - - - "company_name": "Alan Turing", - - - - - - - "email": "Grace Hopper", - - - - - - - "phone": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "lead_status": "converted", - - - - - - - "lead_rating": "hot", - - - - - - - "notes": "Ada Lovelace", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "lead_name": "Grace Hopper", - - - - - - - "company_name": "Marie Curie", - - - - - - - "email": "Alan Turing", - - - - - - - "phone": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "lead_status": "contacted", - - - - - - - "lead_rating": "cold", - - - - - - - "notes": "Marie Curie", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "lead_name": "Grace Hopper", - - - - - - - "company_name": "Ada Lovelace", - - - - - - - "email": "Marie Curie", - - - - - - - "phone": "Grace Hopper", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "lead_status": "unqualified", - - - - - - - "lead_rating": "cold", - - - - - - - "notes": "Grace Hopper", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "lead_name": "Alan Turing", - - - - - - - "company_name": "Grace Hopper", - - - - - - - "email": "Ada Lovelace", - - - - - - - "phone": "Marie Curie", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "lead_status": "unqualified", - - - - - - - "lead_rating": "hot", - - - - - - - "notes": "Marie Curie", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - }, - - { - - - - - "lead_name": "Alan Turing", - - - - - - - "company_name": "Ada Lovelace", - - - - - - - "email": "Ada Lovelace", - - - - - - - "phone": "Grace Hopper", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "lead_status": "contacted", - - - - - - - "lead_rating": "cold", - - - - - - - "notes": "Marie Curie", - - - - - - - "last_contacted_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - }, - -]; - - - -const PipelineStagesData = [ - - { - - - - - "name": "Ada Lovelace", - - - - - - - "sort_order": 6, - - - - - - - "win_probability": 5.85, - - - - - - - "is_won": true, - - - - - - - "is_lost": true, - - - - - - - "is_active": false, - - - - }, - - { - - - - - "name": "Marie Curie", - - - - - - - "sort_order": 5, - - - - - - - "win_probability": 0.2, - - - - - - - "is_won": false, - - - - - - - "is_lost": true, - - - - - - - "is_active": true, - - - - }, - - { - - - - - "name": "Alan Turing", - - - - - - - "sort_order": 5, - - - - - - - "win_probability": 6.76, - - - - - - - "is_won": false, - - - - - - - "is_lost": true, - - - - - - - "is_active": true, - - - - }, - - { - - - - - "name": "Grace Hopper", - - - - - - - "sort_order": 9, - - - - - - - "win_probability": 9.52, - - - - - - - "is_won": false, - - - - - - - "is_lost": true, - - - - - - - "is_active": true, - - - - }, - - { - - - - - "name": "Marie Curie", - - - - - - - "sort_order": 8, - - - - - - - "win_probability": 7.95, - - - - - - - "is_won": false, - - - - - - - "is_lost": false, - - - - - - - "is_active": true, - - - - }, - -]; - - - -const DealsData = [ - - { - - - - - "name": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "amount": 7.0, - - - - - - - "currency": "Alan Turing", - - - - - - - "expected_close_at": new Date(Date.now()), - - - - - - - "closed_at": new Date(Date.now()), - - - - - - - "deal_status": "lost", - - - - - - - "loss_reason": "Ada Lovelace", - - - - - - - "last_activity_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "description": "Ada Lovelace", - - - - }, - - { - - - - - "name": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "amount": 6.55, - - - - - - - "currency": "Ada Lovelace", - - - - - - - "expected_close_at": new Date(Date.now()), - - - - - - - "closed_at": new Date(Date.now()), - - - - - - - "deal_status": "lost", - - - - - - - "loss_reason": "Grace Hopper", - - - - - - - "last_activity_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "description": "Grace Hopper", - - - - }, - - { - - - - - "name": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "amount": 7.08, - - - - - - - "currency": "Marie Curie", - - - - - - - "expected_close_at": new Date(Date.now()), - - - - - - - "closed_at": new Date(Date.now()), - - - - - - - "deal_status": "lost", - - - - - - - "loss_reason": "Grace Hopper", - - - - - - - "last_activity_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "description": "Ada Lovelace", - - - - }, - - { - - - - - "name": "Marie Curie", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "amount": 0.11, - - - - - - - "currency": "Alan Turing", - - - - - - - "expected_close_at": new Date(Date.now()), - - - - - - - "closed_at": new Date(Date.now()), - - - - - - - "deal_status": "lost", - - - - - - - "loss_reason": "Ada Lovelace", - - - - - - - "last_activity_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "description": "Grace Hopper", - - - - }, - - { - - - - - "name": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "amount": 5.7, - - - - - - - "currency": "Grace Hopper", - - - - - - - "expected_close_at": new Date(Date.now()), - - - - - - - "closed_at": new Date(Date.now()), - - - - - - - "deal_status": "won", - - - - - - - "loss_reason": "Grace Hopper", - - - - - - - "last_activity_at": new Date(Date.now()), - - - - - - - "next_follow_up_at": new Date(Date.now()), - - - - - - - "description": "Grace Hopper", - - - - }, - -]; - - - -const ActivitiesData = [ - - { - - - - - "activity_type": "email", - - - - - - - "subject": "Alan Turing", - - - - - - - "details": "Marie Curie", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "activity_at": new Date(Date.now()), - - - - - - - "due_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "status": "planned", - - - - - - - "priority": "low", - - - - - - - // type code here for "files" field - - - - }, - - { - - - - - "activity_type": "note", - - - - - - - "subject": "Grace Hopper", - - - - - - - "details": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "activity_at": new Date(Date.now()), - - - - - - - "due_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "status": "canceled", - - - - - - - "priority": "medium", - - - - - - - // type code here for "files" field - - - - }, - - { - - - - - "activity_type": "task", - - - - - - - "subject": "Alan Turing", - - - - - - - "details": "Alan Turing", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "activity_at": new Date(Date.now()), - - - - - - - "due_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "status": "canceled", - - - - - - - "priority": "low", - - - - - - - // type code here for "files" field - - - - }, - - { - - - - - "activity_type": "note", - - - - - - - "subject": "Ada Lovelace", - - - - - - - "details": "Marie Curie", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "activity_at": new Date(Date.now()), - - - - - - - "due_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "status": "completed", - - - - - - - "priority": "high", - - - - - - - // type code here for "files" field - - - - }, - - { - - - - - "activity_type": "note", - - - - - - - "subject": "Ada Lovelace", - - - - - - - "details": "Ada Lovelace", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "activity_at": new Date(Date.now()), - - - - - - - "due_at": new Date(Date.now()), - - - - - - - "completed_at": new Date(Date.now()), - - - - - - - "status": "completed", - - - - - - - "priority": "medium", - - - - - - - // type code here for "files" field - - - - }, - -]; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Similar logic for "relation_many" - - - - - - - - - - - - - - - - - - - - - - - - - - - async function associateAccountWithOwner() { - - const relatedOwner0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Account0 = await Accounts.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Account0?.setOwner) - { - await - Account0. - setOwner(relatedOwner0); - } - - const relatedOwner1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Account1 = await Accounts.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Account1?.setOwner) - { - await - Account1. - setOwner(relatedOwner1); - } - - const relatedOwner2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Account2 = await Accounts.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Account2?.setOwner) - { - await - Account2. - setOwner(relatedOwner2); - } - - const relatedOwner3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Account3 = await Accounts.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Account3?.setOwner) - { - await - Account3. - setOwner(relatedOwner3); - } - - const relatedOwner4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Account4 = await Accounts.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Account4?.setOwner) - { - await - Account4. - setOwner(relatedOwner4); - } - - } - - - - - - - - - - - - - - - - - - - - - - - - - async function associateContactWithAccount() { - - const relatedAccount0 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Contact0 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Contact0?.setAccount) - { - await - Contact0. - setAccount(relatedAccount0); - } - - const relatedAccount1 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Contact1 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Contact1?.setAccount) - { - await - Contact1. - setAccount(relatedAccount1); - } - - const relatedAccount2 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Contact2 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Contact2?.setAccount) - { - await - Contact2. - setAccount(relatedAccount2); - } - - const relatedAccount3 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Contact3 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Contact3?.setAccount) - { - await - Contact3. - setAccount(relatedAccount3); - } - - const relatedAccount4 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Contact4 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Contact4?.setAccount) - { - await - Contact4. - setAccount(relatedAccount4); - } - - } - - - - - async function associateContactWithOwner() { - - const relatedOwner0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Contact0 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Contact0?.setOwner) - { - await - Contact0. - setOwner(relatedOwner0); - } - - const relatedOwner1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Contact1 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Contact1?.setOwner) - { - await - Contact1. - setOwner(relatedOwner1); - } - - const relatedOwner2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Contact2 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Contact2?.setOwner) - { - await - Contact2. - setOwner(relatedOwner2); - } - - const relatedOwner3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Contact3 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Contact3?.setOwner) - { - await - Contact3. - setOwner(relatedOwner3); - } - - const relatedOwner4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Contact4 = await Contacts.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Contact4?.setOwner) - { - await - Contact4. - setOwner(relatedOwner4); - } - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - async function associateLeadWithOwner() { - - const relatedOwner0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Lead0 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Lead0?.setOwner) - { - await - Lead0. - setOwner(relatedOwner0); - } - - const relatedOwner1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Lead1 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Lead1?.setOwner) - { - await - Lead1. - setOwner(relatedOwner1); - } - - const relatedOwner2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Lead2 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Lead2?.setOwner) - { - await - Lead2. - setOwner(relatedOwner2); - } - - const relatedOwner3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Lead3 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Lead3?.setOwner) - { - await - Lead3. - setOwner(relatedOwner3); - } - - const relatedOwner4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Lead4 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Lead4?.setOwner) - { - await - Lead4. - setOwner(relatedOwner4); - } - - } - - - - - async function associateLeadWithSource() { - - const relatedSource0 = await LeadSources.findOne({ - offset: Math.floor(Math.random() * (await LeadSources.count())), - }); - const Lead0 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Lead0?.setSource) - { - await - Lead0. - setSource(relatedSource0); - } - - const relatedSource1 = await LeadSources.findOne({ - offset: Math.floor(Math.random() * (await LeadSources.count())), - }); - const Lead1 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Lead1?.setSource) - { - await - Lead1. - setSource(relatedSource1); - } - - const relatedSource2 = await LeadSources.findOne({ - offset: Math.floor(Math.random() * (await LeadSources.count())), - }); - const Lead2 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Lead2?.setSource) - { - await - Lead2. - setSource(relatedSource2); - } - - const relatedSource3 = await LeadSources.findOne({ - offset: Math.floor(Math.random() * (await LeadSources.count())), - }); - const Lead3 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Lead3?.setSource) - { - await - Lead3. - setSource(relatedSource3); - } - - const relatedSource4 = await LeadSources.findOne({ - offset: Math.floor(Math.random() * (await LeadSources.count())), - }); - const Lead4 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Lead4?.setSource) - { - await - Lead4. - setSource(relatedSource4); - } - - } - - - - - - - - - - - - - - - async function associateLeadWithAccount() { - - const relatedAccount0 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Lead0 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Lead0?.setAccount) - { - await - Lead0. - setAccount(relatedAccount0); - } - - const relatedAccount1 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Lead1 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Lead1?.setAccount) - { - await - Lead1. - setAccount(relatedAccount1); - } - - const relatedAccount2 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Lead2 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Lead2?.setAccount) - { - await - Lead2. - setAccount(relatedAccount2); - } - - const relatedAccount3 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Lead3 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Lead3?.setAccount) - { - await - Lead3. - setAccount(relatedAccount3); - } - - const relatedAccount4 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Lead4 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Lead4?.setAccount) - { - await - Lead4. - setAccount(relatedAccount4); - } - - } - - - - - async function associateLeadWithPrimary_contact() { - - const relatedPrimary_contact0 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Lead0 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Lead0?.setPrimary_contact) - { - await - Lead0. - setPrimary_contact(relatedPrimary_contact0); - } - - const relatedPrimary_contact1 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Lead1 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Lead1?.setPrimary_contact) - { - await - Lead1. - setPrimary_contact(relatedPrimary_contact1); - } - - const relatedPrimary_contact2 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Lead2 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Lead2?.setPrimary_contact) - { - await - Lead2. - setPrimary_contact(relatedPrimary_contact2); - } - - const relatedPrimary_contact3 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Lead3 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Lead3?.setPrimary_contact) - { - await - Lead3. - setPrimary_contact(relatedPrimary_contact3); - } - - const relatedPrimary_contact4 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Lead4 = await Leads.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Lead4?.setPrimary_contact) - { - await - Lead4. - setPrimary_contact(relatedPrimary_contact4); - } - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - async function associateDealWithStage() { - - const relatedStage0 = await PipelineStages.findOne({ - offset: Math.floor(Math.random() * (await PipelineStages.count())), - }); - const Deal0 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Deal0?.setStage) - { - await - Deal0. - setStage(relatedStage0); - } - - const relatedStage1 = await PipelineStages.findOne({ - offset: Math.floor(Math.random() * (await PipelineStages.count())), - }); - const Deal1 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Deal1?.setStage) - { - await - Deal1. - setStage(relatedStage1); - } - - const relatedStage2 = await PipelineStages.findOne({ - offset: Math.floor(Math.random() * (await PipelineStages.count())), - }); - const Deal2 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Deal2?.setStage) - { - await - Deal2. - setStage(relatedStage2); - } - - const relatedStage3 = await PipelineStages.findOne({ - offset: Math.floor(Math.random() * (await PipelineStages.count())), - }); - const Deal3 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Deal3?.setStage) - { - await - Deal3. - setStage(relatedStage3); - } - - const relatedStage4 = await PipelineStages.findOne({ - offset: Math.floor(Math.random() * (await PipelineStages.count())), - }); - const Deal4 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Deal4?.setStage) - { - await - Deal4. - setStage(relatedStage4); - } - - } - - - - - async function associateDealWithOwner() { - - const relatedOwner0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Deal0 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Deal0?.setOwner) - { - await - Deal0. - setOwner(relatedOwner0); - } - - const relatedOwner1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Deal1 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Deal1?.setOwner) - { - await - Deal1. - setOwner(relatedOwner1); - } - - const relatedOwner2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Deal2 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Deal2?.setOwner) - { - await - Deal2. - setOwner(relatedOwner2); - } - - const relatedOwner3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Deal3 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Deal3?.setOwner) - { - await - Deal3. - setOwner(relatedOwner3); - } - - const relatedOwner4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Deal4 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Deal4?.setOwner) - { - await - Deal4. - setOwner(relatedOwner4); - } - - } - - - - - async function associateDealWithAccount() { - - const relatedAccount0 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Deal0 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Deal0?.setAccount) - { - await - Deal0. - setAccount(relatedAccount0); - } - - const relatedAccount1 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Deal1 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Deal1?.setAccount) - { - await - Deal1. - setAccount(relatedAccount1); - } - - const relatedAccount2 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Deal2 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Deal2?.setAccount) - { - await - Deal2. - setAccount(relatedAccount2); - } - - const relatedAccount3 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Deal3 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Deal3?.setAccount) - { - await - Deal3. - setAccount(relatedAccount3); - } - - const relatedAccount4 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Deal4 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Deal4?.setAccount) - { - await - Deal4. - setAccount(relatedAccount4); - } - - } - - - - - async function associateDealWithPrimary_contact() { - - const relatedPrimary_contact0 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Deal0 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Deal0?.setPrimary_contact) - { - await - Deal0. - setPrimary_contact(relatedPrimary_contact0); - } - - const relatedPrimary_contact1 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Deal1 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Deal1?.setPrimary_contact) - { - await - Deal1. - setPrimary_contact(relatedPrimary_contact1); - } - - const relatedPrimary_contact2 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Deal2 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Deal2?.setPrimary_contact) - { - await - Deal2. - setPrimary_contact(relatedPrimary_contact2); - } - - const relatedPrimary_contact3 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Deal3 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Deal3?.setPrimary_contact) - { - await - Deal3. - setPrimary_contact(relatedPrimary_contact3); - } - - const relatedPrimary_contact4 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Deal4 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Deal4?.setPrimary_contact) - { - await - Deal4. - setPrimary_contact(relatedPrimary_contact4); - } - - } - - - - - async function associateDealWithOriginating_lead() { - - const relatedOriginating_lead0 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Deal0 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Deal0?.setOriginating_lead) - { - await - Deal0. - setOriginating_lead(relatedOriginating_lead0); - } - - const relatedOriginating_lead1 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Deal1 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Deal1?.setOriginating_lead) - { - await - Deal1. - setOriginating_lead(relatedOriginating_lead1); - } - - const relatedOriginating_lead2 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Deal2 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Deal2?.setOriginating_lead) - { - await - Deal2. - setOriginating_lead(relatedOriginating_lead2); - } - - const relatedOriginating_lead3 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Deal3 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Deal3?.setOriginating_lead) - { - await - Deal3. - setOriginating_lead(relatedOriginating_lead3); - } - - const relatedOriginating_lead4 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Deal4 = await Deals.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Deal4?.setOriginating_lead) - { - await - Deal4. - setOriginating_lead(relatedOriginating_lead4); - } - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - async function associateActivityWithOwner() { - - const relatedOwner0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Activity0 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Activity0?.setOwner) - { - await - Activity0. - setOwner(relatedOwner0); - } - - const relatedOwner1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Activity1 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Activity1?.setOwner) - { - await - Activity1. - setOwner(relatedOwner1); - } - - const relatedOwner2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Activity2 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Activity2?.setOwner) - { - await - Activity2. - setOwner(relatedOwner2); - } - - const relatedOwner3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Activity3 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Activity3?.setOwner) - { - await - Activity3. - setOwner(relatedOwner3); - } - - const relatedOwner4 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Activity4 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Activity4?.setOwner) - { - await - Activity4. - setOwner(relatedOwner4); - } - - } - - - - - async function associateActivityWithLead() { - - const relatedLead0 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Activity0 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Activity0?.setLead) - { - await - Activity0. - setLead(relatedLead0); - } - - const relatedLead1 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Activity1 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Activity1?.setLead) - { - await - Activity1. - setLead(relatedLead1); - } - - const relatedLead2 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Activity2 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Activity2?.setLead) - { - await - Activity2. - setLead(relatedLead2); - } - - const relatedLead3 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Activity3 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Activity3?.setLead) - { - await - Activity3. - setLead(relatedLead3); - } - - const relatedLead4 = await Leads.findOne({ - offset: Math.floor(Math.random() * (await Leads.count())), - }); - const Activity4 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Activity4?.setLead) - { - await - Activity4. - setLead(relatedLead4); - } - - } - - - - - async function associateActivityWithDeal() { - - const relatedDeal0 = await Deals.findOne({ - offset: Math.floor(Math.random() * (await Deals.count())), - }); - const Activity0 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Activity0?.setDeal) - { - await - Activity0. - setDeal(relatedDeal0); - } - - const relatedDeal1 = await Deals.findOne({ - offset: Math.floor(Math.random() * (await Deals.count())), - }); - const Activity1 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Activity1?.setDeal) - { - await - Activity1. - setDeal(relatedDeal1); - } - - const relatedDeal2 = await Deals.findOne({ - offset: Math.floor(Math.random() * (await Deals.count())), - }); - const Activity2 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Activity2?.setDeal) - { - await - Activity2. - setDeal(relatedDeal2); - } - - const relatedDeal3 = await Deals.findOne({ - offset: Math.floor(Math.random() * (await Deals.count())), - }); - const Activity3 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Activity3?.setDeal) - { - await - Activity3. - setDeal(relatedDeal3); - } - - const relatedDeal4 = await Deals.findOne({ - offset: Math.floor(Math.random() * (await Deals.count())), - }); - const Activity4 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Activity4?.setDeal) - { - await - Activity4. - setDeal(relatedDeal4); - } - - } - - - - - async function associateActivityWithContact() { - - const relatedContact0 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Activity0 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Activity0?.setContact) - { - await - Activity0. - setContact(relatedContact0); - } - - const relatedContact1 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Activity1 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Activity1?.setContact) - { - await - Activity1. - setContact(relatedContact1); - } - - const relatedContact2 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Activity2 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Activity2?.setContact) - { - await - Activity2. - setContact(relatedContact2); - } - - const relatedContact3 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Activity3 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Activity3?.setContact) - { - await - Activity3. - setContact(relatedContact3); - } - - const relatedContact4 = await Contacts.findOne({ - offset: Math.floor(Math.random() * (await Contacts.count())), - }); - const Activity4 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Activity4?.setContact) - { - await - Activity4. - setContact(relatedContact4); - } - - } - - - - - async function associateActivityWithAccount() { - - const relatedAccount0 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Activity0 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Activity0?.setAccount) - { - await - Activity0. - setAccount(relatedAccount0); - } - - const relatedAccount1 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Activity1 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Activity1?.setAccount) - { - await - Activity1. - setAccount(relatedAccount1); - } - - const relatedAccount2 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Activity2 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Activity2?.setAccount) - { - await - Activity2. - setAccount(relatedAccount2); - } - - const relatedAccount3 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Activity3 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Activity3?.setAccount) - { - await - Activity3. - setAccount(relatedAccount3); - } - - const relatedAccount4 = await Accounts.findOne({ - offset: Math.floor(Math.random() * (await Accounts.count())), - }); - const Activity4 = await Activities.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Activity4?.setAccount) - { - await - Activity4. - setAccount(relatedAccount4); - } - - } - - - - - - - - - - - - - - - +const coachUserId = "af5a87be-8f9c-4630-902a-37a60b7005ba"; module.exports = { - up: async (queryInterface, Sequelize) => { - - - - - - - - await Accounts.bulkCreate(AccountsData); - - - - - await Contacts.bulkCreate(ContactsData); - - - - - await LeadSources.bulkCreate(LeadSourcesData); - - - - - await Leads.bulkCreate(LeadsData); - - - - - await PipelineStages.bulkCreate(PipelineStagesData); - - - - - await Deals.bulkCreate(DealsData); - - - - - await Activities.bulkCreate(ActivitiesData); - - - await Promise.all([ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Similar logic for "relation_many" - - - - - - - - - - - - - - - - - - - - - - - - - - await associateAccountWithOwner(), - - - - - - - - - - - - - - - - - - - - - - - - await associateContactWithAccount(), - - - - - await associateContactWithOwner(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - await associateLeadWithOwner(), - - - - - await associateLeadWithSource(), - - - - - - - - - - - - - - - await associateLeadWithAccount(), - - - - - await associateLeadWithPrimary_contact(), - - - - - - - - - - - - - - - - - - - - - - - - - await associateDealWithStage(), - - - - - await associateDealWithOwner(), - - - - - await associateDealWithAccount(), - - - - - await associateDealWithPrimary_contact(), - - - - - await associateDealWithOriginating_lead(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - await associateActivityWithOwner(), - - - - - await associateActivityWithLead(), - - - - - await associateActivityWithDeal(), - - - - - await associateActivityWithContact(), - - - - - await associateActivityWithAccount(), - - - - - - - - - - - - - - - - ]); - - }, + async up(queryInterface) { + const now = new Date(); + const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000); + const nextWeek = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - down: async (queryInterface, Sequelize) => { - - - - - - - await queryInterface.bulkDelete('accounts', null, {}); - - - await queryInterface.bulkDelete('contacts', null, {}); - - - await queryInterface.bulkDelete('lead_sources', null, {}); - - - await queryInterface.bulkDelete('leads', null, {}); - - - await queryInterface.bulkDelete('pipeline_stages', null, {}); - - - await queryInterface.bulkDelete('deals', null, {}); - - - await queryInterface.bulkDelete('activities', null, {}); - - - }, -}; \ No newline at end of file + const packageId = "4e529dc6-5c02-4903-9b47-0ab0adac1001"; + const clientAId = "98405fa4-fec0-4207-98d0-5d2c60ba4001"; + const clientBId = "98405fa4-fec0-4207-98d0-5d2c60ba4002"; + const sessionAId = "2263bfd3-3ee9-49f3-9af8-c78ce49f1001"; + const sessionBId = "2263bfd3-3ee9-49f3-9af8-c78ce49f1002"; + + await queryInterface.bulkInsert("packages", [ + { + id: packageId, + title: "Executive Momentum", + description: "A focused coaching package for founders and operators who need sharper decisions, clearer priorities, and calmer execution.", + price: "$2,400", + duration: "8 weeks", + cta: "Book discovery call", + included_sessions: 6, + is_active: true, + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + + await queryInterface.bulkInsert("clients", [ + { + id: clientAId, + name: "Maya Chen", + email: "maya@example.com", + status: "active", + goals: "Delegate operational decisions, build a steadier weekly planning rhythm, and prepare for a senior leadership transition.", + notes: "Prefers direct feedback and concise written follow-ups. Avoid overloading with frameworks.", + company: "Northstar Labs", + role_title: "Founder", + tags: "founder,leadership,delegation", + next_session_at: tomorrow, + last_session_at: now, + packageId, + ownerId: coachUserId, + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + { + id: clientBId, + name: "Daniel Brooks", + email: "daniel@example.com", + status: "active", + goals: "Move from reactive management to repeatable team rituals and stronger feedback conversations.", + notes: "Responds well to experiments with clear success criteria.", + company: "Finch Studio", + role_title: "Head of Product", + tags: "product,management,feedback", + next_session_at: nextWeek, + last_session_at: now, + packageId, + ownerId: coachUserId, + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + + await queryInterface.bulkInsert("sessions", [ + { + id: sessionAId, + clientId: clientAId, + title: "Delegation and decision boundaries", + session_at: now, + status: "completed", + transcript_notes: "Maya noticed she still reviews every hiring decision. We mapped which decisions need founder review and which can be delegated.", + ai_summary: "Maya is ready to test a decision-rights matrix for hiring and customer escalations. The central theme was trusting senior leads without disappearing from critical calls.", + key_topics: "delegation,decision rights,hiring,leadership transition", + goals_discussed: "Reduce founder bottlenecks; create a weekly leadership decision review.", + blockers: "Fear that delegated decisions will lower quality; unclear escalation rules.", + commitments: "Draft decision-rights matrix before Friday and review with COO.", + homework: "Write three examples of decisions Maya will stop approving personally.", + emotional_themes: "Cautious optimism, control, trust", + important_quotes: "\"I know I am the bottleneck, but I do not know what good letting go looks like.\"", + follow_up_email: "Thanks for today. Your next step is to draft the decision-rights matrix and identify three decisions you will stop approving personally.", + next_session_prep: "Ask what happened when Maya shared the matrix with the COO.", + private_coach_notes: "Watch for over-correcting into vague oversight. She needs specific operating agreements.", + shared_client_notes: "This week is about making delegation concrete through visible decision boundaries.", + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + { + id: sessionBId, + clientId: clientBId, + title: "Feedback rituals", + session_at: now, + status: "completed", + transcript_notes: "Daniel wants his weekly 1:1s to feel less like status updates. We designed a three-question feedback pattern.", + ai_summary: "Daniel will pilot a simple 1:1 structure that separates status, friction, and feedback. He wants the team to surface blockers earlier.", + key_topics: "1:1s,feedback,team rituals,management", + goals_discussed: "Improve team feedback cadence; reduce late surprises.", + blockers: "Daniel worries direct questions may feel too intense for newer team members.", + commitments: "Pilot the 1:1 structure with two team members this week.", + homework: "Ask each team member: What is stuck? What needs a decision? What feedback do you have for me?", + emotional_themes: "Curiosity, hesitation, responsibility", + important_quotes: "\"I want fewer surprises without becoming the person everyone fears updating.\"", + follow_up_email: "Your experiment this week is two 1:1s with the new three-question structure. Keep notes on what changed.", + next_session_prep: "Review what surfaced earlier and whether the questions need softening.", + private_coach_notes: "He benefits from practical scripts more than theory.", + shared_client_notes: "Use structure to make feedback safer and more routine.", + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + + await queryInterface.bulkInsert("action_items", [ + { + id: "5c58d06d-15f2-4f6d-9a61-6d495c5d5001", + clientId: clientAId, + sessionId: sessionAId, + title: "Draft decision-rights matrix", + due_at: tomorrow, + status: "in_progress", + notes: "Start with hiring, customer escalations, and roadmap tradeoffs.", + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + { + id: "5c58d06d-15f2-4f6d-9a61-6d495c5d5002", + clientId: clientBId, + sessionId: sessionBId, + title: "Pilot new 1:1 structure", + due_at: nextWeek, + status: "not_started", + notes: "Run with two team members and capture what surfaced earlier.", + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + + await queryInterface.bulkInsert("resources", [ + { + id: "2d913f0b-3277-4244-9c8d-102f57cd7001", + clientId: clientAId, + packageId, + title: "Decision Rights Worksheet", + description: "A worksheet for separating owner-only decisions from team-owned decisions.", + url: "https://example.com/decision-rights", + resource_type: "worksheet", + is_shared: true, + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + { + id: "2d913f0b-3277-4244-9c8d-102f57cd7002", + clientId: clientBId, + packageId, + title: "1:1 Feedback Script", + description: "A lightweight script for making feedback and blockers part of every 1:1.", + url: "https://example.com/feedback-script", + resource_type: "worksheet", + is_shared: true, + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + + await queryInterface.bulkInsert("testimonials", [ + { + id: "f9476bc0-5c2a-406a-a329-dbcd1b4a9001", + name: "Ari Morgan", + role_company: "CEO, Fieldline", + quote: "The sessions turned scattered leadership pressure into a simple operating rhythm we could actually keep.", + visible_on_site: true, + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + + await queryInterface.bulkInsert("prep_briefs", [ + { + id: "f5e25961-6fd7-4038-870c-12db120d3001", + clientId: clientAId, + sessionId: sessionAId, + next_session_at: tomorrow, + previous_summary: "Maya committed to drafting decision boundaries and testing them with her COO.", + open_commitments: "Decision-rights matrix; three decisions no longer requiring founder approval.", + suggested_questions: "What felt risky to delegate? Where did the team ask for more clarity? What decision still needs your direct voice?", + sensitive_topics: "Founder control and trust in senior leadership.", + status: "ready", + createdById: coachUserId, + updatedById: coachUserId, + createdAt: now, + updatedAt: now, + }, + ]); + }, + + async down(queryInterface) { + await queryInterface.bulkDelete("prep_briefs", null, {}); + await queryInterface.bulkDelete("testimonials", null, {}); + await queryInterface.bulkDelete("resources", null, {}); + await queryInterface.bulkDelete("action_items", null, {}); + await queryInterface.bulkDelete("sessions", null, {}); + await queryInterface.bulkDelete("clients", null, {}); + await queryInterface.bulkDelete("packages", null, {}); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index 6d88f9c..051e269 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -14,10 +14,10 @@ const swaggerJsDoc = require('swagger-jsdoc'); const authRoutes = require('./routes/auth'); const fileRoutes = require('./routes/file'); const searchRoutes = require('./routes/search'); -const sqlRoutes = require('./routes/sql'); const pexelsRoutes = require('./routes/pexels'); const openaiRoutes = require('./routes/openai'); +const coachingRoutes = require('./routes/coaching'); @@ -27,21 +27,6 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); -const accountsRoutes = require('./routes/accounts'); - -const contactsRoutes = require('./routes/contacts'); - -const lead_sourcesRoutes = require('./routes/lead_sources'); - -const leadsRoutes = require('./routes/leads'); - -const pipeline_stagesRoutes = require('./routes/pipeline_stages'); - -const dealsRoutes = require('./routes/deals'); - -const activitiesRoutes = require('./routes/activities'); - - const getBaseUrl = (url) => { if (!url) return ''; return url.endsWith('/api') ? url.slice(0, -4) : url; @@ -52,8 +37,8 @@ const options = { openapi: "3.0.0", info: { version: "1.0.0", - title: "Sales Pipeline CRM", - description: "Sales Pipeline CRM Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.", + title: "Coaching SaaS Workspace", + description: "Coaching SaaS Workspace REST API for clients, session memory, resources, and coach operations.", }, servers: [ { @@ -105,20 +90,7 @@ app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoute app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); -app.use('/api/accounts', passport.authenticate('jwt', {session: false}), accountsRoutes); - -app.use('/api/contacts', passport.authenticate('jwt', {session: false}), contactsRoutes); - -app.use('/api/lead_sources', passport.authenticate('jwt', {session: false}), lead_sourcesRoutes); - -app.use('/api/leads', passport.authenticate('jwt', {session: false}), leadsRoutes); - -app.use('/api/pipeline_stages', passport.authenticate('jwt', {session: false}), pipeline_stagesRoutes); - -app.use('/api/deals', passport.authenticate('jwt', {session: false}), dealsRoutes); - -app.use('/api/activities', passport.authenticate('jwt', {session: false}), activitiesRoutes); - +app.use('/api/coaching', passport.authenticate('jwt', {session: false}), coachingRoutes); app.use( '/api/openai', passport.authenticate('jwt', { session: false }), @@ -134,11 +106,6 @@ app.use( '/api/search', passport.authenticate('jwt', { session: false }), searchRoutes); -app.use( - '/api/sql', - passport.authenticate('jwt', { session: false }), - sqlRoutes); - const publicDir = path.join( __dirname, diff --git a/backend/src/routes/coaching.js b/backend/src/routes/coaching.js new file mode 100644 index 0000000..3ebee1f --- /dev/null +++ b/backend/src/routes/coaching.js @@ -0,0 +1,248 @@ +const express = require("express"); +const db = require("../db/models"); +const wrapAsync = require("../helpers").wrapAsync; +const { LocalAIApi } = require("../ai/LocalAIApi"); + +const router = express.Router(); + +router.get( + "/summary", + wrapAsync(async (req, res) => { + const [clients, sessions, actionItems, resources, prepBriefs] = await Promise.all([ + db.clients.count(), + db.sessions.count(), + db.action_items.count({ where: { status: ["not_started", "in_progress"] } }), + db.resources.count({ where: { is_shared: true } }), + db.prep_briefs.count({ where: { status: "ready" } }), + ]); + + const nextSessions = await db.sessions.findAll({ + limit: 4, + order: [["session_at", "DESC"]], + include: [{ model: db.clients, as: "client" }], + }); + + const activeClients = await db.clients.findAll({ + limit: 4, + order: [["next_session_at", "ASC"]], + include: [{ model: db.packages, as: "package" }], + }); + + res.status(200).send({ + counts: { + clients, + sessions, + actionItems, + resources, + prepBriefs, + }, + nextSessions, + activeClients, + }); + }), +); + +router.get( + "/clients", + wrapAsync(async (req, res) => { + const clients = await db.clients.findAll({ + order: [["next_session_at", "ASC"]], + include: [ + { model: db.packages, as: "package" }, + { model: db.sessions, as: "sessions", limit: 2, order: [["session_at", "DESC"]] }, + { model: db.action_items, as: "action_items", limit: 3, order: [["due_at", "ASC"]] }, + ], + }); + + res.status(200).send(clients); + }), +); + +router.get( + "/clients/:id", + wrapAsync(async (req, res) => { + const client = await db.clients.findByPk(req.params.id, { + include: [ + { model: db.packages, as: "package" }, + { model: db.sessions, as: "sessions", order: [["session_at", "DESC"]] }, + { model: db.action_items, as: "action_items", order: [["due_at", "ASC"]] }, + { model: db.resources, as: "resources" }, + { model: db.prep_briefs, as: "prep_briefs", order: [["next_session_at", "DESC"]] }, + ], + }); + + if (!client) { + res.status(404).send({ error: "client_not_found" }); + return; + } + + res.status(200).send(client); + }), +); + +router.get( + "/session-memory", + wrapAsync(async (req, res) => { + const sessions = await db.sessions.findAll({ + limit: 20, + order: [["session_at", "DESC"]], + include: [{ model: db.clients, as: "client" }], + }); + + res.status(200).send(sessions); + }), +); + +router.post( + "/sessions", + wrapAsync(async (req, res) => { + const data = req.body.data || req.body; + const session = await db.sessions.create({ + clientId: data.clientId, + title: data.title, + session_at: data.session_at || new Date(), + status: data.status || "completed", + transcript_notes: data.transcript_notes, + ai_summary: data.ai_summary, + key_topics: data.key_topics, + goals_discussed: data.goals_discussed, + blockers: data.blockers, + commitments: data.commitments, + homework: data.homework, + emotional_themes: data.emotional_themes, + important_quotes: data.important_quotes, + follow_up_email: data.follow_up_email, + next_session_prep: data.next_session_prep, + private_coach_notes: data.private_coach_notes, + shared_client_notes: data.shared_client_notes, + createdById: req.currentUser.id, + updatedById: req.currentUser.id, + }); + + res.status(200).send(session); + }), +); + +router.post( + "/session-memory/generate", + wrapAsync(async (req, res) => { + const { clientId, sessionId, transcript } = req.body; + + if (!clientId) { + res.status(400).send({ error: "client_id_required" }); + return; + } + + if (!transcript || !String(transcript).trim()) { + res.status(400).send({ error: "transcript_required" }); + return; + } + + const client = await db.clients.findByPk(clientId, { + include: [ + { model: db.sessions, as: "sessions", limit: 5, order: [["session_at", "DESC"]] }, + { model: db.action_items, as: "action_items", limit: 10, order: [["due_at", "ASC"]] }, + ], + }); + + if (!client) { + res.status(404).send({ error: "client_not_found" }); + return; + } + + const response = await LocalAIApi.createResponse( + { + input: [ + { + role: "system", + content: [ + { + type: "input_text", + text: [ + "You are a coaching operations assistant.", + "Extract structured session memory for a professional coaching workspace.", + "Return strict JSON only with these string fields:", + "title, ai_summary, key_topics, goals_discussed, blockers, commitments, homework, emotional_themes, important_quotes, follow_up_email, next_session_prep, private_coach_notes, shared_client_notes.", + ].join(" "), + }, + ], + }, + { + role: "user", + content: [ + { + type: "input_text", + text: JSON.stringify({ + client: { + name: client.name, + company: client.company, + role_title: client.role_title, + goals: client.goals, + notes: client.notes, + }, + recent_sessions: client.sessions || [], + open_action_items: client.action_items || [], + transcript, + }), + }, + ], + }, + ], + }, + { poll_timeout: 180, poll_interval: 3 }, + ); + + if (!response.success) { + res.status(502).send(response); + return; + } + + const memory = LocalAIApi.decodeJsonFromResponse(response); + + if (sessionId) { + await db.sessions.update( + { + title: memory.title, + ai_summary: memory.ai_summary, + key_topics: memory.key_topics, + goals_discussed: memory.goals_discussed, + blockers: memory.blockers, + commitments: memory.commitments, + homework: memory.homework, + emotional_themes: memory.emotional_themes, + important_quotes: memory.important_quotes, + follow_up_email: memory.follow_up_email, + next_session_prep: memory.next_session_prep, + private_coach_notes: memory.private_coach_notes, + shared_client_notes: memory.shared_client_notes, + updatedById: req.currentUser.id, + }, + { where: { id: sessionId } }, + ); + } + + res.status(200).send(memory); + }), +); + +router.get( + "/client-portal/:clientId", + wrapAsync(async (req, res) => { + const client = await db.clients.findByPk(req.params.clientId, { + include: [ + { model: db.sessions, as: "sessions", order: [["session_at", "DESC"]] }, + { model: db.action_items, as: "action_items", order: [["due_at", "ASC"]] }, + { model: db.resources, as: "resources", where: { is_shared: true }, required: false }, + ], + }); + + if (!client) { + res.status(404).send({ error: "client_not_found" }); + return; + } + + res.status(200).send(client); + }), +); + +module.exports = router; diff --git a/frontend/src/config.ts b/frontend/src/config.ts index a9783c8..a653669 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -8,7 +8,7 @@ export const localStorageStyleKey = 'style' export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20' -export const appTitle = 'created by Flatlogic generator!' +export const appTitle = 'Coaching SaaS Workspace' export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}` diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..cc97f6a 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -122,7 +122,7 @@ export default function LayoutAuthenticated({ onAsideLgClose={() => setIsAsideLgActive(false)} /> {children} - Hand-crafted & Made with ❤️ + Coaching SaaS Workspace ) diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 96adc65..956e7a0 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -7,101 +7,32 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiViewDashboardOutline, label: 'Dashboard', }, - + { + href: '/clients', + label: 'Clients', + icon: icon.mdiAccountGroup, + }, + { + href: '/session-memory', + label: 'Session Memory', + icon: icon.mdiTable, + }, + { + href: '/client-portal', + label: 'Client Portal', + icon: icon.mdiAccountCircle, + }, { href: '/users/users-list', - label: 'Users', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiAccountGroup ?? icon.mdiTable, + label: 'Team', + icon: icon.mdiAccountGroup, permissions: 'READ_USERS' }, - { - href: '/roles/roles-list', - label: 'Roles', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, - permissions: 'READ_ROLES' - }, - { - href: '/permissions/permissions-list', - label: 'Permissions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, - permissions: 'READ_PERMISSIONS' - }, - { - href: '/accounts/accounts-list', - label: 'Accounts', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiDomain' in icon ? icon['mdiDomain' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ACCOUNTS' - }, - { - href: '/contacts/contacts-list', - label: 'Contacts', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountBox' in icon ? icon['mdiAccountBox' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CONTACTS' - }, - { - href: '/lead_sources/lead_sources-list', - label: 'Lead sources', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiSourceBranch' in icon ? icon['mdiSourceBranch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_LEAD_SOURCES' - }, - { - href: '/leads/leads-list', - label: 'Leads', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountSearch' in icon ? icon['mdiAccountSearch' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_LEADS' - }, - { - href: '/pipeline_stages/pipeline_stages-list', - label: 'Pipeline stages', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiTransitConnectionVariant' in icon ? icon['mdiTransitConnectionVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_PIPELINE_STAGES' - }, - { - href: '/deals/deals-list', - label: 'Deals', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiHandshake' in icon ? icon['mdiHandshake' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_DEALS' - }, - { - href: '/activities/activities-list', - label: 'Activities', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiCalendarCheck' in icon ? icon['mdiCalendarCheck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ACTIVITIES' - }, { href: '/profile', label: 'Profile', icon: icon.mdiAccountCircle, }, - - - { - href: '/api-docs', - target: '_blank', - label: 'Swagger API', - icon: icon.mdiFileCode, - permissions: 'READ_API_DOCS' - }, ] export default menuAside diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 4f669bc..8632a59 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -149,8 +149,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { setStepsEnabled(false); }; - const title = 'Sales Pipeline CRM' - const description = "Internal sales pipeline CRM to track leads, deals, contacts, and follow-ups with clear stage visibility." + const title = 'Coaching SaaS Workspace' + const description = "A coaching workspace for client context, session memory, action items, resources, and client portal delivery." const url = "https://flatlogic.com/" const image = "https://project-screens.s3.amazonaws.com/screenshots/40234/app-hero-20260609-100604.png" const imageWidth = '1920' diff --git a/frontend/src/pages/client-portal.tsx b/frontend/src/pages/client-portal.tsx new file mode 100644 index 0000000..b105812 --- /dev/null +++ b/frontend/src/pages/client-portal.tsx @@ -0,0 +1,128 @@ +import * as icon from '@mdi/js'; +import Head from 'next/head' +import React from 'react' +import axios from 'axios'; +import type { ReactElement } from 'react' +import LayoutAuthenticated from '../layouts/Authenticated' +import SectionMain from '../components/SectionMain' +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' +import CardBox from '../components/CardBox' +import { getPageTitle } from '../config' + +type PortalClient = { + id: string; + name: string; + goals?: string; + sessions?: Array<{ id: string; title: string; shared_client_notes?: string }>; + action_items?: Array<{ id: string; title: string; status: string }>; + resources?: Array<{ id: string; title: string; description?: string; url?: string }>; +}; + +const ClientPortal = () => { + const [clients, setClients] = React.useState>([]); + const [clientId, setClientId] = React.useState(''); + const [portalClient, setPortalClient] = React.useState(null); + + React.useEffect(() => { + async function loadClients() { + const response = await axios.get('/coaching/clients'); + setClients(response.data); + if (response.data.length > 0) { + setClientId(response.data[0].id); + } + } + + loadClients(); + }, []); + + React.useEffect(() => { + async function loadPortal() { + if (!clientId) { + return; + } + + const response = await axios.get(`/coaching/client-portal/${clientId}`); + setPortalClient(response.data); + } + + loadPortal(); + }, [clientId]); + + return ( + <> + + {getPageTitle('Client Portal')} + + + + {''} + + + + + + + + {portalClient && ( +
+ +

Your coaching workspace

+

{portalClient.name}

+

{portalClient.goals}

+ +

Shared session notes

+
+ {(portalClient.sessions || []).map((session) => ( +
+

{session.title}

+

{session.shared_client_notes}

+
+ ))} +
+
+ +
+ +

Commitments

+
+ {(portalClient.action_items || []).map((item) => ( +
+

{item.title}

+

{item.status.replace('_', ' ')}

+
+ ))} +
+
+ + +

Resources

+
+ {(portalClient.resources || []).map((resource) => ( + +

{resource.title}

+

{resource.description}

+
+ ))} +
+
+
+
+ )} +
+ + ) +} + +ClientPortal.getLayout = function getLayout(page: ReactElement) { + return {page} +} + +export default ClientPortal diff --git a/frontend/src/pages/clients.tsx b/frontend/src/pages/clients.tsx new file mode 100644 index 0000000..251dfec --- /dev/null +++ b/frontend/src/pages/clients.tsx @@ -0,0 +1,147 @@ +import * as icon from '@mdi/js'; +import Head from 'next/head' +import React from 'react' +import axios from 'axios'; +import type { ReactElement } from 'react' +import LayoutAuthenticated from '../layouts/Authenticated' +import SectionMain from '../components/SectionMain' +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' +import CardBox from '../components/CardBox' +import { getPageTitle } from '../config' + +type ActionItem = { + id: string; + title: string; + status: string; +}; + +type Session = { + id: string; + title: string; + ai_summary?: string; +}; + +type Client = { + id: string; + name: string; + email: string; + status: string; + goals?: string; + notes?: string; + company?: string; + role_title?: string; + tags?: string; + sessions?: Session[]; + action_items?: ActionItem[]; + package?: { + title?: string; + duration?: string; + }; +}; + +const Clients = () => { + const [clients, setClients] = React.useState([]); + const [selectedClientId, setSelectedClientId] = React.useState(''); + + React.useEffect(() => { + async function loadClients() { + const response = await axios.get('/coaching/clients'); + setClients(response.data); + if (response.data.length > 0) { + setSelectedClientId(response.data[0].id); + } + } + + loadClients(); + }, []); + + const selectedClient = clients.find((client) => client.id === selectedClientId) || clients[0]; + + return ( + <> + + {getPageTitle('Clients')} + + + + {''} + + +
+ +
+ {clients.map((client) => ( + + ))} +
+
+ + {selectedClient && ( + +
+

{selectedClient.package?.title || 'Coaching client'}

+

{selectedClient.name}

+

{selectedClient.email}

+
+ +
+
+

Goals

+

{selectedClient.goals}

+
+
+

Coach Notes

+

{selectedClient.notes}

+
+
+ +
+
+

Recent Sessions

+
+ {(selectedClient.sessions || []).map((session) => ( +
+

{session.title}

+

{session.ai_summary}

+
+ ))} +
+
+
+

Action Items

+
+ {(selectedClient.action_items || []).map((item) => ( +
+

{item.title}

+

{item.status.replace('_', ' ')}

+
+ ))} +
+
+
+
+ )} +
+
+ + ) +} + +Clients.getLayout = function getLayout(page: ReactElement) { + return {page} +} + +export default Clients diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 96697a3..41a504b 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -3,429 +3,141 @@ import Head from 'next/head' import React from 'react' import axios from 'axios'; import type { ReactElement } from 'react' +import Link from 'next/link'; import LayoutAuthenticated from '../layouts/Authenticated' import SectionMain from '../components/SectionMain' import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' -import BaseIcon from "../components/BaseIcon"; +import CardBox from '../components/CardBox' +import BaseIcon from '../components/BaseIcon' import { getPageTitle } from '../config' -import Link from "next/link"; -import { hasPermission } from "../helpers/userPermissions"; -import { fetchWidgets } from '../stores/roles/rolesSlice'; -import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; -import { SmartWidget } from '../components/SmartWidget/SmartWidget'; +type Client = { + id: string; + name: string; + company?: string; + role_title?: string; + next_session_at?: string; + package?: { + title?: string; + }; +}; + +type Session = { + id: string; + title?: string; + ai_summary?: string; + session_at?: string; + client?: Client; +}; + +type Summary = { + counts: { + clients: number; + sessions: number; + actionItems: number; + resources: number; + prepBriefs: number; + }; + activeClients: Client[]; + nextSessions: Session[]; +}; + +const emptySummary: Summary = { + counts: { + clients: 0, + sessions: 0, + actionItems: 0, + resources: 0, + prepBriefs: 0, + }, + activeClients: [], + nextSessions: [], +}; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; const Dashboard = () => { - const dispatch = useAppDispatch(); - const iconsColor = useAppSelector((state) => state.style.iconsColor); - const corners = useAppSelector((state) => state.style.corners); - const cardsStyle = useAppSelector((state) => state.style.cardsStyle); + const [summary, setSummary] = React.useState(emptySummary); + const [loading, setLoading] = React.useState(true); - const loadingMessage = 'Loading...'; - - - const [users, setUsers] = React.useState(loadingMessage); - const [roles, setRoles] = React.useState(loadingMessage); - const [permissions, setPermissions] = React.useState(loadingMessage); - const [accounts, setAccounts] = React.useState(loadingMessage); - const [contacts, setContacts] = React.useState(loadingMessage); - const [lead_sources, setLead_sources] = React.useState(loadingMessage); - const [leads, setLeads] = React.useState(loadingMessage); - const [pipeline_stages, setPipeline_stages] = React.useState(loadingMessage); - const [deals, setDeals] = React.useState(loadingMessage); - const [activities, setActivities] = React.useState(loadingMessage); - - - const [widgetsRole, setWidgetsRole] = React.useState({ - role: { value: '', label: '' }, - }); - const { currentUser } = useAppSelector((state) => state.auth); - const { isFetchingQuery } = useAppSelector((state) => state.openAi); - - const { rolesWidgets, loading } = useAppSelector((state) => state.roles); - - - async function loadData() { - const entities = ['users','roles','permissions','accounts','contacts','lead_sources','leads','pipeline_stages','deals','activities',]; - const fns = [setUsers,setRoles,setPermissions,setAccounts,setContacts,setLead_sources,setLeads,setPipeline_stages,setDeals,setActivities,]; - - const requests = entities.map((entity, index) => { - - if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { - return axios.get(`/${entity.toLowerCase()}/count`); - } else { - fns[index](null); - return Promise.resolve({data: {count: null}}); - } - - }); - - Promise.allSettled(requests).then((results) => { - results.forEach((result, i) => { - if (result.status === 'fulfilled') { - fns[i](result.value.data.count); - } else { - fns[i](result.reason.message); - } - }); - }); + React.useEffect(() => { + async function loadSummary() { + const response = await axios.get('/coaching/summary'); + setSummary(response.data); + setLoading(false); } - - async function getWidgets(roleId) { - await dispatch(fetchWidgets(roleId)); - } - React.useEffect(() => { - if (!currentUser) return; - loadData().then(); - setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }); - }, [currentUser]); - React.useEffect(() => { - if (!currentUser || !widgetsRole?.role?.value) return; - getWidgets(widgetsRole?.role?.value || '').then(); - }, [widgetsRole?.role?.value]); - + loadSummary(); + }, []); + + const stats = [ + ['Clients', summary.counts.clients, icon.mdiAccountGroup, '/clients'], + ['Sessions', summary.counts.sessions, icon.mdiTable, '/session-memory'], + ['Open actions', summary.counts.actionItems, icon.mdiTable, '/clients'], + ['Shared resources', summary.counts.resources, icon.mdiTable, '/client-portal'], + ['Prep briefs', summary.counts.prepBriefs, icon.mdiTable, '/session-memory'], + ]; + return ( <> - - {getPageTitle('Overview')} - + {getPageTitle('Dashboard')} - + {''} - - {hasPermission(currentUser, 'CREATE_ROLES') && } - {!!rolesWidgets.length && - hasPermission(currentUser, 'CREATE_ROLES') && ( -

- {`${widgetsRole?.role?.label || 'Users'}'s widgets`} -

- )} -
- {(isFetchingQuery || loading) && ( -
- {' '} - Loading widgets... +
+ {stats.map(([label, value, iconPath, href]) => ( + + +
+
+

{label}

+

{loading ? '...' : value}

+
+
- )} - - { rolesWidgets && - rolesWidgets.map((widget) => ( - - ))} +
+ + ))}
- {!!rolesWidgets.length &&
} - -
- - - {hasPermission(currentUser, 'READ_USERS') && -
-
-
-
- Users -
-
- {users} -
-
-
- -
+
+ +
+

Active Clients

+ View all +
+
+ {summary.activeClients.map((client) => ( + +
+
+

{client.name}

+

{client.role_title} · {client.company}

+

{client.package?.title || 'Coaching package'}

+
+ + ))} +
+
+ + +
+

Recent Session Memory

+ Open memory +
+
+ {summary.nextSessions.map((session) => ( +
+

{session.title}

+

{session.client?.name}

+

{session.ai_summary}

- } - - {hasPermission(currentUser, 'READ_ROLES') && -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PERMISSIONS') && -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ACCOUNTS') && -
-
-
-
- Accounts -
-
- {accounts} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_CONTACTS') && -
-
-
-
- Contacts -
-
- {contacts} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_LEAD_SOURCES') && -
-
-
-
- Lead sources -
-
- {lead_sources} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_LEADS') && -
-
-
-
- Leads -
-
- {leads} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PIPELINE_STAGES') && -
-
-
-
- Pipeline stages -
-
- {pipeline_stages} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_DEALS') && -
-
-
-
- Deals -
-
- {deals} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ACTIVITIES') && -
-
-
-
- Activities -
-
- {activities} -
-
-
- -
-
-
- } - - + ))} +
+
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index a8b6e65..c52ffdf 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,99 @@ - -import React, { useEffect, useState } from 'react'; +import React from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; +import BaseButton from '../components/BaseButton'; import { getPageTitle } from '../config'; -import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; - export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Sales Pipeline CRM' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( - - ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- - -
) - } - }; - return ( -
+ <> - {getPageTitle('Starter Page')} + {getPageTitle('Coaching Workspace')} - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

+
+
+
+

+ Coaching SaaS Workspace +

+

+ Turn every coaching session into clear memory, next steps, and client momentum. +

+

+ A vertical SaaS workspace for coaches who need client context, AI-assisted session notes, + prep briefs, shared resources, and a client portal without stitching together generic tools. +

+
+ +
- - - +
- - -
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+
+
+
+
+

Next session

+

Maya Chen

+
+ + Ready + +
+
+
+

Prep brief

+

+ Review delegation boundaries, decision-rights matrix, and where Maya still feels exposed. +

+
+
+ {['Open commitments', 'Suggested questions', 'Shared resources', 'Follow-up email'].map((item) => ( +
+

{item}

+

+ Generated from previous sessions and coach notes. +

+
+ ))} +
+
+
+
+ -
+
+
+ {[ + ['Session Memory', 'Extract summaries, blockers, homework, quotes, and follow-up notes from session transcripts.'], + ['Client Workspace', 'Keep goals, private notes, resources, and action items organized around every client.'], + ['Client Portal', 'Share the right notes, resources, and commitments without exposing coach-only context.'], + ].map(([title, copy]) => ( +
+

{title}

+

{copy}

+
+ ))} +
+
+ +
+
+

© 2026 Coaching SaaS Workspace. All rights reserved.

+
+ Privacy Policy + Terms of Use +
+
+
+ + ); } Starter.getLayout = function getLayout(page: ReactElement) { return {page}; }; - diff --git a/frontend/src/pages/session-memory.tsx b/frontend/src/pages/session-memory.tsx new file mode 100644 index 0000000..8bcc2b8 --- /dev/null +++ b/frontend/src/pages/session-memory.tsx @@ -0,0 +1,149 @@ +import * as icon from '@mdi/js'; +import Head from 'next/head' +import React from 'react' +import axios from 'axios'; +import type { ReactElement } from 'react' +import LayoutAuthenticated from '../layouts/Authenticated' +import SectionMain from '../components/SectionMain' +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' +import CardBox from '../components/CardBox' +import BaseButton from '../components/BaseButton' +import { getPageTitle } from '../config' + +type Client = { + id: string; + name: string; +}; + +type Session = { + id: string; + title?: string; + ai_summary?: string; + key_topics?: string; + homework?: string; + follow_up_email?: string; + client?: Client; +}; + +const SessionMemory = () => { + const [clients, setClients] = React.useState([]); + const [sessions, setSessions] = React.useState([]); + const [clientId, setClientId] = React.useState(''); + const [transcript, setTranscript] = React.useState(''); + const [generatedMemory, setGeneratedMemory] = React.useState(null); + const [isGenerating, setIsGenerating] = React.useState(false); + + async function loadData() { + const [clientsResponse, sessionsResponse] = await Promise.all([ + axios.get('/coaching/clients'), + axios.get('/coaching/session-memory'), + ]); + setClients(clientsResponse.data); + setSessions(sessionsResponse.data); + if (!clientId && clientsResponse.data.length > 0) { + setClientId(clientsResponse.data[0].id); + } + } + + React.useEffect(() => { + loadData(); + }, []); + + async function generateMemory() { + setIsGenerating(true); + const response = await axios.post('/coaching/session-memory/generate', { + clientId, + transcript, + }); + setGeneratedMemory(response.data); + setIsGenerating(false); + } + + return ( + <> + + {getPageTitle('Session Memory')} + + + + {''} + + +
+ +

Extract a Session

+ + + +