Skip to content

Commit e3536cf

Browse files
Merging Next Into --> Main (#1065)
2 parents 9c5958e + 40bebdc commit e3536cf

File tree

78 files changed

+913
-465
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+913
-465
lines changed

apps/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@impler/api",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"author": "implerhq",
55
"license": "MIT",
66
"private": true,

apps/api/src/app/column/dtos/column-request.dto.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Type } from 'class-transformer';
1515

1616
import { ValidationTypesEnum } from '@impler/client';
1717
import { IsValidRegex } from '@shared/framework/is-valid-regex.validator';
18+
import { IsValidDigitsConstraint } from '@shared/framework/is-valid-digits.validator';
1819
import { IsNumberOrString } from '@shared/framework/number-or-string.validator';
1920
import { ColumnDelimiterEnum, ColumnTypesEnum, Defaults } from '@impler/shared';
2021

@@ -36,17 +37,25 @@ export class ValidationDto {
3637
errorMessage?: string;
3738

3839
@ApiPropertyOptional({
39-
description: 'Minimum value',
40+
description: 'Minimum value for digit validation',
4041
})
4142
@IsNumber()
4243
@IsOptional()
44+
@ValidateIf((object) => object.validate === ValidationTypesEnum.DIGITS)
45+
@Validate(IsValidDigitsConstraint, {
46+
message: 'Invalid number of digits',
47+
})
4348
min?: number;
4449

4550
@ApiPropertyOptional({
46-
description: 'Maximum value',
51+
description: 'Maximum value for digit validation',
4752
})
4853
@IsNumber()
4954
@IsOptional()
55+
@ValidateIf((object) => object.validate === ValidationTypesEnum.DIGITS)
56+
@Validate(IsValidDigitsConstraint, {
57+
message: 'Invalid number of digits',
58+
})
5059
max?: number;
5160

5261
@ApiPropertyOptional({

apps/api/src/app/review/usecases/do-review/base-review.usecase.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable multiline-comment-style */
12
import * as fs from 'fs';
23
import * as dayjs from 'dayjs';
34
import * as Papa from 'papaparse';
@@ -10,7 +11,7 @@ import Ajv, { AnySchemaObject, ErrorObject, ValidateFunction } from 'ajv';
1011
import { ValidationErrorMessages } from '@shared/types/review.types';
1112
import { ColumnTypesEnum, Defaults, ITemplateSchemaItem } from '@impler/shared';
1213
import { SManager, BATCH_LIMIT, MAIN_CODE, ExecuteIsolateResult } from '@shared/services/sandbox';
13-
import { ValidationTypesEnum, LengthValidationType, RangeValidationType } from '@impler/client';
14+
import { ValidationTypesEnum, LengthValidationType, RangeValidationType, DigitsValidationType } from '@impler/client';
1415

1516
dayjs.extend(customParseFormat);
1617

@@ -112,6 +113,9 @@ export class BaseReview {
112113
const lengthValidation = column.validations?.find(
113114
(validation) => validation.validate === ValidationTypesEnum.LENGTH
114115
) as LengthValidationType;
116+
const digitsValidation = column.validations?.find(
117+
(validation) => validation.validate === ValidationTypesEnum.DIGITS
118+
) as DigitsValidationType;
115119

116120
switch (column.type) {
117121
case ColumnTypesEnum.STRING:
@@ -122,15 +126,31 @@ export class BaseReview {
122126
};
123127
break;
124128
case ColumnTypesEnum.NUMBER:
125-
case ColumnTypesEnum.DOUBLE:
129+
case ColumnTypesEnum.DOUBLE: {
130+
const isInteger = column.type === ColumnTypesEnum.NUMBER;
131+
126132
property = {
127-
...(column.type === ColumnTypesEnum.NUMBER && { multipleOf: 1 }),
128133
type: ['number', 'null'],
129134
...(!column.isRequired && { default: null }),
135+
136+
// only enforce integer for NUMBER
137+
//...(isInteger && { multipleOf: 1 }),
138+
139+
// normal range validation (applies to both)
130140
...(typeof rangeValidation?.min === 'number' && { minimum: rangeValidation?.min }),
131141
...(typeof rangeValidation?.max === 'number' && { maximum: rangeValidation?.max }),
142+
143+
// digit validation (only for NUMBER)
144+
...(isInteger &&
145+
digitsValidation && {
146+
digitCount: {
147+
...(typeof digitsValidation.min === 'number' && { min: digitsValidation.min }),
148+
...(typeof digitsValidation.max === 'number' && { max: digitsValidation.max }),
149+
},
150+
}),
132151
};
133152
break;
153+
}
134154
case ColumnTypesEnum.SELECT:
135155
case ColumnTypesEnum.IMAGE:
136156
const selectValues =
@@ -263,6 +283,30 @@ export class BaseReview {
263283
validationErrorMessages?.[field]?.[ValidationTypesEnum.RANGE] ||
264284
`${String(data)} must be greater than or equal to ${error.params.limit}`;
265285
break;
286+
case error.keyword === 'digitCount': {
287+
const customMessage = validationErrorMessages?.[field]?.[ValidationTypesEnum.DIGITS];
288+
if (customMessage) {
289+
message = customMessage;
290+
} else {
291+
const minDigits = error.parentSchema.digitCount.min;
292+
const maxDigits = error.parentSchema.digitCount.max;
293+
294+
if (minDigits !== undefined && maxDigits !== undefined) {
295+
if (minDigits === maxDigits) {
296+
message = `Must have exactly ${minDigits} digits`;
297+
} else {
298+
message = `Must have between ${minDigits} and ${maxDigits} digits`;
299+
}
300+
} else if (minDigits !== undefined) {
301+
message = `Must have at least ${minDigits} digits`;
302+
} else if (maxDigits !== undefined) {
303+
message = `Must have at most ${maxDigits} digits`;
304+
} else {
305+
message = 'Invalid number of digits';
306+
}
307+
}
308+
break;
309+
}
266310
// empty string case
267311
case error.keyword === 'emptyCheck':
268312
case error.keyword === 'required':
@@ -548,6 +592,25 @@ export class BaseReview {
548592
},
549593
});
550594

595+
ajv.addKeyword({
596+
keyword: 'digitCount',
597+
type: 'number',
598+
schemaType: 'object',
599+
// schema = { min: number, max: number }
600+
validate: (schema: { min?: number; max?: number }, data: number) => {
601+
if (data === null || data === undefined) return true;
602+
603+
// Count digits (ignore sign and decimal part)
604+
const digits = Math.floor(Math.log10(Math.abs(data))) + 1;
605+
606+
if (schema.min !== undefined && digits < schema.min) return false;
607+
if (schema.max !== undefined && digits > schema.max) return false;
608+
609+
return true;
610+
},
611+
errors: true,
612+
});
613+
551614
const valuesMap = new Map();
552615
Object.keys(uniqueCombinations).forEach((keyword) => {
553616
valuesMap.set(keyword, new Set());
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable multiline-comment-style */
2+
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
3+
4+
@ValidatorConstraint({ name: 'isValidDigits', async: false })
5+
export class IsValidDigitsConstraint implements ValidatorConstraintInterface {
6+
validate(value: any, args: ValidationArguments) {
7+
if (value === undefined || value === null || value === '') {
8+
return true;
9+
}
10+
11+
const strValue = String(value).replace(/[^0-9]/g, '');
12+
13+
if (strValue === '' || isNaN(Number(strValue))) {
14+
return false;
15+
}
16+
17+
const validation = args.object as any;
18+
const numDigits = strValue.length;
19+
20+
// if (validation.min !== undefined && numDigits < validation.min) {
21+
// console.log(`Validation failed: Number has fewer than ${validation.min} digits`);
22+
// return false;
23+
// }
24+
25+
if (validation.max !== undefined && numDigits > validation.max) {
26+
return false;
27+
}
28+
29+
return true;
30+
}
31+
32+
defaultMessage(args: ValidationArguments) {
33+
const validation = args.object as any;
34+
35+
if (validation.min !== undefined && validation.max !== undefined) {
36+
return `Number must have between ${validation.min} and ${validation.max} digits`;
37+
} else if (validation.min !== undefined) {
38+
return `Number must have at least ${validation.min} digits`;
39+
} else if (validation.max !== undefined) {
40+
return `Number must have at most ${validation.max} digits`;
41+
}
42+
43+
return 'Invalid number of digits';
44+
}
45+
}

apps/api/src/app/template/usecases/update-template-columns/update-template-columns.usecase.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { APIMessages } from '@shared/constants';
44
import { PaymentAPIService } from '@impler/services';
55
import { UpdateImageColumns, SaveSampleFile } from '@shared/usecases';
66
import { AVAILABLE_BILLABLEMETRIC_CODE_ENUM, ColumnTypesEnum } from '@impler/shared';
7-
import { ColumnRepository, CustomizationRepository, TemplateRepository } from '@impler/dal';
7+
import { ColumnEntity, ColumnRepository, CustomizationRepository, TemplateRepository } from '@impler/dal';
88
import { AddColumnCommand } from 'app/column/commands/add-column.command';
99
import { UniqueColumnException } from '@shared/exceptions/unique-column.exception';
1010
import { UpdateCustomization } from '../update-customization/update-customization.usecase';
@@ -25,10 +25,19 @@ export class UpdateTemplateColumns {
2525
async execute(userColumns: AddColumnCommand[], _templateId: string, email: string) {
2626
await this.checkSchema(userColumns, email);
2727

28+
// eslint-disable-next-line prefer-const
29+
let userInitialColumns: ColumnEntity[] = await this.columnRepository.find({ _templateId });
2830
await this.columnRepository.deleteMany({ _templateId });
2931
userColumns.forEach((column, index) => {
32+
const existingUserColumns = userInitialColumns.find((col: ColumnEntity) => col.key === column.key);
33+
3034
column.sequence = index;
3135
column.dateFormats = column.dateFormats?.map((format) => format.toUpperCase()) || [];
36+
column.isRequired = existingUserColumns?.isRequired || false;
37+
column.isUnique = existingUserColumns?.isUnique || false;
38+
column.selectValues = existingUserColumns?.selectValues || [];
39+
column.dateFormats = existingUserColumns?.dateFormats || [];
40+
column.validations = existingUserColumns?.validations || [];
3241
});
3342
const columns = await this.columnRepository.createMany(userColumns);
3443
await this.saveSampleFile.execute(columns, _templateId);

apps/queue-manager/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ RUN cp src/.env.production dist/.env.production
3131

3232
EXPOSE 3000
3333

34-
CMD [ "pm2-runtime", "/usr/src/app/apps/queue-manager/dist/index.js" ]
34+
CMD [ "pm2-runtime", "/usr/src/app/apps/queue-manager/dist/index.js" ]

apps/queue-manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@impler/queue-manager",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"author": "implerhq",
55
"license": "MIT",
66
"private": true,

apps/web/.storybook/preview.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@ import { themes } from '@storybook/theming';
33
import { MantineProvider, ColorSchemeProvider } from '@mantine/core';
44
// @ts-ignore
55
import { mantineConfig } from '@config';
6-
import { useDarkMode } from 'storybook-dark-mode';
76

87
function ThemeWrapper(props: { children: React.ReactNode }) {
9-
const colorScheme = useDarkMode() ? 'dark' : 'light';
10-
118
return (
12-
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={() => {}}>
13-
<MantineProvider theme={{ ...mantineConfig, colorScheme }} withGlobalStyles withNormalizeCSS>
9+
<ColorSchemeProvider colorScheme={'dark'} toggleColorScheme={() => {}}>
10+
<MantineProvider theme={{ ...mantineConfig, colorScheme: 'dark' }} withGlobalStyles withNormalizeCSS>
1411
{props.children}
1512
</MantineProvider>
1613
</ColorSchemeProvider>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { IconType } from '@types';
2+
import { IconSizes } from 'config';
3+
4+
export const DrawerLeftCloseIcon = ({ size = 'sm' }: IconType) => {
5+
return (
6+
<svg width={IconSizes[size]} height={IconSizes[size]} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
7+
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
8+
<rect width="18" height="18" x="3" y="3" rx="2" />
9+
<path d="M9 3v18m7-6l-3-3l3-3" />
10+
</g>
11+
</svg>
12+
);
13+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { IconType } from '@types';
2+
import { IconSizes } from 'config';
3+
4+
export const DrawerRightOpenIcon = ({ size = 'sm' }: IconType) => {
5+
return (
6+
<svg
7+
width={IconSizes[size]}
8+
height={IconSizes[size]}
9+
viewBox="0 0 24 24"
10+
fill="none"
11+
xmlns="http://www.w3.org/2000/svg"
12+
>
13+
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
14+
<rect width="18" height="18" x="3" y="3" rx="2" />
15+
<path d="M15 3v18M8 9l3 3l-3 3" />
16+
</g>
17+
</svg>
18+
);
19+
};

0 commit comments

Comments
 (0)