Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37200a1c3b | |||
| bc8c9eabeb | |||
| c415660640 | |||
| ad75662919 | |||
| aa7c5a778b | |||
| 8d43ed4515 | |||
| 3d1d3d9f04 | |||
| b19dc703cc | |||
| 9b668619c5 | |||
| e7c83dc04f | |||
| 693fe01fa4 | |||
| c616713435 | |||
| 859c948d01 |
@@ -3,11 +3,11 @@ import { CommandMenuAIChatThreadsPage } from '@/command-menu/pages/AIChatThreads
|
||||
import { CommandMenuAskAIPage } from '@/command-menu/pages/ask-ai/components/CommandMenuAskAIPage';
|
||||
import { CommandMenuCalendarEventPage } from '@/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage';
|
||||
import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage';
|
||||
import { CommandMenuPageLayoutChartSettings } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutChartSettings';
|
||||
import { CommandMenuPageLayoutGraphFilter } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphFilter';
|
||||
import { CommandMenuPageLayoutGraphTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect';
|
||||
import { CommandMenuPageLayoutIframeSettings } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeSettings';
|
||||
import { CommandMenuPageLayoutWidgetTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect';
|
||||
import { CommandMenuPageLayoutTabSettings } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings';
|
||||
import { CommandMenuPageLayoutWidgetTypeSelect } from '@/command-menu/pages/page-layout/components/CommandMenuPageLayoutWidgetTypeSelect';
|
||||
import { CommandMenuMergeRecordPage } from '@/command-menu/pages/record-page/components/CommandMenuMergeRecordPage';
|
||||
import { CommandMenuRecordPage } from '@/command-menu/pages/record-page/components/CommandMenuRecordPage';
|
||||
import { CommandMenuEditRichTextPage } from '@/command-menu/pages/rich-text-page/components/CommandMenuEditRichTextPage';
|
||||
@@ -48,7 +48,7 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
|
||||
],
|
||||
[
|
||||
CommandMenuPages.PageLayoutGraphTypeSelect,
|
||||
<CommandMenuPageLayoutGraphTypeSelect />,
|
||||
<CommandMenuPageLayoutChartSettings />,
|
||||
],
|
||||
[
|
||||
CommandMenuPages.PageLayoutGraphFilter,
|
||||
|
||||
+2
-11
@@ -14,7 +14,7 @@ const StyledContainer = styled.div`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const CommandMenuPageLayoutGraphTypeSelect = () => {
|
||||
export const CommandMenuPageLayoutChartSettings = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
|
||||
const draftPageLayout = useRecoilComponentValue(
|
||||
@@ -27,21 +27,12 @@ export const CommandMenuPageLayoutGraphTypeSelect = () => {
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
if (!isDefined(pageLayoutEditingWidgetId)) {
|
||||
throw new Error('Widget ID must be present while editing the widget');
|
||||
}
|
||||
|
||||
const widgetInEditMode = draftPageLayout.tabs
|
||||
.flatMap((tab) => tab.widgets)
|
||||
.find((widget) => widget.id === pageLayoutEditingWidgetId);
|
||||
|
||||
if (!isDefined(widgetInEditMode)) {
|
||||
throw new Error(
|
||||
`Widget with ID ${pageLayoutEditingWidgetId} not found in page layout`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!isDefined(widgetInEditMode) ||
|
||||
!isDefined(widgetInEditMode.configuration) ||
|
||||
!('graphType' in widgetInEditMode.configuration)
|
||||
) {
|
||||
+19
-19
@@ -1,10 +1,11 @@
|
||||
import { ChartFiltersSettings } from '@/command-menu/pages/page-layout/components/ChartFiltersSettings';
|
||||
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
||||
import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const CommandMenuPageLayoutGraphFilter = () => {
|
||||
@@ -24,28 +25,27 @@ export const CommandMenuPageLayoutGraphFilter = () => {
|
||||
.flatMap((tab) => tab.widgets)
|
||||
.find((widget) => widget.id === pageLayoutEditingWidgetId);
|
||||
|
||||
if (!isDefined(widgetInEditMode)) {
|
||||
throw new Error(
|
||||
`Widget with ID ${pageLayoutEditingWidgetId} not found in page layout`,
|
||||
);
|
||||
}
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
if (!isDefined(widgetInEditMode?.objectMetadataId)) {
|
||||
throw new Error('No data source in chart');
|
||||
}
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
objectId: widgetInEditMode.objectMetadataId,
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayoutEditingWidgetId)) {
|
||||
throw new Error('Widget ID must be present while editing the widget');
|
||||
}
|
||||
|
||||
if (!isChartWidget(widgetInEditMode)) {
|
||||
if (
|
||||
!isDefined(widgetInEditMode) ||
|
||||
!isDefined(widgetInEditMode.objectMetadataId) ||
|
||||
!isChartWidget(widgetInEditMode)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id === widgetInEditMode?.objectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
throw new Error(
|
||||
`Object metadata item not found for id ${widgetInEditMode?.objectMetadataId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChartFiltersSettings
|
||||
|
||||
+6
-2
@@ -49,8 +49,12 @@ export const AdvancedFilterDropdownFilterInput = ({
|
||||
<AdvancedFilterDropdownTextInput recordFilter={recordFilter} />
|
||||
))}
|
||||
{filterType === 'RATING' && <ObjectFilterDropdownRatingInput />}
|
||||
{filterType === 'DATE_TIME' && <ObjectFilterDropdownDateTimeInput />}
|
||||
{filterType === 'DATE' && <ObjectFilterDropdownDateInput />}
|
||||
{filterType === 'DATE_TIME' && (
|
||||
<ObjectFilterDropdownDateTimeInput instanceId={filterDropdownId} />
|
||||
)}
|
||||
{filterType === 'DATE' && (
|
||||
<ObjectFilterDropdownDateInput instanceId={filterDropdownId} />
|
||||
)}
|
||||
{filterType === 'RELATION' && (
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.ExtraLarge}>
|
||||
<ObjectFilterDropdownSearchInput />
|
||||
|
||||
+8
-1
@@ -24,7 +24,13 @@ import {
|
||||
import { dateLocaleState } from '~/localization/states/dateLocaleState';
|
||||
import { formatDateString } from '~/utils/string/formatDateString';
|
||||
|
||||
export const ObjectFilterDropdownDateInput = () => {
|
||||
type ObjectFilterDropdownDateInputProps = {
|
||||
instanceId: string;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownDateInput = ({
|
||||
instanceId,
|
||||
}: ObjectFilterDropdownDateInputProps) => {
|
||||
const { dateFormat, timeZone } = useContext(UserContext);
|
||||
const dateLocale = useRecoilValue(dateLocaleState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
@@ -109,6 +115,7 @@ export const ObjectFilterDropdownDateInput = () => {
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
instanceId={instanceId}
|
||||
relativeDate={relativeDate}
|
||||
isRelative={isRelativeOperand}
|
||||
date={plainDateValue ?? null}
|
||||
|
||||
+8
-1
@@ -24,7 +24,13 @@ import {
|
||||
import { dateLocaleState } from '~/localization/states/dateLocaleState';
|
||||
import { formatDateTimeString } from '~/utils/string/formatDateTimeString';
|
||||
|
||||
export const ObjectFilterDropdownDateTimeInput = () => {
|
||||
type ObjectFilterDropdownDateTimeInputProps = {
|
||||
instanceId: string;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownDateTimeInput = ({
|
||||
instanceId,
|
||||
}: ObjectFilterDropdownDateTimeInputProps) => {
|
||||
const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
|
||||
const dateLocale = useRecoilValue(dateLocaleState);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
@@ -112,6 +118,7 @@ export const ObjectFilterDropdownDateTimeInput = () => {
|
||||
|
||||
return (
|
||||
<DateTimePicker
|
||||
instanceId={instanceId}
|
||||
relativeDate={relativeDate}
|
||||
isRelative={isRelativeOperand}
|
||||
date={internalDate}
|
||||
|
||||
+2
-2
@@ -82,7 +82,7 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownDateInput />
|
||||
<ObjectFilterDropdownDateInput instanceId={filterDropdownId} />
|
||||
</>
|
||||
);
|
||||
} else if (filterType === 'DATE_TIME') {
|
||||
@@ -90,7 +90,7 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
<>
|
||||
<ObjectFilterDropdownInnerSelectOperandDropdown />
|
||||
<DropdownMenuSeparator />
|
||||
<ObjectFilterDropdownDateTimeInput />
|
||||
<ObjectFilterDropdownDateTimeInput instanceId={filterDropdownId} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
|
||||
+3
-9
@@ -13,13 +13,9 @@ import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUs
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { stringifyRelativeDateFilter } from '@/views/view-filter-value/utils/stringifyRelativeDateFilter';
|
||||
import { DEFAULT_RELATIVE_DATE_FILTER_VALUE } from 'twenty-shared/constants';
|
||||
|
||||
import {
|
||||
isDefined,
|
||||
type RelativeDateFilter,
|
||||
type RelativeDateFilterDirection,
|
||||
type RelativeDateFilterUnit,
|
||||
} from 'twenty-shared/utils';
|
||||
import { isDefined, type RelativeDateFilter } from 'twenty-shared/utils';
|
||||
|
||||
export const useApplyObjectFilterDropdownOperand = () => {
|
||||
const objectFilterDropdownCurrentRecordFilter = useRecoilComponentValue(
|
||||
@@ -108,9 +104,7 @@ export const useApplyObjectFilterDropdownOperand = () => {
|
||||
recordFilterToUpsert.displayValue = displayValue;
|
||||
} else if (newOperand === RecordFilterOperand.IS_RELATIVE) {
|
||||
const defaultRelativeDate: RelativeDateFilter = {
|
||||
direction: 'THIS' as RelativeDateFilterDirection,
|
||||
amount: 1,
|
||||
unit: 'DAY' as RelativeDateFilterUnit,
|
||||
...DEFAULT_RELATIVE_DATE_FILTER_VALUE,
|
||||
timezone: userTimezone,
|
||||
};
|
||||
|
||||
|
||||
+1
@@ -121,6 +121,7 @@ export const RecordCalendarTopBar = () => {
|
||||
dropdownComponents={
|
||||
<DropdownContent widthInPixels={280}>
|
||||
<DateTimePicker
|
||||
instanceId={recordCalendarId}
|
||||
date={recordCalendarSelectedDate}
|
||||
onChange={handleDateChange}
|
||||
onClose={handleDateChange}
|
||||
|
||||
+1
@@ -337,6 +337,7 @@ export const FormDateFieldInput = ({
|
||||
<StyledDateInputAbsoluteContainer>
|
||||
<OverlayContainer>
|
||||
<DatePicker
|
||||
instanceId={instanceId}
|
||||
date={pickerDate}
|
||||
onChange={handlePickerChange}
|
||||
onClose={handlePickerMouseSelect}
|
||||
|
||||
+1
@@ -336,6 +336,7 @@ export const FormDateTimeFieldInput = ({
|
||||
<StyledDateInputAbsoluteContainer>
|
||||
<OverlayContainer>
|
||||
<DateTimePicker
|
||||
instanceId={instanceId}
|
||||
date={pickerDate ?? new Date()}
|
||||
onChange={handlePickerChange}
|
||||
onClose={handlePickerMouseSelect}
|
||||
|
||||
+4
@@ -1,6 +1,7 @@
|
||||
import { RelativeDatePickerHeader } from '@/ui/input/components/internal/date/components/RelativeDatePickerHeader';
|
||||
|
||||
import { isNonEmptyString, isString } from '@sniptt/guards';
|
||||
import { useId } from 'react';
|
||||
import { DEFAULT_RELATIVE_DATE_FILTER_VALUE } from 'twenty-shared/constants';
|
||||
|
||||
import {
|
||||
@@ -21,6 +22,8 @@ export const FormRelativeDatePicker = ({
|
||||
onChange,
|
||||
readonly,
|
||||
}: FormRelativeDatePickerProps) => {
|
||||
const instanceId = useId();
|
||||
|
||||
const value =
|
||||
isString(defaultValue) && isNonEmptyString(defaultValue)
|
||||
? safeParseRelativeDateFilterJSONStringified(defaultValue)
|
||||
@@ -32,6 +35,7 @@ export const FormRelativeDatePicker = ({
|
||||
|
||||
return (
|
||||
<RelativeDatePickerHeader
|
||||
instanceId={instanceId}
|
||||
onChange={handleValueChange}
|
||||
direction={value?.direction ?? 'THIS'}
|
||||
unit={value?.unit ?? 'DAY'}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
@@ -24,9 +25,13 @@ export const useDeletePageLayoutWidget = (pageLayoutIdFromProps?: string) => {
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const deletePageLayoutWidget = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(widgetId: string) => {
|
||||
closeCommandMenu();
|
||||
|
||||
const pageLayoutDraft = snapshot
|
||||
.getLoadable(pageLayoutDraftState)
|
||||
.getValue();
|
||||
@@ -53,7 +58,7 @@ export const useDeletePageLayoutWidget = (pageLayoutIdFromProps?: string) => {
|
||||
}));
|
||||
}
|
||||
},
|
||||
[pageLayoutCurrentLayoutsState, pageLayoutDraftState],
|
||||
[closeCommandMenu, pageLayoutCurrentLayoutsState, pageLayoutDraftState],
|
||||
);
|
||||
|
||||
return { deletePageLayoutWidget };
|
||||
|
||||
@@ -109,6 +109,7 @@ export const DateInput = ({
|
||||
return (
|
||||
<div ref={wrapperRef}>
|
||||
<DatePicker
|
||||
instanceId={instanceId}
|
||||
date={internalValue ?? null}
|
||||
onChange={handleChange}
|
||||
onClose={handleClose}
|
||||
|
||||
@@ -109,6 +109,7 @@ export const DateTimeInput = ({
|
||||
return (
|
||||
<div ref={wrapperRef}>
|
||||
<DateTimePicker
|
||||
instanceId={instanceId}
|
||||
date={internalValue ?? null}
|
||||
onChange={handleChange}
|
||||
onClose={handleClose}
|
||||
|
||||
+3
@@ -302,6 +302,7 @@ const StyledDatePickerFallback = styled.div`
|
||||
`;
|
||||
|
||||
type DatePickerProps = {
|
||||
instanceId: string;
|
||||
isRelative?: boolean;
|
||||
hideHeaderInput?: boolean;
|
||||
date: Nullable<string>;
|
||||
@@ -333,6 +334,7 @@ const ReactDatePicker = lazy<ComponentType<DatePickerPropsType>>(() =>
|
||||
);
|
||||
|
||||
export const DatePicker = ({
|
||||
instanceId,
|
||||
date,
|
||||
onChange,
|
||||
onClose,
|
||||
@@ -476,6 +478,7 @@ export const DatePicker = ({
|
||||
}) =>
|
||||
isRelative ? (
|
||||
<RelativeDatePickerHeader
|
||||
instanceId={instanceId}
|
||||
direction={relativeDate?.direction ?? 'PAST'}
|
||||
amount={relativeDate?.amount}
|
||||
unit={relativeDate?.unit ?? 'DAY'}
|
||||
|
||||
+3
@@ -300,6 +300,7 @@ const StyledDatePickerFallback = styled.div`
|
||||
`;
|
||||
|
||||
type DateTimePickerProps = {
|
||||
instanceId: string;
|
||||
isRelative?: boolean;
|
||||
hideHeaderInput?: boolean;
|
||||
date: Date | null;
|
||||
@@ -331,6 +332,7 @@ const ReactDatePicker = lazy<ComponentType<DatePickerPropsType>>(() =>
|
||||
);
|
||||
|
||||
export const DateTimePicker = ({
|
||||
instanceId,
|
||||
date,
|
||||
onChange,
|
||||
onClose,
|
||||
@@ -484,6 +486,7 @@ export const DateTimePicker = ({
|
||||
}) =>
|
||||
isRelative ? (
|
||||
<RelativeDatePickerHeader
|
||||
instanceId={instanceId}
|
||||
direction={relativeDate?.direction ?? 'PAST'}
|
||||
amount={relativeDate?.amount}
|
||||
unit={relativeDate?.unit ?? 'DAY'}
|
||||
|
||||
+5
-3
@@ -21,6 +21,7 @@ const StyledContainer = styled.div<{ noPadding: boolean }>`
|
||||
`;
|
||||
|
||||
type RelativeDatePickerHeaderProps = {
|
||||
instanceId: string;
|
||||
direction: RelativeDateFilterDirection;
|
||||
amount?: Nullable<number>;
|
||||
unit: RelativeDateFilterUnit;
|
||||
@@ -31,6 +32,7 @@ type RelativeDatePickerHeaderProps = {
|
||||
};
|
||||
|
||||
export const RelativeDatePickerHeader = ({
|
||||
instanceId,
|
||||
direction,
|
||||
unit,
|
||||
amount,
|
||||
@@ -53,7 +55,7 @@ export const RelativeDatePickerHeader = ({
|
||||
return (
|
||||
<StyledContainer noPadding={isFormField ?? false}>
|
||||
<Select
|
||||
dropdownId="direction-select"
|
||||
dropdownId={`direction-select-${instanceId}`}
|
||||
value={direction}
|
||||
onChange={(newDirection) => {
|
||||
if (amount === undefined && newDirection !== 'THIS') {
|
||||
@@ -71,7 +73,7 @@ export const RelativeDatePickerHeader = ({
|
||||
disabled={readonly}
|
||||
/>
|
||||
<SettingsTextInput
|
||||
instanceId="relative-date-picker-amount"
|
||||
instanceId={`relative-date-picker-amount-${instanceId}`}
|
||||
width={50}
|
||||
value={textInputValue}
|
||||
onChange={(text) => {
|
||||
@@ -92,7 +94,7 @@ export const RelativeDatePickerHeader = ({
|
||||
disabled={direction === 'THIS' || readonly}
|
||||
/>
|
||||
<Select
|
||||
dropdownId="unit-select"
|
||||
dropdownId={`unit-select-${instanceId}`}
|
||||
value={unit}
|
||||
onChange={(newUnit) => {
|
||||
if (direction !== 'THIS' && amount === undefined) {
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ const meta: Meta<typeof DateTimePicker> = {
|
||||
const [, updateArgs] = useArgs();
|
||||
return (
|
||||
<DateTimePicker
|
||||
instanceId="story-date-time-picker"
|
||||
date={isDefined(date) ? new Date(date) : new Date()}
|
||||
onChange={(newDate) => updateArgs({ date: newDate })}
|
||||
/>
|
||||
|
||||
+4
-15
@@ -45,23 +45,12 @@ const StyledTabList = styled(TabList)`
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledContentContainer = styled.div<{
|
||||
isInRightDrawer: boolean;
|
||||
}>`
|
||||
background: ${({ theme, isInRightDrawer }) =>
|
||||
isInRightDrawer ? theme.background.secondary : theme.background.primary};
|
||||
border: ${({ theme, isInRightDrawer }) =>
|
||||
isInRightDrawer ? `1px solid ${theme.border.color.light}` : 'none'};
|
||||
border-radius: ${({ theme, isInRightDrawer }) =>
|
||||
isInRightDrawer ? theme.border.radius.md : '0'};
|
||||
const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
|
||||
flex: 1;
|
||||
margin: ${({ theme, isInRightDrawer }) =>
|
||||
isInRightDrawer ? theme.spacing(3) : '0'};
|
||||
overflow-y: auto;
|
||||
|
||||
.scroll-wrapper-y-enabled {
|
||||
height: auto;
|
||||
}
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
padding-bottom: ${({ theme, isInRightDrawer }) =>
|
||||
isInRightDrawer ? theme.spacing(16) : 0};
|
||||
`;
|
||||
|
||||
type ShowPageSubContainerProps = {
|
||||
|
||||
+34
-1
@@ -11,6 +11,7 @@ import { UpgradeCommandRunner } from 'src/database/commands/command-runners/upgr
|
||||
import { type ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
|
||||
@@ -72,9 +73,37 @@ const buildUpgradeCommandModule = async ({
|
||||
appVersion,
|
||||
commandRunner,
|
||||
}: BuildUpgradeCommandModuleArgs) => {
|
||||
const mockDataSourceService = {
|
||||
getLastDataSourceMetadataFromWorkspaceId: jest.fn(),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
commandRunner,
|
||||
{
|
||||
provide: commandRunner,
|
||||
useFactory: (
|
||||
workspaceRepository: Repository<WorkspaceEntity>,
|
||||
twentyConfigService: TwentyConfigService,
|
||||
twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
dataSourceService: DataSourceService,
|
||||
syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||
) => {
|
||||
return new commandRunner(
|
||||
workspaceRepository,
|
||||
twentyConfigService,
|
||||
twentyORMGlobalManager,
|
||||
dataSourceService,
|
||||
syncWorkspaceMetadataCommand,
|
||||
);
|
||||
},
|
||||
inject: [
|
||||
getRepositoryToken(WorkspaceEntity),
|
||||
TwentyConfigService,
|
||||
TwentyORMGlobalManager,
|
||||
DataSourceService,
|
||||
SyncWorkspaceMetadataCommand,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(WorkspaceEntity),
|
||||
useValue: {
|
||||
@@ -110,6 +139,10 @@ const buildUpgradeCommandModule = async ({
|
||||
getDataSourceForWorkspace: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DataSourceService,
|
||||
useValue: mockDataSourceService,
|
||||
},
|
||||
{
|
||||
provide: SyncWorkspaceMetadataCommand,
|
||||
useValue: {
|
||||
|
||||
+15
-166
@@ -1,180 +1,29 @@
|
||||
import chalk from 'chalk';
|
||||
import { Option } from 'nest-commander';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { In, MoreThanOrEqual, type Repository } from 'typeorm';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import {
|
||||
WorkspacesMigrationCommandRunner,
|
||||
type WorkspacesMigrationCommandOptions,
|
||||
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
export type ActiveOrSuspendedWorkspacesMigrationCommandOptions = {
|
||||
workspaceIds: string[];
|
||||
startFromWorkspaceId?: string;
|
||||
workspaceCountLimit?: number;
|
||||
dryRun?: boolean;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export type RunOnWorkspaceArgs = {
|
||||
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
|
||||
workspaceId: string;
|
||||
dataSource: WorkspaceDataSource;
|
||||
index: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type WorkspaceMigrationReport = {
|
||||
fail: {
|
||||
workspaceId: string;
|
||||
error: Error;
|
||||
}[];
|
||||
success: {
|
||||
workspaceId: string;
|
||||
}[];
|
||||
};
|
||||
export type ActiveOrSuspendedWorkspacesMigrationCommandOptions =
|
||||
WorkspacesMigrationCommandOptions;
|
||||
|
||||
export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
|
||||
Options extends
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandOptions = ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
> extends MigrationCommandRunner {
|
||||
private workspaceIds: Set<string> = new Set();
|
||||
private startFromWorkspaceId: string | undefined;
|
||||
private workspaceCountLimit: number | undefined;
|
||||
public migrationReport: WorkspaceMigrationReport = {
|
||||
fail: [],
|
||||
success: [],
|
||||
};
|
||||
|
||||
> extends WorkspacesMigrationCommandRunner<Options> {
|
||||
constructor(
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super();
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService, [
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
]);
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--start-from-workspace-id [workspace_id]',
|
||||
description:
|
||||
'Start from a specific workspace id. Workspaces are processed in ascending order of id.',
|
||||
required: false,
|
||||
})
|
||||
parseStartFromWorkspaceId(val: string): string {
|
||||
this.startFromWorkspaceId = val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--workspace-count-limit [count]',
|
||||
description:
|
||||
'Limit the number of workspaces to process. Workspaces are processed in ascending order of id.',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceCountLimit(val: string): number {
|
||||
this.workspaceCountLimit = parseInt(val);
|
||||
|
||||
if (isNaN(this.workspaceCountLimit)) {
|
||||
throw new Error('Workspace count limit must be a number');
|
||||
}
|
||||
|
||||
if (this.workspaceCountLimit <= 0) {
|
||||
throw new Error('Workspace count limit must be greater than 0');
|
||||
}
|
||||
|
||||
return this.workspaceCountLimit;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description:
|
||||
'workspace id. Command runs on all active workspaces if not provided.',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceId(val: string): Set<string> {
|
||||
this.workspaceIds.add(val);
|
||||
|
||||
return this.workspaceIds;
|
||||
}
|
||||
|
||||
protected async fetchActiveWorkspaceIds(): Promise<string[]> {
|
||||
const activeWorkspaces = await this.workspaceRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
activationStatus: In([
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
]),
|
||||
...(this.startFromWorkspaceId
|
||||
? { id: MoreThanOrEqual(this.startFromWorkspaceId) }
|
||||
: {}),
|
||||
},
|
||||
order: {
|
||||
id: 'ASC',
|
||||
},
|
||||
take: this.workspaceCountLimit,
|
||||
});
|
||||
|
||||
return activeWorkspaces.map((workspace) => workspace.id);
|
||||
}
|
||||
|
||||
override async runMigrationCommand(
|
||||
_passedParams: string[],
|
||||
options: Options,
|
||||
) {
|
||||
const activeWorkspaceIds =
|
||||
this.workspaceIds.size > 0
|
||||
? Array.from(this.workspaceIds)
|
||||
: await this.fetchActiveWorkspaceIds();
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
|
||||
}
|
||||
|
||||
for (const [index, workspaceId] of activeWorkspaceIds.entries()) {
|
||||
this.logger.log(
|
||||
`Running command on workspace ${workspaceId} ${index + 1}/${activeWorkspaceIds.length}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.runOnWorkspace({
|
||||
options,
|
||||
workspaceId,
|
||||
dataSource,
|
||||
index: index,
|
||||
total: activeWorkspaceIds.length,
|
||||
});
|
||||
this.migrationReport.success.push({
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.migrationReport.fail.push({
|
||||
error,
|
||||
workspaceId,
|
||||
});
|
||||
this.logger.warn(
|
||||
chalk.red(`Error in workspace ${workspaceId}: ${error.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.migrationReport.fail.forEach(({ error, workspaceId }) =>
|
||||
this.logger.error(`Error in workspace ${workspaceId}: ${error.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
public abstract runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void>;
|
||||
}
|
||||
|
||||
+15
-4
@@ -12,10 +12,14 @@ import { In, Repository } from 'typeorm';
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import {
|
||||
RunOnWorkspaceArgs,
|
||||
WorkspacesMigrationCommandRunner,
|
||||
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { type DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
import {
|
||||
@@ -25,8 +29,14 @@ import {
|
||||
import { getPreviousVersion } from 'src/utils/version/get-previous-version';
|
||||
|
||||
export type VersionCommands = {
|
||||
beforeSyncMetadata: ActiveOrSuspendedWorkspacesMigrationCommandRunner[];
|
||||
afterSyncMetadata: ActiveOrSuspendedWorkspacesMigrationCommandRunner[];
|
||||
beforeSyncMetadata: (
|
||||
| WorkspacesMigrationCommandRunner
|
||||
| ActiveOrSuspendedWorkspacesMigrationCommandRunner
|
||||
)[];
|
||||
afterSyncMetadata: (
|
||||
| WorkspacesMigrationCommandRunner
|
||||
| ActiveOrSuspendedWorkspacesMigrationCommandRunner
|
||||
)[];
|
||||
};
|
||||
export type AllCommands = Record<string, VersionCommands>;
|
||||
const execPromise = promisify(exec);
|
||||
@@ -43,9 +53,10 @@ export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMi
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyConfigService: TwentyConfigService,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
private async loadActiveOrSuspendedWorkspace() {
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
import chalk from 'chalk';
|
||||
import { Option } from 'nest-commander';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { In, MoreThanOrEqual, type Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { type TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
export type WorkspacesMigrationCommandOptions = {
|
||||
workspaceIds: string[];
|
||||
startFromWorkspaceId?: string;
|
||||
workspaceCountLimit?: number;
|
||||
dryRun?: boolean;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export type RunOnWorkspaceArgs = {
|
||||
options: WorkspacesMigrationCommandOptions;
|
||||
workspaceId: string;
|
||||
dataSource?: WorkspaceDataSource;
|
||||
index: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type WorkspaceMigrationReport = {
|
||||
fail: {
|
||||
workspaceId: string;
|
||||
error: Error;
|
||||
}[];
|
||||
success: {
|
||||
workspaceId: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export abstract class WorkspacesMigrationCommandRunner<
|
||||
Options extends
|
||||
WorkspacesMigrationCommandOptions = WorkspacesMigrationCommandOptions,
|
||||
> extends MigrationCommandRunner {
|
||||
protected workspaceIds: Set<string> = new Set();
|
||||
private startFromWorkspaceId: string | undefined;
|
||||
private workspaceCountLimit: number | undefined;
|
||||
public migrationReport: WorkspaceMigrationReport = {
|
||||
fail: [],
|
||||
success: [],
|
||||
};
|
||||
|
||||
constructor(
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly activationStatuses: WorkspaceActivationStatus[],
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--start-from-workspace-id [workspace_id]',
|
||||
description:
|
||||
'Start from a specific workspace id. Workspaces are processed in ascending order of id.',
|
||||
required: false,
|
||||
})
|
||||
parseStartFromWorkspaceId(val: string): string {
|
||||
this.startFromWorkspaceId = val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--workspace-count-limit [count]',
|
||||
description:
|
||||
'Limit the number of workspaces to process. Workspaces are processed in ascending order of id.',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceCountLimit(val: string): number {
|
||||
this.workspaceCountLimit = parseInt(val);
|
||||
|
||||
if (isNaN(this.workspaceCountLimit)) {
|
||||
throw new Error('Workspace count limit must be a number');
|
||||
}
|
||||
|
||||
if (this.workspaceCountLimit <= 0) {
|
||||
throw new Error('Workspace count limit must be greater than 0');
|
||||
}
|
||||
|
||||
return this.workspaceCountLimit;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-w, --workspace-id [workspace_id]',
|
||||
description:
|
||||
'workspace id. Command runs on all workspaces matching the activation statuses if not provided.',
|
||||
required: false,
|
||||
})
|
||||
parseWorkspaceId(val: string): Set<string> {
|
||||
this.workspaceIds.add(val);
|
||||
|
||||
return this.workspaceIds;
|
||||
}
|
||||
|
||||
protected async fetchWorkspaceIds(): Promise<string[]> {
|
||||
const workspaces = await this.workspaceRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
activationStatus: In(this.activationStatuses),
|
||||
...(this.startFromWorkspaceId
|
||||
? { id: MoreThanOrEqual(this.startFromWorkspaceId) }
|
||||
: {}),
|
||||
},
|
||||
order: {
|
||||
id: 'ASC',
|
||||
},
|
||||
take: this.workspaceCountLimit,
|
||||
});
|
||||
|
||||
return workspaces.map((workspace) => workspace.id);
|
||||
}
|
||||
|
||||
override async runMigrationCommand(
|
||||
_passedParams: string[],
|
||||
options: Options,
|
||||
) {
|
||||
const workspaceIdsToProcess =
|
||||
this.workspaceIds.size > 0
|
||||
? Array.from(this.workspaceIds)
|
||||
: await this.fetchWorkspaceIds();
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(chalk.yellow('Dry run mode: No changes will be applied'));
|
||||
}
|
||||
|
||||
for (const [index, workspaceId] of workspaceIdsToProcess.entries()) {
|
||||
this.logger.log(
|
||||
`Running command on workspace ${workspaceId} ${index + 1}/${workspaceIdsToProcess.length}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const workspaceHasDataSource =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const dataSource = isDefined(workspaceHasDataSource)
|
||||
? await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
await this.runOnWorkspace({
|
||||
options,
|
||||
workspaceId,
|
||||
dataSource,
|
||||
index: index,
|
||||
total: workspaceIdsToProcess.length,
|
||||
});
|
||||
this.migrationReport.success.push({
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.migrationReport.fail.push({
|
||||
error,
|
||||
workspaceId,
|
||||
});
|
||||
this.logger.warn(
|
||||
chalk.red(`Error in workspace ${workspaceId}: ${error.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
|
||||
workspaceId,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.migrationReport.fail.forEach(({ error, workspaceId }) =>
|
||||
this.logger.error(`Error in workspace ${workspaceId}: ${error.message}`),
|
||||
);
|
||||
}
|
||||
|
||||
public abstract runOnWorkspace(args: RunOnWorkspaceArgs): Promise<void>;
|
||||
}
|
||||
+5
-5
@@ -4,11 +4,10 @@ import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@@ -25,12 +24,13 @@ export class AddWorkflowRunStopStatusesCommand extends ActiveOrSuspendedWorkspac
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -4,11 +4,10 @@ import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
@@ -24,12 +23,13 @@ export class CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand extends
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
@@ -22,10 +21,11 @@ export class CreateViewKanbanFieldMetadataIdForeignKeyMigrationCommand extends A
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@@ -20,9 +19,10 @@ export class FlushCacheCommand extends ActiveOrSuspendedWorkspacesMigrationComma
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -4,11 +4,10 @@ import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@@ -22,11 +21,12 @@ export class MakeSureDashboardNamingAvailableCommand extends ActiveOrSuspendedWo
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly objectMetadataServiceV2: ObjectMetadataServiceV2,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+6
-6
@@ -1,15 +1,14 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { type AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
@@ -24,8 +23,9 @@ export class MigrateAttachmentAuthorToCreatedByCommand extends ActiveOrSuspended
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { type AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
|
||||
@@ -32,8 +31,9 @@ export class MigrateAttachmentTypeToFileCategoryCommand extends ActiveOrSuspende
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
|
||||
@@ -21,10 +20,11 @@ export class MigrateChannelPartialFullSyncStagesCommand extends ActiveOrSuspende
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+6
-6
@@ -1,15 +1,14 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, In, Repository, type QueryRunner } from 'typeorm';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { DataSource, In, Repository, type QueryRunner } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SEARCH_FIELDS_FOR_CUSTOM_OBJECT } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
@@ -42,6 +41,7 @@ export class RegenerateSearchVectorsCommand extends ActiveOrSuspendedWorkspacesM
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceSchemaManager: WorkspaceSchemaManagerService,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@@ -50,7 +50,7 @@ export class RegenerateSearchVectorsCommand extends ActiveOrSuspendedWorkspacesM
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+6
-6
@@ -1,17 +1,16 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
|
||||
@@ -32,6 +31,7 @@ export class SeedDashboardViewCommand extends ActiveOrSuspendedWorkspacesMigrati
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(DataSourceEntity)
|
||||
@@ -40,7 +40,7 @@ export class SeedDashboardViewCommand extends ActiveOrSuspendedWorkspacesMigrati
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+2
@@ -15,6 +15,7 @@ import { ApplicationEntity } from 'src/engine/core-modules/application/applicati
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
@@ -35,6 +36,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
DataSourceEntity,
|
||||
ApplicationEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
ObjectMetadataModule,
|
||||
|
||||
+5
-5
@@ -3,12 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { In, type Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@@ -25,8 +24,9 @@ export class CleanOrphanedRoleTargetsCommand extends ActiveOrSuspendedWorkspaces
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,12 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { In, type Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@@ -22,10 +21,11 @@ export class CleanOrphanedUserWorkspacesCommand extends ActiveOrSuspendedWorkspa
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,14 +3,13 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/core-modules/application/constants/twenty-standard-applications';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
@@ -23,11 +22,12 @@ export class CreateTwentyStandardApplicationCommand extends ActiveOrSuspendedWor
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+2
@@ -8,6 +8,7 @@ import { ApplicationEntity } from 'src/engine/core-modules/application/applicati
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -27,6 +28,7 @@ import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-sc
|
||||
RoleTargetsEntity,
|
||||
ApplicationEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
ApplicationModule,
|
||||
],
|
||||
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { CALENDAR_CHANNEL_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-12:add-calendar-events-import-scheduled-sync-stage',
|
||||
description:
|
||||
'Replace calendar channel syncStage enum with complete CalendarChannelSyncStage values and update default to PENDING_CONFIGURATION',
|
||||
})
|
||||
export class AddCalendarEventsImportScheduledSyncStageCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Updating calendar channel syncStage enum and default value for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.updateCalendarChannelSyncStageFieldMetadata(
|
||||
workspaceId,
|
||||
options,
|
||||
);
|
||||
|
||||
await this.addCalendarEventsImportScheduledEnumValue(workspaceId, options);
|
||||
|
||||
this.logger.log(
|
||||
`Successfully updated calendar channel syncStage enum and default value for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async addCalendarEventsImportScheduledEnumValue(
|
||||
workspaceId: string,
|
||||
options: RunOnWorkspaceArgs['options'],
|
||||
): Promise<void> {
|
||||
const calendarChannelObject = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: STANDARD_OBJECT_IDS.calendarChannel,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!calendarChannelObject) {
|
||||
this.logger.log(
|
||||
`CalendarChannel object not found for workspace ${workspaceId}, skipping enum migration`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'calendarChannel';
|
||||
const columnName = 'syncStage';
|
||||
const enumName = 'calendarChannel_syncStage_enum';
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would replace ${enumName} with complete CalendarChannelSyncStage enum values for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// Remove default value first
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT`,
|
||||
);
|
||||
|
||||
// Convert column to text to remove enum dependency
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${columnName}" TYPE text USING "${columnName}"::text`,
|
||||
);
|
||||
|
||||
// Drop the old enum (now safe since no column uses it)
|
||||
await queryRunner.query(
|
||||
`DROP TYPE IF EXISTS ${schemaName}."${enumName}" CASCADE`,
|
||||
);
|
||||
|
||||
// Create new enum with all CalendarChannelSyncStage values
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE ${schemaName}."${enumName}" AS ENUM (
|
||||
'${CalendarChannelSyncStage.PENDING_CONFIGURATION}',
|
||||
'${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING}',
|
||||
'${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED}',
|
||||
'${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING}',
|
||||
'${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING}',
|
||||
'${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED}',
|
||||
'${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING}',
|
||||
'${CalendarChannelSyncStage.FAILED}'
|
||||
)`,
|
||||
);
|
||||
|
||||
// Convert column back to enum type
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}"
|
||||
ALTER COLUMN "${columnName}" TYPE ${schemaName}."${enumName}"
|
||||
USING "${columnName}"::${schemaName}."${enumName}"`,
|
||||
);
|
||||
|
||||
// Update default value to PENDING_CONFIGURATION
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}"
|
||||
ALTER COLUMN "${columnName}" SET DEFAULT '${CalendarChannelSyncStage.PENDING_CONFIGURATION}'::${schemaName}."${enumName}"`,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
this.logger.log(
|
||||
`Successfully replaced ${enumName} with complete CalendarChannelSyncStage enum values and updated default to PENDING_CONFIGURATION for workspace ${workspaceId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
|
||||
this.logger.error(
|
||||
`Error replacing ${enumName} for workspace ${workspaceId}: ${error}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
private async updateCalendarChannelSyncStageFieldMetadata(
|
||||
workspaceId: string,
|
||||
options: RunOnWorkspaceArgs['options'],
|
||||
): Promise<void> {
|
||||
const calendarChannelObject = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: STANDARD_OBJECT_IDS.calendarChannel,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!calendarChannelObject) {
|
||||
this.logger.log(
|
||||
`CalendarChannel object not found for workspace ${workspaceId}, skipping field metadata update`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStageField = await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: CALENDAR_CHANNEL_STANDARD_FIELD_IDS.syncStage,
|
||||
objectMetadataId: calendarChannelObject.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!syncStageField) {
|
||||
this.logger.log(
|
||||
`CalendarChannel syncStage field not found for workspace ${workspaceId}, skipping field metadata update`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would add CALENDAR_EVENTS_IMPORT_SCHEDULED to CalendarChannel syncStage field metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStageFieldOptions = [
|
||||
{
|
||||
value: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
label: 'Calendar event list fetch pending',
|
||||
position: 0,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
label: 'Calendar event list fetch scheduled',
|
||||
position: 1,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
label: 'Calendar event list fetch ongoing',
|
||||
position: 2,
|
||||
color: 'orange',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING,
|
||||
label: 'Calendar events import pending',
|
||||
position: 3,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED,
|
||||
label: 'Calendar events import scheduled',
|
||||
position: 4,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
label: 'Calendar events import ongoing',
|
||||
position: 5,
|
||||
color: 'orange',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.FAILED,
|
||||
label: 'Failed',
|
||||
position: 6,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
value: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
label: 'Pending configuration',
|
||||
position: 7,
|
||||
color: 'gray',
|
||||
},
|
||||
];
|
||||
|
||||
syncStageField.options = syncStageFieldOptions;
|
||||
syncStageField.defaultValue = `'${CalendarChannelSyncStage.PENDING_CONFIGURATION}'`;
|
||||
|
||||
await this.fieldMetadataRepository.save(syncStageField);
|
||||
|
||||
this.logger.log(
|
||||
`Added CALENDAR_EVENTS_IMPORT_SCHEDULED to CalendarChannel syncStage field metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { MESSAGE_CHANNEL_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-12:add-messages-import-scheduled-sync-stage',
|
||||
description:
|
||||
'Replace message channel syncStage enum with complete MessageChannelSyncStage values and update default to PENDING_CONFIGURATION',
|
||||
})
|
||||
export class AddMessagesImportScheduledSyncStageCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Updating message channel syncStage enum and default value for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.updateMessageChannelSyncStageFieldMetadata(workspaceId, options);
|
||||
|
||||
await this.addMessagesImportScheduledEnumValue(workspaceId, options);
|
||||
|
||||
this.logger.log(
|
||||
`Successfully updated message channel syncStage enum and default value for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async addMessagesImportScheduledEnumValue(
|
||||
workspaceId: string,
|
||||
options: RunOnWorkspaceArgs['options'],
|
||||
): Promise<void> {
|
||||
const messageChannelObject = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: STANDARD_OBJECT_IDS.messageChannel,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!messageChannelObject) {
|
||||
this.logger.log(
|
||||
`MessageChannel object not found for workspace ${workspaceId}, skipping enum migration`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'messageChannel';
|
||||
const columnName = 'syncStage';
|
||||
const enumName = 'messageChannel_syncStage_enum';
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would replace ${enumName} with complete MessageChannelSyncStage enum values for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// Remove default value first
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${columnName}" DROP DEFAULT`,
|
||||
);
|
||||
|
||||
// Convert column to text to remove enum dependency
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${columnName}" TYPE text USING "${columnName}"::text`,
|
||||
);
|
||||
|
||||
// Drop the old enum (now safe since no column uses it)
|
||||
await queryRunner.query(
|
||||
`DROP TYPE IF EXISTS ${schemaName}."${enumName}" CASCADE`,
|
||||
);
|
||||
|
||||
// Create new enum with all MessageChannelSyncStage values
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE ${schemaName}."${enumName}" AS ENUM (
|
||||
'${MessageChannelSyncStage.PENDING_CONFIGURATION}',
|
||||
'${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}',
|
||||
'${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}',
|
||||
'${MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING}',
|
||||
'${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}',
|
||||
'${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}',
|
||||
'${MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING}',
|
||||
'${MessageChannelSyncStage.FAILED}'
|
||||
)`,
|
||||
);
|
||||
|
||||
// Convert column back to enum type
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}"
|
||||
ALTER COLUMN "${columnName}" TYPE ${schemaName}."${enumName}"
|
||||
USING "${columnName}"::${schemaName}."${enumName}"`,
|
||||
);
|
||||
|
||||
// Update default value to PENDING_CONFIGURATION
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}"
|
||||
ALTER COLUMN "${columnName}" SET DEFAULT '${MessageChannelSyncStage.PENDING_CONFIGURATION}'::${schemaName}."${enumName}"`,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
this.logger.log(
|
||||
`Successfully replaced ${enumName} with complete MessageChannelSyncStage enum values and updated default to PENDING_CONFIGURATION for workspace ${workspaceId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
|
||||
this.logger.error(
|
||||
`Error replacing ${enumName} for workspace ${workspaceId}: ${error}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
private async updateMessageChannelSyncStageFieldMetadata(
|
||||
workspaceId: string,
|
||||
options: RunOnWorkspaceArgs['options'],
|
||||
): Promise<void> {
|
||||
const messageChannelObject = await this.objectMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: STANDARD_OBJECT_IDS.messageChannel,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!messageChannelObject) {
|
||||
this.logger.log(
|
||||
`MessageChannel object not found for workspace ${workspaceId}, skipping field metadata update`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStageField = await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncStage,
|
||||
objectMetadataId: messageChannelObject.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!syncStageField) {
|
||||
this.logger.log(
|
||||
`MessageChannel syncStage field not found for workspace ${workspaceId}, skipping field metadata update`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would add MESSAGES_IMPORT_SCHEDULED to MessageChannel syncStage field metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStageFieldOptions = [
|
||||
{
|
||||
value: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
label: 'Messages list fetch pending',
|
||||
position: 0,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
label: 'Messages list fetch scheduled',
|
||||
position: 1,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
label: 'Messages list fetch ongoing',
|
||||
position: 2,
|
||||
color: 'orange',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||
label: 'Messages import pending',
|
||||
position: 3,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
label: 'Messages import scheduled',
|
||||
position: 4,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
label: 'Messages import ongoing',
|
||||
position: 5,
|
||||
color: 'orange',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.FAILED,
|
||||
label: 'Failed',
|
||||
position: 6,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
value: MessageChannelSyncStage.PENDING_CONFIGURATION,
|
||||
label: 'Pending configuration',
|
||||
position: 7,
|
||||
color: 'gray',
|
||||
},
|
||||
];
|
||||
|
||||
syncStageField.options = syncStageFieldOptions;
|
||||
syncStageField.defaultValue = `'${MessageChannelSyncStage.PENDING_CONFIGURATION}'`;
|
||||
|
||||
await this.fieldMetadataRepository.save(syncStageField);
|
||||
|
||||
this.logger.log(
|
||||
`Added MESSAGES_IMPORT_SCHEDULED to MessageChannel syncStage field metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -2,14 +2,16 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
WorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
@@ -17,14 +19,21 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.
|
||||
description:
|
||||
'Create workspace-custom application for workspaces that do not have them',
|
||||
})
|
||||
export class CreateWorkspaceCustomApplicationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
export class CreateWorkspaceCustomApplicationCommand extends WorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService, [
|
||||
WorkspaceActivationStatus.ONGOING_CREATION,
|
||||
WorkspaceActivationStatus.PENDING_CREATION,
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.INACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
]);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,12 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
// Note: this command needs to be run after CreateWorkspaceCustomApplicationCommand
|
||||
@@ -21,9 +20,10 @@ export class SetStandardApplicationNotUninstallableCommand extends ActiveOrSuspe
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+17
-1
@@ -1,25 +1,41 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AddCalendarEventsImportScheduledSyncStageCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-add-calendar-events-import-scheduled-sync-stage.command';
|
||||
import { AddMessagesImportScheduledSyncStageCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-add-messages-import-scheduled-sync-stage.command';
|
||||
import { CreateWorkspaceCustomApplicationCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-create-workspace-custom-application.command';
|
||||
import { SetStandardApplicationNotUninstallableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-set-standard-application-not-uninstallable.command';
|
||||
import { WorkspaceCustomApplicationIdNonNullableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-workspace-custom-application-id-non-nullable-migration.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
ObjectMetadataEntity,
|
||||
FieldMetadataEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
ApplicationModule,
|
||||
FieldMetadataModule,
|
||||
],
|
||||
providers: [
|
||||
AddCalendarEventsImportScheduledSyncStageCommand,
|
||||
AddMessagesImportScheduledSyncStageCommand,
|
||||
CreateWorkspaceCustomApplicationCommand,
|
||||
SetStandardApplicationNotUninstallableCommand,
|
||||
WorkspaceCustomApplicationIdNonNullableCommand,
|
||||
],
|
||||
exports: [
|
||||
AddCalendarEventsImportScheduledSyncStageCommand,
|
||||
AddMessagesImportScheduledSyncStageCommand,
|
||||
CreateWorkspaceCustomApplicationCommand,
|
||||
SetStandardApplicationNotUninstallableCommand,
|
||||
WorkspaceCustomApplicationIdNonNullableCommand,
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
@@ -21,10 +20,11 @@ export class WorkspaceCustomApplicationIdNonNullableCommand extends ActiveOrSusp
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
@@ -22,12 +21,13 @@ export class FixLabelIdentifierPositionAndVisibilityCommand extends ActiveOrSusp
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
@InjectRepository(ViewFieldEntity)
|
||||
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FixLabelIdentifierPositionAndVisibilityCommand } from 'src/database/commands/upgrade-version-command/1-6/1-6-fix-label-identifier-position-and-visibility.command';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
@@ -20,6 +21,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
||||
ViewEntity,
|
||||
ViewFieldEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceDataSourceModule,
|
||||
WorkspaceSchemaManagerModule,
|
||||
WorkspaceMetadataVersionModule,
|
||||
|
||||
+5
-5
@@ -4,11 +4,10 @@ import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||
@@ -23,10 +22,11 @@ export class BackfillWorkflowManualTriggerAvailabilityCommand extends ActiveOrSu
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+2
-1
@@ -3,9 +3,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { BackfillWorkflowManualTriggerAvailabilityCommand } from 'src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([WorkspaceEntity])],
|
||||
imports: [TypeOrmModule.forFeature([WorkspaceEntity]), DataSourceModule],
|
||||
providers: [BackfillWorkflowManualTriggerAvailabilityCommand],
|
||||
exports: [BackfillWorkflowManualTriggerAvailabilityCommand],
|
||||
})
|
||||
|
||||
+11
-5
@@ -2,13 +2,13 @@ import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
||||
@@ -38,6 +38,7 @@ export class DeduplicateUniqueFieldsCommand extends ActiveOrSuspendedWorkspacesM
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly indexMetadataService: IndexMetadataService,
|
||||
protected readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||
protected readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||
@@ -46,7 +47,7 @@ export class DeduplicateUniqueFieldsCommand extends ActiveOrSuspendedWorkspacesM
|
||||
@InjectRepository(IndexMetadataEntity)
|
||||
protected readonly indexMetadataRepository: Repository<IndexMetadataEntity>,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
@@ -57,6 +58,11 @@ export class DeduplicateUniqueFieldsCommand extends ActiveOrSuspendedWorkspacesM
|
||||
this.logger.log(
|
||||
`Deduplicating indexed fields for workspace ${workspaceId}`,
|
||||
);
|
||||
if (!isDefined(dataSource)) {
|
||||
throw new Error(
|
||||
'Could not find workspace dataSource, should never occur',
|
||||
);
|
||||
}
|
||||
|
||||
await this.deduplicateUniqueUserEmailFieldForWorkspaceMembers({
|
||||
dataSource,
|
||||
|
||||
+7
-7
@@ -4,14 +4,13 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-8:fill-null-serverless-function-layer-id',
|
||||
@@ -26,12 +25,13 @@ export class FillNullServerlessFunctionLayerIdCommand extends ActiveOrSuspendedW
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ServerlessFunctionEntity)
|
||||
protected readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
@InjectRepository(ServerlessFunctionLayerEntity)
|
||||
protected readonly serverlessFunctionLayerRepository: Repository<ServerlessFunctionLayerEntity>,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+6
-6
@@ -1,14 +1,13 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -32,6 +31,7 @@ export class MigrateChannelSyncStagesCommand extends ActiveOrSuspendedWorkspaces
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
@@ -39,7 +39,7 @@ export class MigrateChannelSyncStagesCommand extends ActiveOrSuspendedWorkspaces
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -8,11 +8,10 @@ import {
|
||||
} from 'twenty-shared/utils';
|
||||
import { Raw, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||
import { isWorkflowFilterAction } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/guards/is-workflow-filter-action.guard';
|
||||
@@ -28,8 +27,9 @@ export class MigrateWorkflowStepFilterOperandValueCommand extends ActiveOrSuspen
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository, type QueryRunner } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
@@ -26,9 +25,10 @@ export class RegeneratePersonSearchVectorWithPhonesCommand extends ActiveOrSuspe
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceSchemaManager: WorkspaceSchemaManagerService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+2
@@ -7,6 +7,7 @@ import { MigrateChannelSyncStagesCommand } from 'src/database/commands/upgrade-v
|
||||
import { MigrateWorkflowStepFilterOperandValueCommand } from 'src/database/commands/upgrade-version-command/1-8/1-8-migrate-workflow-step-filter-operand-value';
|
||||
import { RegeneratePersonSearchVectorWithPhonesCommand } from 'src/database/commands/upgrade-version-command/1-8/1-8-regenerate-person-search-vector-with-phones.command';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module';
|
||||
@@ -27,6 +28,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
|
||||
ServerlessFunctionEntity,
|
||||
ServerlessFunctionLayerEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
IndexMetadataModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
WorkspaceMigrationModule,
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import { V1_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
|
||||
import { V1_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-8/1-8-upgrade-version-command.module';
|
||||
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
||||
|
||||
@Module({
|
||||
@@ -20,6 +21,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
V1_10_UpgradeVersionCommandModule,
|
||||
V1_11_UpgradeVersionCommandModule,
|
||||
V1_12_UpgradeVersionCommandModule,
|
||||
DataSourceModule,
|
||||
WorkspaceSyncMetadataModule,
|
||||
],
|
||||
providers: [UpgradeCommand],
|
||||
|
||||
+9
@@ -21,6 +21,8 @@ import { RegenerateSearchVectorsCommand } from 'src/database/commands/upgrade-ve
|
||||
import { CleanOrphanedRoleTargetsCommand } from 'src/database/commands/upgrade-version-command/1-11/1-11-clean-orphaned-role-targets.command';
|
||||
import { CleanOrphanedUserWorkspacesCommand } from 'src/database/commands/upgrade-version-command/1-11/1-11-clean-orphaned-user-workspaces.command';
|
||||
import { CreateTwentyStandardApplicationCommand } from 'src/database/commands/upgrade-version-command/1-11/1-11-create-twenty-standard-application.command';
|
||||
import { AddCalendarEventsImportScheduledSyncStageCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-add-calendar-events-import-scheduled-sync-stage.command';
|
||||
import { AddMessagesImportScheduledSyncStageCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-add-messages-import-scheduled-sync-stage.command';
|
||||
import { CreateWorkspaceCustomApplicationCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-create-workspace-custom-application.command';
|
||||
import { SetStandardApplicationNotUninstallableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-set-standard-application-not-uninstallable.command';
|
||||
import { WorkspaceCustomApplicationIdNonNullableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-workspace-custom-application-id-non-nullable-migration.command';
|
||||
@@ -33,6 +35,7 @@ import { MigrateWorkflowStepFilterOperandValueCommand } from 'src/database/comma
|
||||
import { RegeneratePersonSearchVectorWithPhonesCommand } from 'src/database/commands/upgrade-version-command/1-8/1-8-regenerate-person-search-vector-with-phones.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
|
||||
@@ -48,6 +51,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyConfigService: TwentyConfigService,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||
|
||||
// 1.6 Commands
|
||||
@@ -84,11 +88,14 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly createTwentyStandardApplicationCommand: CreateTwentyStandardApplicationCommand,
|
||||
protected readonly createWorkspaceCustomApplicationCommand: CreateWorkspaceCustomApplicationCommand,
|
||||
protected readonly workspaceCustomApplicationIdNonNullableCommand: WorkspaceCustomApplicationIdNonNullableCommand,
|
||||
protected readonly addMessagesImportScheduledSyncStageCommand: AddMessagesImportScheduledSyncStageCommand,
|
||||
protected readonly addCalendarEventsImportScheduledSyncStageCommand: AddCalendarEventsImportScheduledSyncStageCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
twentyConfigService,
|
||||
twentyORMGlobalManager,
|
||||
dataSourceService,
|
||||
syncWorkspaceMetadataCommand,
|
||||
);
|
||||
|
||||
@@ -143,6 +150,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
beforeSyncMetadata: [
|
||||
this.createWorkspaceCustomApplicationCommand,
|
||||
this.workspaceCustomApplicationIdNonNullableCommand,
|
||||
this.addMessagesImportScheduledSyncStageCommand,
|
||||
this.addCalendarEventsImportScheduledSyncStageCommand,
|
||||
],
|
||||
afterSyncMetadata: [this.setStandardApplicationNotUninstallableCommand],
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
import { CoreEngineModule } from 'src/engine/core-modules/core-engine.module';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { useSentryTracing } from 'src/engine/core-modules/exception-handler/hooks/use-sentry-tracing';
|
||||
import { useDisableIntrospectionForUnauthenticatedUsers } from 'src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
@@ -71,6 +72,9 @@ export class GraphQLConfigService
|
||||
i18nService: this.i18nService,
|
||||
twentyConfigService: this.twentyConfigService,
|
||||
}),
|
||||
useDisableIntrospectionForUnauthenticatedUsers(
|
||||
this.twentyConfigService.get('NODE_ENV') === NodeEnvironment.PRODUCTION,
|
||||
),
|
||||
];
|
||||
|
||||
if (Sentry.isInitialized()) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useCachedMetadata } from 'src/engine/api/graphql/graphql-config/hooks/u
|
||||
import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module';
|
||||
import { type CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { type ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { useDisableIntrospectionForUnauthenticatedUsers } from 'src/engine/core-modules/graphql/hooks/use-disable-introspection-for-unauthenticated-users.hook';
|
||||
import { useGraphQLErrorHandlerHook } from 'src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook';
|
||||
import { type I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { type MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
@@ -41,6 +42,9 @@ export const metadataModuleFactory = async (
|
||||
cacheSetter: cacheStorageService.set.bind(cacheStorageService),
|
||||
operationsToCache: ['ObjectMetadataItems', 'FindAllCoreViews'],
|
||||
}),
|
||||
useDisableIntrospectionForUnauthenticatedUsers(
|
||||
twentyConfigService.get('NODE_ENV') === NodeEnvironment.PRODUCTION,
|
||||
),
|
||||
],
|
||||
path: '/metadata',
|
||||
context: () => ({
|
||||
|
||||
@@ -34,6 +34,7 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
|
||||
@Module({
|
||||
@@ -56,6 +57,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
UserWorkspaceEntity,
|
||||
FeatureFlagEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
],
|
||||
providers: [
|
||||
BillingSubscriptionService,
|
||||
|
||||
+5
-5
@@ -6,13 +6,12 @@ import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { BillingCustomerEntity } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
|
||||
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
@@ -27,8 +26,9 @@ export class BillingSyncCustomerDataCommand extends ActiveOrSuspendedWorkspacesM
|
||||
@InjectRepository(BillingCustomerEntity)
|
||||
protected readonly billingCustomerRepository: Repository<BillingCustomerEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+5
-5
@@ -6,14 +6,13 @@ import { Command, Option } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { BillingSubscriptionEntity } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
||||
import { StripeSubscriptionItemService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription-item.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
@@ -33,8 +32,9 @@ export class BillingUpdateSubscriptionPriceCommand extends ActiveOrSuspendedWork
|
||||
protected readonly billingSubscriptionRepository: Repository<BillingSubscriptionEntity>,
|
||||
private readonly billingSubscriptionService: BillingSubscriptionService,
|
||||
private readonly stripeSubscriptionItemService: StripeSubscriptionItemService,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
@Option({
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { type Plugin } from 'graphql-yoga';
|
||||
import { NoSchemaIntrospectionCustomRule } from 'graphql/validation/rules/custom/NoSchemaIntrospectionCustomRule';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type GraphQLContext } from 'src/engine/api/graphql/graphql-config/graphql-config.service';
|
||||
|
||||
export const useDisableIntrospectionForUnauthenticatedUsers = (
|
||||
isProductionEnvironment: boolean,
|
||||
): Plugin<GraphQLContext> => ({
|
||||
onValidate: ({ context, addValidationRule }) => {
|
||||
const isAuthenticated = isDefined(context.req.workspace);
|
||||
|
||||
if (!isAuthenticated && isProductionEnvironment) {
|
||||
addValidationRule(NoSchemaIntrospectionCustomRule);
|
||||
}
|
||||
},
|
||||
});
|
||||
+3
-1
@@ -146,9 +146,11 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
|
||||
async updateOneField({
|
||||
updateFieldInput,
|
||||
workspaceId,
|
||||
isSystemBuild = false,
|
||||
}: {
|
||||
updateFieldInput: Omit<UpdateFieldInput, 'workspaceId'>;
|
||||
workspaceId: string;
|
||||
isSystemBuild?: boolean;
|
||||
}): Promise<FlatFieldMetadata> {
|
||||
const {
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
@@ -256,7 +258,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
|
||||
}),
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
isSystemBuild,
|
||||
inferDeletionFromMissingEntities: {
|
||||
index: true,
|
||||
viewGroup: true,
|
||||
|
||||
+8
-30
@@ -10,7 +10,6 @@ import {
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { getCompositeTypeOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/get-composite-type-or-throw.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
@@ -270,6 +269,14 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
newColumnName: toCompositeColumnName,
|
||||
});
|
||||
}
|
||||
} else if (!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
oldColumnName: update.from,
|
||||
newColumnName: update.to,
|
||||
});
|
||||
}
|
||||
|
||||
const enumOperations = collectEnumOperationsForField({
|
||||
@@ -411,33 +418,4 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleMorphOrRelationSettingsUpdate({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
update,
|
||||
}: UpdateFieldPropertyUpdateHandlerArgs<
|
||||
'settings',
|
||||
MorphOrRelationFieldMetadataType
|
||||
>) {
|
||||
const fromJoinColumnName = update.from.joinColumnName;
|
||||
const toJoinColumnName = update.to.joinColumnName;
|
||||
|
||||
if (
|
||||
!isDefined(fromJoinColumnName) ||
|
||||
!isDefined(toJoinColumnName) ||
|
||||
fromJoinColumnName === toJoinColumnName
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
oldColumnName: fromJoinColumnName,
|
||||
newColumnName: toJoinColumnName,
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -3,15 +3,13 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
|
||||
import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service';
|
||||
|
||||
@@ -24,12 +22,12 @@ export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMig
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+26
-2
@@ -4,6 +4,10 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import {
|
||||
CalendarEventListFetchJob,
|
||||
type CalendarEventListFetchJobData,
|
||||
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
@@ -14,6 +18,10 @@ import {
|
||||
MessageChannelSyncStatus,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
type MessagingMessageListFetchJobData,
|
||||
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
|
||||
|
||||
export type StartChannelSyncInput = {
|
||||
connectedAccountId: string;
|
||||
@@ -56,9 +64,17 @@ export class ChannelSyncService {
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await messageChannelRepository.update(messageChannel.id, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
});
|
||||
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +97,17 @@ export class ChannelSyncService {
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
|
||||
await this.calendarQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
calendarChannelId: calendarChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util';
|
||||
import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util';
|
||||
import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util';
|
||||
import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util';
|
||||
|
||||
@Injectable()
|
||||
export class GmailEmailAliasErrorHandlerService {
|
||||
private readonly logger = new Logger(GmailEmailAliasErrorHandlerService.name);
|
||||
|
||||
constructor() {}
|
||||
|
||||
public handleError(error: unknown): void {
|
||||
this.logger.error(`Google: Error getting email aliases: ${error}`);
|
||||
if (isGmailNetworkError(error)) {
|
||||
throw parseGmailNetworkError(error);
|
||||
}
|
||||
|
||||
if (isGmailApiError(error)) {
|
||||
throw parseGmailApiError(error);
|
||||
}
|
||||
|
||||
throw new MessageImportDriverException(
|
||||
'Unknown error',
|
||||
MessageImportDriverExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { google } from 'googleapis';
|
||||
|
||||
import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@@ -9,6 +10,7 @@ import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-acco
|
||||
export class GoogleEmailAliasManagerService {
|
||||
constructor(
|
||||
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
|
||||
private readonly gmailEmailAliasErrorHandlerService: GmailEmailAliasErrorHandlerService,
|
||||
) {}
|
||||
|
||||
public async getHandleAliases(
|
||||
@@ -24,10 +26,14 @@ export class GoogleEmailAliasManagerService {
|
||||
auth: oAuth2Client,
|
||||
});
|
||||
|
||||
const emailsResponse = await peopleClient.people.get({
|
||||
resourceName: 'people/me',
|
||||
personFields: 'emailAddresses',
|
||||
});
|
||||
const emailsResponse = await peopleClient.people
|
||||
.get({
|
||||
resourceName: 'people/me',
|
||||
personFields: 'emailAddresses',
|
||||
})
|
||||
.catch((error) => {
|
||||
throw this.gmailEmailAliasErrorHandlerService.handleError(error);
|
||||
});
|
||||
|
||||
const emailAddresses = emailsResponse.data.emailAddresses;
|
||||
|
||||
+4
-2
@@ -1,7 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service';
|
||||
import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service';
|
||||
import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service';
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
|
||||
@@ -10,6 +11,7 @@ import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-
|
||||
providers: [
|
||||
EmailAliasManagerService,
|
||||
GoogleEmailAliasManagerService,
|
||||
GmailEmailAliasErrorHandlerService,
|
||||
MicrosoftEmailAliasManagerService,
|
||||
],
|
||||
exports: [EmailAliasManagerService],
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import {
|
||||
ConnectedAccountRefreshAccessTokenExceptionCode,
|
||||
} from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { isAxiosTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-axios-gaxios-error.util';
|
||||
import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util';
|
||||
|
||||
export type ConnectedAccountTokens = {
|
||||
accessToken: string;
|
||||
@@ -142,7 +142,7 @@ export class ConnectedAccountRefreshTokensService {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isAxiosTemporaryError(error)) {
|
||||
if (isGmailNetworkError(error)) {
|
||||
throw new ConnectedAccountRefreshAccessTokenException(
|
||||
`Error refreshing tokens for connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}: ${error.code}`,
|
||||
ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR,
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
+5
-5
@@ -3,11 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
type RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
|
||||
@@ -21,8 +20,9 @@ export class MessagingMessageCleanerRemoveOrphansCommand extends ActiveOrSuspend
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly messagingMessageCleanerService: MessagingMessageCleanerService,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
|
||||
+2
-1
@@ -2,13 +2,14 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { MessagingMessageCleanerRemoveOrphansCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-message-clearner-remove-orphans.command';
|
||||
import { MessagingConnectedAccountDeletionCleanupJob } from 'src/modules/messaging/message-cleaner/jobs/messaging-connected-account-deletion-cleanup.job';
|
||||
import { MessagingMessageCleanerConnectedAccountListener } from 'src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([WorkspaceEntity])],
|
||||
imports: [TypeOrmModule.forFeature([WorkspaceEntity]), DataSourceModule],
|
||||
providers: [
|
||||
MessagingMessageCleanerService,
|
||||
MessagingConnectedAccountDeletionCleanupJob,
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util';
|
||||
import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util';
|
||||
import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util';
|
||||
import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util';
|
||||
|
||||
@Injectable()
|
||||
export class GmailFoldersErrorHandlerService {
|
||||
private readonly logger = new Logger(GmailFoldersErrorHandlerService.name);
|
||||
|
||||
constructor() {}
|
||||
|
||||
public handleError(error: unknown): void {
|
||||
this.logger.error(`Gmail: Error fetching folders: ${error}`);
|
||||
if (isGmailNetworkError(error)) {
|
||||
throw parseGmailNetworkError(error);
|
||||
}
|
||||
|
||||
if (isGmailApiError(error)) {
|
||||
throw parseGmailApiError(error);
|
||||
}
|
||||
|
||||
throw new MessageImportDriverException(
|
||||
'Unknown error',
|
||||
MessageImportDriverExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -10,11 +10,11 @@ import {
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service';
|
||||
import { extractGmailFolderName } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/extract-gmail-folder-name.util';
|
||||
import { getGmailFolderParentId } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/get-gmail-folder-parent-id.util';
|
||||
import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util';
|
||||
import { MESSAGING_GMAIL_DEFAULT_NOT_SYNCED_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-default-not-synced-labels';
|
||||
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
|
||||
|
||||
@Injectable()
|
||||
export class GmailGetAllFoldersService implements MessageFolderDriver {
|
||||
@@ -22,7 +22,7 @@ export class GmailGetAllFoldersService implements MessageFolderDriver {
|
||||
|
||||
constructor(
|
||||
private readonly oAuth2ClientManagerService: OAuth2ClientManagerService,
|
||||
private readonly gmailMessageListFetchErrorHandler: GmailMessageListFetchErrorHandler,
|
||||
private readonly gmailFoldersErrorHandlerService: GmailFoldersErrorHandlerService,
|
||||
) {}
|
||||
|
||||
async getAllMessageFolders(
|
||||
@@ -53,7 +53,7 @@ export class GmailGetAllFoldersService implements MessageFolderDriver {
|
||||
`Connected account ${connectedAccount.id}: Error fetching labels: ${error.message}`,
|
||||
);
|
||||
|
||||
this.gmailMessageListFetchErrorHandler.handleError(error);
|
||||
this.gmailFoldersErrorHandlerService.handleError(error);
|
||||
|
||||
return { data: { labels: [] } };
|
||||
});
|
||||
+5
-3
@@ -6,9 +6,10 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/microsoft-get-all-folders.service';
|
||||
import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service';
|
||||
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
|
||||
import { MessagingGmailDriverModule } from 'src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module';
|
||||
import { MessagingIMAPDriverModule } from 'src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module';
|
||||
@@ -28,6 +29,7 @@ import { MessagingMicrosoftDriverModule } from 'src/modules/messaging/message-im
|
||||
providers: [
|
||||
SyncMessageFoldersService,
|
||||
GmailGetAllFoldersService,
|
||||
GmailFoldersErrorHandlerService,
|
||||
ImapGetAllFoldersService,
|
||||
MicrosoftGetAllFoldersService,
|
||||
],
|
||||
|
||||
+3
-3
@@ -14,9 +14,9 @@ import {
|
||||
MessageFolderPendingSyncAction,
|
||||
type MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/microsoft-get-all-folders.service';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service';
|
||||
import { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/microsoft/types/folders';
|
||||
|
||||
type SyncMessageFoldersInput = {
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import { Command, CommandRunner } from 'nest-commander';
|
||||
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import {
|
||||
MESSAGING_PROCESS_FOLDER_ACTIONS_CRON_PATTERN,
|
||||
MessagingProcessFolderActionsCronJob,
|
||||
} from 'src/modules/messaging/message-import-manager/crons/jobs/messaging-process-folder-actions.cron.job';
|
||||
|
||||
@Command({
|
||||
name: 'cron:messaging:process-folder-actions',
|
||||
description:
|
||||
'Starts a cron job to process pending folder actions (deletion) for message channels',
|
||||
})
|
||||
export class MessagingProcessFolderActionsCronCommand extends CommandRunner {
|
||||
constructor(
|
||||
@InjectMessageQueue(MessageQueue.cronQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
await this.messageQueueService.addCron<undefined>({
|
||||
jobName: MessagingProcessFolderActionsCronJob.name,
|
||||
data: undefined,
|
||||
options: {
|
||||
repeat: {
|
||||
pattern: MESSAGING_PROCESS_FOLDER_ACTIONS_CRON_PATTERN,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import {
|
||||
MessagingProcessFolderActionsJob,
|
||||
type MessagingProcessFolderActionsJobData,
|
||||
} from 'src/modules/messaging/message-import-manager/jobs/messaging-process-folder-actions.job';
|
||||
|
||||
export const MESSAGING_PROCESS_FOLDER_ACTIONS_CRON_PATTERN = '*/15 * * * *';
|
||||
|
||||
@Processor(MessageQueue.cronQueue)
|
||||
export class MessagingProcessFolderActionsCronJob {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectMessageQueue(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
@Process(MessagingProcessFolderActionsCronJob.name)
|
||||
@SentryCronMonitor(
|
||||
MessagingProcessFolderActionsCronJob.name,
|
||||
MESSAGING_PROCESS_FOLDER_ACTIONS_CRON_PATTERN,
|
||||
)
|
||||
async handle(): Promise<void> {
|
||||
const activeWorkspaces = await this.workspaceRepository.find({
|
||||
where: {
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
for (const activeWorkspace of activeWorkspaces) {
|
||||
try {
|
||||
const schemaName = getWorkspaceSchemaName(activeWorkspace.id);
|
||||
|
||||
const messageChannels = await this.coreDataSource.query(
|
||||
`SELECT DISTINCT mc.id
|
||||
FROM ${schemaName}."messageChannel" mc
|
||||
INNER JOIN ${schemaName}."messageFolder" mf ON mf."messageChannelId" = mc.id
|
||||
WHERE mf."pendingSyncAction" = '${MessageFolderPendingSyncAction.FOLDER_DELETION}'`,
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageQueueService.add<MessagingProcessFolderActionsJobData>(
|
||||
MessagingProcessFolderActionsJob.name,
|
||||
{
|
||||
workspaceId: activeWorkspace.id,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.exceptionHandlerService.captureExceptions([error], {
|
||||
workspace: {
|
||||
id: activeWorkspace.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-3
@@ -17,7 +17,6 @@ import { GmailGetMessageListService } from 'src/modules/messaging/message-import
|
||||
import { GmailGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service';
|
||||
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
|
||||
import { GmailMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-messages-import-error-handler.service';
|
||||
import { GmailNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-network-error-handler.service';
|
||||
import { MessageParticipantManagerModule } from 'src/modules/messaging/message-participant-manager/message-participant-manager.module';
|
||||
|
||||
@Module({
|
||||
@@ -40,14 +39,12 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p
|
||||
GmailFetchByBatchService,
|
||||
GmailGetMessagesService,
|
||||
GmailGetMessageListService,
|
||||
GmailNetworkErrorHandler,
|
||||
GmailMessageListFetchErrorHandler,
|
||||
GmailMessagesImportErrorHandler,
|
||||
],
|
||||
exports: [
|
||||
GmailGetMessagesService,
|
||||
GmailGetMessageListService,
|
||||
GmailNetworkErrorHandler,
|
||||
GmailMessageListFetchErrorHandler,
|
||||
GmailMessagesImportErrorHandler,
|
||||
],
|
||||
|
||||
+27
-153
@@ -1,196 +1,70 @@
|
||||
// Gmail API Error Response Mocks for users.messages.list
|
||||
import { type GmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type';
|
||||
|
||||
const gmailApiErrorMocks = {
|
||||
// 400 Bad Request - Invalid query parameters
|
||||
badRequest: {
|
||||
error: {
|
||||
code: 400,
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
location: 'orderBy',
|
||||
locationType: 'parameter',
|
||||
message:
|
||||
'Sorting is not supported for queries with fullText terms. Results are always in descending relevance order.',
|
||||
reason: 'badRequest',
|
||||
},
|
||||
],
|
||||
message:
|
||||
'Sorting is not supported for queries with fullText terms. Results are always in descending relevance order.',
|
||||
},
|
||||
code: '400',
|
||||
message: 'badRequest',
|
||||
},
|
||||
|
||||
// 400 Invalid Grant
|
||||
invalidGrant: {
|
||||
error: {
|
||||
code: 400,
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'invalid_grant',
|
||||
message: 'Invalid Credentials',
|
||||
},
|
||||
],
|
||||
message: 'Invalid Credentials',
|
||||
},
|
||||
code: '400',
|
||||
message: 'invalid_grant',
|
||||
},
|
||||
|
||||
// 400 Failed Precondition
|
||||
failedPrecondition: {
|
||||
error: {
|
||||
code: 400,
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'failedPrecondition',
|
||||
message: 'Failed Precondition',
|
||||
},
|
||||
],
|
||||
message: 'Failed Precondition',
|
||||
},
|
||||
code: '400',
|
||||
message: 'failedPrecondition',
|
||||
},
|
||||
|
||||
// 401 Invalid Credentials
|
||||
invalidCredentials: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'authError',
|
||||
message: 'Invalid Credentials',
|
||||
locationType: 'header',
|
||||
location: 'Authorization',
|
||||
},
|
||||
],
|
||||
code: 401,
|
||||
message: 'Invalid Credentials',
|
||||
},
|
||||
code: '401',
|
||||
message: 'authError',
|
||||
},
|
||||
|
||||
// 404 Not Found
|
||||
notFound: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'notFound',
|
||||
message: 'Resource not found: userId',
|
||||
location: 'userId',
|
||||
locationType: 'parameter',
|
||||
},
|
||||
],
|
||||
code: 404,
|
||||
message: 'Resource not found: userId',
|
||||
},
|
||||
code: '404',
|
||||
message: 'notFound',
|
||||
},
|
||||
|
||||
// 410 Gone
|
||||
gone: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'resourceGone',
|
||||
message: 'Resource has been deleted',
|
||||
location: 'messageId',
|
||||
locationType: 'parameter',
|
||||
},
|
||||
],
|
||||
code: 410,
|
||||
message: 'Resource has been deleted',
|
||||
},
|
||||
code: '410',
|
||||
message: 'resourceGone',
|
||||
},
|
||||
|
||||
// 403 Daily Limit Exceeded
|
||||
dailyLimitExceeded: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'usageLimits',
|
||||
reason: 'dailyLimitExceeded',
|
||||
message: 'Daily Limit Exceeded',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'Daily Limit Exceeded',
|
||||
},
|
||||
code: '403',
|
||||
message: 'dailyLimitExceeded',
|
||||
},
|
||||
|
||||
// 403 User Rate Limit Exceeded
|
||||
userRateLimitExceeded: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'usageLimits',
|
||||
reason: 'userRateLimitExceeded',
|
||||
message: 'User Rate Limit Exceeded',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'User Rate Limit Exceeded',
|
||||
},
|
||||
code: '403',
|
||||
message: 'userRateLimitExceeded',
|
||||
},
|
||||
|
||||
// 403 Rate Limit Exceeded
|
||||
rateLimitExceeded: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'usageLimits',
|
||||
reason: 'rateLimitExceeded',
|
||||
message: 'Rate Limit Exceeded',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'Rate Limit Exceeded',
|
||||
},
|
||||
code: '403',
|
||||
message: 'rateLimitExceeded',
|
||||
},
|
||||
|
||||
// 403 Domain Policy Error
|
||||
domainPolicyError: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'domainPolicy',
|
||||
message: 'The domain administrators have disabled Gmail apps.',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'The domain administrators have disabled Gmail apps.',
|
||||
},
|
||||
code: '403',
|
||||
message: 'domainPolicy',
|
||||
},
|
||||
|
||||
// 429 Too Many Requests (Concurrent Requests)
|
||||
tooManyConcurrentRequests: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'rateLimitExceeded',
|
||||
message: 'Too many concurrent requests for user',
|
||||
},
|
||||
],
|
||||
code: 429,
|
||||
message: 'Too many concurrent requests for user',
|
||||
},
|
||||
code: '429',
|
||||
message: 'tooManyConcurrentRequests',
|
||||
},
|
||||
|
||||
// 500 Backend Error
|
||||
backendError: {
|
||||
error: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'backendError',
|
||||
message: 'Backend Error',
|
||||
},
|
||||
],
|
||||
code: 500,
|
||||
message: 'Backend Error',
|
||||
},
|
||||
code: '500',
|
||||
message: 'backendError',
|
||||
},
|
||||
|
||||
getError: function (code: number, type?: string) {
|
||||
getError: function (code: number, type?: string): GmailApiError {
|
||||
switch (code) {
|
||||
case 400:
|
||||
switch (type) {
|
||||
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
import { type GmailApiBatchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-batch-error.type';
|
||||
|
||||
const gmailBatchApiErrorMocks = {
|
||||
// 400 Bad Request - Invalid query parameters
|
||||
badRequest: {
|
||||
code: 400,
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
location: 'orderBy',
|
||||
locationType: 'parameter',
|
||||
message:
|
||||
'Sorting is not supported for queries with fullText terms. Results are always in descending relevance order.',
|
||||
reason: 'badRequest',
|
||||
},
|
||||
],
|
||||
message:
|
||||
'Sorting is not supported for queries with fullText terms. Results are always in descending relevance order.',
|
||||
},
|
||||
|
||||
// 400 Invalid Grant
|
||||
invalidGrant: {
|
||||
code: 400,
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'invalid_grant',
|
||||
message: 'Invalid Credentials',
|
||||
},
|
||||
],
|
||||
message: 'Invalid Credentials',
|
||||
},
|
||||
|
||||
// 400 Failed Precondition
|
||||
failedPrecondition: {
|
||||
code: 400,
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'failedPrecondition',
|
||||
message: 'Failed Precondition',
|
||||
},
|
||||
],
|
||||
message: 'Failed Precondition',
|
||||
},
|
||||
|
||||
// 401 Invalid Credentials
|
||||
invalidCredentials: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'authError',
|
||||
message: 'Invalid Credentials',
|
||||
locationType: 'header',
|
||||
location: 'Authorization',
|
||||
},
|
||||
],
|
||||
code: 401,
|
||||
message: 'Invalid Credentials',
|
||||
},
|
||||
|
||||
// 404 Not Found
|
||||
notFound: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'notFound',
|
||||
message: 'Resource not found: userId',
|
||||
location: 'userId',
|
||||
locationType: 'parameter',
|
||||
},
|
||||
],
|
||||
code: 404,
|
||||
message: 'Resource not found: userId',
|
||||
},
|
||||
|
||||
// 410 Gone
|
||||
gone: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'resourceGone',
|
||||
message: 'Resource has been deleted',
|
||||
location: 'messageId',
|
||||
locationType: 'parameter',
|
||||
},
|
||||
],
|
||||
code: 410,
|
||||
message: 'Resource has been deleted',
|
||||
},
|
||||
|
||||
// 403 Daily Limit Exceeded
|
||||
dailyLimitExceeded: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'usageLimits',
|
||||
reason: 'dailyLimitExceeded',
|
||||
message: 'Daily Limit Exceeded',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'Daily Limit Exceeded',
|
||||
},
|
||||
|
||||
// 403 User Rate Limit Exceeded
|
||||
userRateLimitExceeded: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'usageLimits',
|
||||
reason: 'userRateLimitExceeded',
|
||||
message: 'User Rate Limit Exceeded',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'User Rate Limit Exceeded',
|
||||
},
|
||||
|
||||
// 403 Rate Limit Exceeded
|
||||
rateLimitExceeded: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'usageLimits',
|
||||
reason: 'rateLimitExceeded',
|
||||
message: 'Rate Limit Exceeded',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'Rate Limit Exceeded',
|
||||
},
|
||||
|
||||
// 403 Domain Policy Error
|
||||
domainPolicyError: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'domainPolicy',
|
||||
message: 'The domain administrators have disabled Gmail apps.',
|
||||
},
|
||||
],
|
||||
code: 403,
|
||||
message: 'The domain administrators have disabled Gmail apps.',
|
||||
},
|
||||
|
||||
// 429 Too Many Requests (Concurrent Requests)
|
||||
tooManyConcurrentRequests: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'rateLimitExceeded',
|
||||
message: 'Too many concurrent requests for user',
|
||||
},
|
||||
],
|
||||
code: 429,
|
||||
message: 'Too many concurrent requests for user',
|
||||
},
|
||||
|
||||
// 500 Backend Error
|
||||
backendError: {
|
||||
errors: [
|
||||
{
|
||||
domain: 'global',
|
||||
reason: 'backendError',
|
||||
message: 'Backend Error',
|
||||
},
|
||||
],
|
||||
code: 500,
|
||||
message: 'Backend Error',
|
||||
},
|
||||
|
||||
getError: function (code: number, type?: string): GmailApiBatchError {
|
||||
switch (code) {
|
||||
case 400:
|
||||
switch (type) {
|
||||
case 'invalid_grant':
|
||||
return this.invalidGrant;
|
||||
case 'failedPrecondition':
|
||||
return this.failedPrecondition;
|
||||
default:
|
||||
return this.badRequest;
|
||||
}
|
||||
case 401:
|
||||
return this.invalidCredentials;
|
||||
case 403:
|
||||
switch (type) {
|
||||
case 'dailyLimit':
|
||||
return this.dailyLimitExceeded;
|
||||
case 'userRateLimit':
|
||||
return this.userRateLimitExceeded;
|
||||
case 'rateLimit':
|
||||
return this.rateLimitExceeded;
|
||||
case 'domainPolicy':
|
||||
return this.domainPolicyError;
|
||||
default:
|
||||
return this.rateLimitExceeded;
|
||||
}
|
||||
case 404:
|
||||
return this.notFound;
|
||||
case 410:
|
||||
return this.gone;
|
||||
case 429:
|
||||
switch (type) {
|
||||
case 'concurrent':
|
||||
return this.tooManyConcurrentRequests;
|
||||
case 'mailSending':
|
||||
return this.mailSendingLimitExceeded;
|
||||
default:
|
||||
return this.tooManyConcurrentRequests;
|
||||
}
|
||||
case 500:
|
||||
return this.backendError;
|
||||
default:
|
||||
throw new Error(`Unknown error code: ${code}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default gmailBatchApiErrorMocks;
|
||||
+21
-14
@@ -1,26 +1,33 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GmailNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-network-error-handler.service';
|
||||
import { parseGmailMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message-list-fetch-error.util';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util';
|
||||
import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util';
|
||||
import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util';
|
||||
import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util';
|
||||
|
||||
@Injectable()
|
||||
export class GmailMessageListFetchErrorHandler {
|
||||
private readonly logger = new Logger(GmailMessageListFetchErrorHandler.name);
|
||||
|
||||
constructor(
|
||||
private readonly gmailNetworkErrorHandler: GmailNetworkErrorHandler,
|
||||
) {}
|
||||
constructor() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public handleError(error: any): void {
|
||||
this.logger.log(`Error fetching message list`, error);
|
||||
|
||||
const networkError = this.gmailNetworkErrorHandler.handleError(error);
|
||||
|
||||
if (networkError) {
|
||||
throw networkError;
|
||||
public handleError(error: unknown): void {
|
||||
this.logger.error(`Gmail: Error fetching message list: ${error}`);
|
||||
if (isGmailNetworkError(error)) {
|
||||
throw parseGmailNetworkError(error);
|
||||
}
|
||||
|
||||
throw parseGmailMessageListFetchError(error, { cause: error });
|
||||
if (isGmailApiError(error)) {
|
||||
throw parseGmailApiError(error);
|
||||
}
|
||||
|
||||
throw new MessageImportDriverException(
|
||||
'Unknown error',
|
||||
MessageImportDriverExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-18
@@ -1,32 +1,34 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GmailNetworkErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-network-error-handler.service';
|
||||
import { parseGmailMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-messages-import-error.util';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { isGmailApiBatchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-batch-error.util';
|
||||
import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util';
|
||||
import { parseGmailApiBatchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-batch-error.util';
|
||||
import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util';
|
||||
|
||||
@Injectable()
|
||||
export class GmailMessagesImportErrorHandler {
|
||||
private readonly logger = new Logger(GmailMessagesImportErrorHandler.name);
|
||||
|
||||
constructor(
|
||||
private readonly gmailNetworkErrorHandler: GmailNetworkErrorHandler,
|
||||
) {}
|
||||
constructor() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public handleError(error: any, messageExternalId: string): void {
|
||||
this.logger.log(`Error fetching messages`, error);
|
||||
public handleError(error: unknown, messageExternalId: string): void {
|
||||
this.logger.error(`Gmail: Error importing messages: ${error}`);
|
||||
|
||||
const networkError = this.gmailNetworkErrorHandler.handleError(error);
|
||||
|
||||
if (networkError) {
|
||||
throw networkError;
|
||||
if (isGmailNetworkError(error)) {
|
||||
throw parseGmailNetworkError(error);
|
||||
}
|
||||
|
||||
const gmailError = parseGmailMessagesImportError(error, messageExternalId, {
|
||||
cause: error,
|
||||
});
|
||||
|
||||
if (gmailError) {
|
||||
throw gmailError;
|
||||
if (isGmailApiBatchError(error)) {
|
||||
throw parseGmailApiBatchError(error, messageExternalId);
|
||||
}
|
||||
|
||||
throw new MessageImportDriverException(
|
||||
'Unknown error',
|
||||
MessageImportDriverExceptionCode.UNKNOWN,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { isAxiosTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-axios-gaxios-error.util';
|
||||
|
||||
@Injectable()
|
||||
export class GmailNetworkErrorHandler {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public handleError(error: any): MessageImportDriverException | null {
|
||||
if (isAxiosTemporaryError(error)) {
|
||||
return new MessageImportDriverException(
|
||||
error.message,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export type GmailApiBatchError = {
|
||||
code: number;
|
||||
errors: {
|
||||
reason: string;
|
||||
message: string;
|
||||
}[];
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type GmailApiError = {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
+8
-8
@@ -1,45 +1,45 @@
|
||||
import gaxiosErrorMocks from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gaxios-error-mocks';
|
||||
import { isAxiosTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-axios-gaxios-error.util';
|
||||
import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util';
|
||||
|
||||
describe('parseGaxiosError', () => {
|
||||
describe('isGmailNetworkError', () => {
|
||||
it('should return a MessageImportDriverException for ECONNRESET', () => {
|
||||
const error = gaxiosErrorMocks.getError('ECONNRESET');
|
||||
const result = isAxiosTemporaryError(error);
|
||||
const result = isGmailNetworkError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return a MessageImportDriverException for ENOTFOUND', () => {
|
||||
const error = gaxiosErrorMocks.getError('ENOTFOUND');
|
||||
const result = isAxiosTemporaryError(error);
|
||||
const result = isGmailNetworkError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return a MessageImportDriverException for ECONNABORTED', () => {
|
||||
const error = gaxiosErrorMocks.getError('ECONNABORTED');
|
||||
const result = isAxiosTemporaryError(error);
|
||||
const result = isGmailNetworkError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return a MessageImportDriverException for ETIMEDOUT', () => {
|
||||
const error = gaxiosErrorMocks.getError('ETIMEDOUT');
|
||||
const result = isAxiosTemporaryError(error);
|
||||
const result = isGmailNetworkError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return a MessageImportDriverException for ERR_NETWORK', () => {
|
||||
const error = gaxiosErrorMocks.getError('ERR_NETWORK');
|
||||
const result = isAxiosTemporaryError(error);
|
||||
const result = isGmailNetworkError(error);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return undefined for unknown error codes', () => {
|
||||
const error = { code: 'UNKNOWN_ERROR' } as any;
|
||||
const result = isAxiosTemporaryError(error);
|
||||
const result = isGmailNetworkError(error);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
+14
-14
@@ -3,12 +3,12 @@ import {
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import gmailApiErrorMocks from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks';
|
||||
import { parseGmailMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message-list-fetch-error.util';
|
||||
import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util';
|
||||
|
||||
describe('parseGmailMessageListFetchError', () => {
|
||||
describe('parseGmailApiError', () => {
|
||||
it('should handle 400 Bad Request', () => {
|
||||
const error = gmailApiErrorMocks.getError(400);
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
|
||||
@@ -16,7 +16,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 400 Invalid Grant', () => {
|
||||
const error = gmailApiErrorMocks.getError(400, 'invalid_grant');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -26,7 +26,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 400 Failed Precondition', () => {
|
||||
const error = gmailApiErrorMocks.getError(400, 'failedPrecondition');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -36,7 +36,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 401 Invalid Credentials', () => {
|
||||
const error = gmailApiErrorMocks.getError(401);
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -46,7 +46,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 403 Daily Limit Exceeded', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'dailyLimit');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -56,7 +56,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 403 User Rate Limit Exceeded', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'userRateLimit');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -66,7 +66,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 403 Rate Limit Exceeded', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'rateLimit');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -76,7 +76,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 403 Domain Policy Error', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'domainPolicy');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -86,7 +86,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 404 as sync cursor error', () => {
|
||||
const error = gmailApiErrorMocks.getError(404);
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -96,7 +96,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 410 Gone', () => {
|
||||
const error = gmailApiErrorMocks.getError(410);
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
|
||||
@@ -104,7 +104,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 429 Too Many Requests', () => {
|
||||
const error = gmailApiErrorMocks.getError(429, 'concurrent');
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
@@ -114,7 +114,7 @@ describe('parseGmailMessageListFetchError', () => {
|
||||
|
||||
it('should handle 500 Backend Error', () => {
|
||||
const error = gmailApiErrorMocks.getError(500);
|
||||
const exception = parseGmailMessageListFetchError(error.error);
|
||||
const exception = parseGmailApiError(error);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception.code).toBe(
|
||||
|
||||
+38
-74
@@ -2,48 +2,39 @@ import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import gmailApiErrorMocks from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks';
|
||||
import { parseGmailMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-messages-import-error.util';
|
||||
import { default as gmailBatchApiErrorMocks } from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-batch-api-error-mocks';
|
||||
import { parseGmailApiBatchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-batch-error.util';
|
||||
|
||||
const messageExternalId = '123';
|
||||
|
||||
describe('parseGmailMessagesImportError', () => {
|
||||
describe('parseGmailApiBatchError', () => {
|
||||
it('should handle 400 Bad Request', () => {
|
||||
const error = gmailApiErrorMocks.getError(400);
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(400);
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 400 Invalid Grant', () => {
|
||||
const error = gmailApiErrorMocks.getError(400, 'invalid_grant');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(400, 'invalid_grant');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 400 Failed Precondition', () => {
|
||||
const error = gmailApiErrorMocks.getError(400, 'failedPrecondition');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(400, 'failedPrecondition');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
@@ -52,146 +43,119 @@ describe('parseGmailMessagesImportError', () => {
|
||||
});
|
||||
|
||||
it('should handle 401 Invalid Credentials', () => {
|
||||
const error = gmailApiErrorMocks.getError(401);
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(401);
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 403 Daily Limit Exceeded', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'dailyLimit');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(403, 'dailyLimit');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 403 User Rate Limit Exceeded', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'userRateLimit');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(403, 'userRateLimit');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 403 Rate Limit Exceeded', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'rateLimit');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(403, 'rateLimit');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 403 Domain Policy Error', () => {
|
||||
const error = gmailApiErrorMocks.getError(403, 'domainPolicy');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(403, 'domainPolicy');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 404 Not Found', () => {
|
||||
const error = gmailApiErrorMocks.getError(404);
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(404);
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 410 Gone', () => {
|
||||
const error = gmailApiErrorMocks.getError(410);
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(410);
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 429 Too Many Requests', () => {
|
||||
const error = gmailApiErrorMocks.getError(429, 'concurrent');
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(429, 'concurrent');
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle 500 Backend Error', () => {
|
||||
const error = gmailApiErrorMocks.getError(500);
|
||||
const exception = parseGmailMessagesImportError(
|
||||
error.error,
|
||||
messageExternalId,
|
||||
);
|
||||
const error = gmailBatchApiErrorMocks.getError(500);
|
||||
const exception = parseGmailApiBatchError(error, messageExternalId);
|
||||
|
||||
expect(exception).toBeInstanceOf(MessageImportDriverException);
|
||||
expect(exception?.code).toBe(
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
);
|
||||
expect(exception?.message).toBe(
|
||||
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
`${error.errors[0].message} for message with externalId: ${messageExternalId}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { type GmailApiBatchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-batch-error.type';
|
||||
|
||||
export const isGmailApiBatchError = (
|
||||
error: unknown,
|
||||
): error is GmailApiBatchError => {
|
||||
if (error === null || typeof error !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!('code' in error) ||
|
||||
!('errors' in error) ||
|
||||
!Array.isArray(error.errors) ||
|
||||
error.errors.length === 0 ||
|
||||
error.errors.some((error) => !('reason' in error) || !('message' in error))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { type GmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type';
|
||||
|
||||
export const isGmailApiError = (error: unknown): error is GmailApiError => {
|
||||
if (error === null || typeof error !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!('code' in error) ||
|
||||
typeof error.code !== 'string' ||
|
||||
!('message' in error) ||
|
||||
typeof error.message !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
+9
-4
@@ -2,10 +2,16 @@ import { type GaxiosError } from 'gaxios';
|
||||
|
||||
import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception';
|
||||
|
||||
export const isAxiosTemporaryError = (error: GaxiosError): boolean => {
|
||||
const { code } = error;
|
||||
export const isGmailNetworkError = (error: unknown): error is GaxiosError => {
|
||||
if (error === null || typeof error !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
if (!('code' in error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (error.code) {
|
||||
case MessageNetworkExceptionCode.ECONNRESET:
|
||||
case MessageNetworkExceptionCode.ENOTFOUND:
|
||||
case MessageNetworkExceptionCode.ECONNABORTED:
|
||||
@@ -13,7 +19,6 @@ export const isAxiosTemporaryError = (error: GaxiosError): boolean => {
|
||||
case MessageNetworkExceptionCode.ERR_NETWORK:
|
||||
case MessageNetworkExceptionCode.EHOSTUNREACH:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
+4
-23
@@ -2,17 +2,11 @@ import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { type GmailApiBatchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-batch-error.type';
|
||||
|
||||
export const parseGmailMessagesImportError = (
|
||||
error: {
|
||||
code?: number;
|
||||
errors: {
|
||||
reason: string;
|
||||
message: string;
|
||||
}[];
|
||||
},
|
||||
messageExternalId: string,
|
||||
options?: { cause?: Error },
|
||||
export const parseGmailApiBatchError = (
|
||||
error: GmailApiBatchError,
|
||||
messageExternalId?: string,
|
||||
): MessageImportDriverException | undefined => {
|
||||
const { code, errors } = error;
|
||||
|
||||
@@ -26,7 +20,6 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
if (reason === 'failedPrecondition') {
|
||||
@@ -34,21 +27,18 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.UNKNOWN,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
|
||||
case 404:
|
||||
@@ -56,14 +46,12 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
|
||||
case 429:
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
|
||||
case 403:
|
||||
@@ -75,14 +63,12 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
if (reason === 'domainPolicy') {
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -92,14 +78,12 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
|
||||
case 503:
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
|
||||
case 500:
|
||||
@@ -109,7 +93,6 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +100,6 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
`${code} - ${reason} - ${message}`,
|
||||
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -129,6 +111,5 @@ export const parseGmailMessagesImportError = (
|
||||
return new MessageImportDriverException(
|
||||
message,
|
||||
MessageImportDriverExceptionCode.UNKNOWN,
|
||||
{ cause: options?.cause },
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user