feat: better server lint (#2850)
* feat: add stylistic eslint plugin * feat: add missing line return * feat: secure line-break style * feat: disallow break before else * feat: line between class members * feat: better new line lint rule
This commit is contained in:
@ -5,7 +5,7 @@ module.exports = {
|
|||||||
tsconfigRootDir : __dirname,
|
tsconfigRootDir : __dirname,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
plugins: ['@typescript-eslint/eslint-plugin', 'import', 'unused-imports'],
|
plugins: ['@typescript-eslint/eslint-plugin', 'import', 'unused-imports', '@stylistic'],
|
||||||
extends: [
|
extends: [
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
@ -77,5 +77,16 @@ module.exports = {
|
|||||||
'import/no-duplicates': ["error", {"considerQueryString": true}],
|
'import/no-duplicates': ["error", {"considerQueryString": true}],
|
||||||
'unused-imports/no-unused-imports': 'warn',
|
'unused-imports/no-unused-imports': 'warn',
|
||||||
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "no-type-imports" }],
|
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "no-type-imports" }],
|
||||||
|
"@stylistic/linebreak-style": ["error", "unix"],
|
||||||
|
"@stylistic/lines-between-class-members": ["error", { "enforce": [
|
||||||
|
{ blankLine: "always", prev: "method", next: "method" }
|
||||||
|
]}],
|
||||||
|
"@stylistic/padding-line-between-statements": [
|
||||||
|
"error",
|
||||||
|
{ blankLine: "always", prev: "*", next: "return" },
|
||||||
|
{ blankLine: "always", prev: ["const", "let", "var"], next: "*"},
|
||||||
|
{ blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] },
|
||||||
|
{ blankLine: "always", prev: "*", next: ["interface", "type"] }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all"
|
"trailingComma": "all",
|
||||||
}
|
"brakeBeforeElse": false
|
||||||
|
}
|
||||||
|
|||||||
@ -106,6 +106,7 @@
|
|||||||
"@nestjs/cli": "^9.0.0",
|
"@nestjs/cli": "^9.0.0",
|
||||||
"@nestjs/schematics": "^9.0.0",
|
"@nestjs/schematics": "^9.0.0",
|
||||||
"@nestjs/testing": "^9.0.0",
|
"@nestjs/testing": "^9.0.0",
|
||||||
|
"@stylistic/eslint-plugin": "^1.5.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bytes": "^3.1.1",
|
"@types/bytes": "^3.1.1",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
|
|||||||
@ -69,6 +69,7 @@ import { ExceptionFilter } from './filters/exception.filter';
|
|||||||
);
|
);
|
||||||
|
|
||||||
const contextId = ContextIdFactory.create();
|
const contextId = ContextIdFactory.create();
|
||||||
|
|
||||||
AppModule.moduleRef.registerRequestByContextId(request, contextId);
|
AppModule.moduleRef.registerRequestByContextId(request, contextId);
|
||||||
|
|
||||||
// Get the SchemaGenerationService from the AppModule
|
// Get the SchemaGenerationService from the AppModule
|
||||||
|
|||||||
@ -11,24 +11,28 @@ export class ApiRestController {
|
|||||||
@Get()
|
@Get()
|
||||||
async handleApiGet(@Req() request: Request): Promise<object> {
|
async handleApiGet(@Req() request: Request): Promise<object> {
|
||||||
const result = await this.apiRestService.get(request);
|
const result = await this.apiRestService.get(request);
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
async handleApiDelete(@Req() request: Request): Promise<object> {
|
async handleApiDelete(@Req() request: Request): Promise<object> {
|
||||||
const result = await this.apiRestService.delete(request);
|
const result = await this.apiRestService.delete(request);
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async handleApiPost(@Req() request: Request): Promise<object> {
|
async handleApiPost(@Req() request: Request): Promise<object> {
|
||||||
const result = await this.apiRestService.create(request);
|
const result = await this.apiRestService.create(request);
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put()
|
@Put()
|
||||||
async handleApiPut(@Req() request: Request): Promise<object> {
|
async handleApiPut(@Req() request: Request): Promise<object> {
|
||||||
const result = await this.apiRestService.update(request);
|
const result = await this.apiRestService.update(request);
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { EnvironmentService } from 'src/integrations/environment/environment.ser
|
|||||||
describe('ApiRestService', () => {
|
describe('ApiRestService', () => {
|
||||||
let service: ApiRestService;
|
let service: ApiRestService;
|
||||||
const objectMetadataItem = { fields: [{ name: 'field', type: 'NUMBER' }] };
|
const objectMetadataItem = { fields: [{ name: 'field', type: 'NUMBER' }] };
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@ -24,6 +24,7 @@ enum FILTER_COMPARATORS {
|
|||||||
const ALLOWED_DEPTH_VALUES = [1, 2];
|
const ALLOWED_DEPTH_VALUES = [1, 2];
|
||||||
const DEFAULT_DEPTH_VALUE = 2;
|
const DEFAULT_DEPTH_VALUE = 2;
|
||||||
const DEFAULT_ORDER_DIRECTION = OrderByDirection.AscNullsFirst;
|
const DEFAULT_ORDER_DIRECTION = OrderByDirection.AscNullsFirst;
|
||||||
|
|
||||||
enum CONJUNCTIONS {
|
enum CONJUNCTIONS {
|
||||||
or = 'or',
|
or = 'or',
|
||||||
and = 'and',
|
and = 'and',
|
||||||
@ -275,16 +276,19 @@ export class ApiRestService {
|
|||||||
const [objectMetadata] = objectMetadataItems.filter(
|
const [objectMetadata] = objectMetadataItems.filter(
|
||||||
(object) => object.namePlural === parsedObject,
|
(object) => object.namePlural === parsedObject,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!objectMetadata) {
|
if (!objectMetadata) {
|
||||||
const [wrongObjectMetadata] = objectMetadataItems.filter(
|
const [wrongObjectMetadata] = objectMetadataItems.filter(
|
||||||
(object) => object.nameSingular === parsedObject,
|
(object) => object.nameSingular === parsedObject,
|
||||||
);
|
);
|
||||||
let hint = 'eg: companies';
|
let hint = 'eg: companies';
|
||||||
|
|
||||||
if (wrongObjectMetadata) {
|
if (wrongObjectMetadata) {
|
||||||
hint = `Did you mean '${wrongObjectMetadata.namePlural}'?`;
|
hint = `Did you mean '${wrongObjectMetadata.namePlural}'?`;
|
||||||
}
|
}
|
||||||
throw Error(`object '${parsedObject}' not found. ${hint}`);
|
throw Error(`object '${parsedObject}' not found. ${hint}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem: objectMetadata,
|
objectMetadataItem: objectMetadata,
|
||||||
@ -295,6 +299,7 @@ export class ApiRestService {
|
|||||||
if (!(filterQuery.includes('(') && filterQuery.includes(')'))) {
|
if (!(filterQuery.includes('(') && filterQuery.includes(')'))) {
|
||||||
return `${DEFAULT_FILTER_CONJUNCTION}(${filterQuery})`;
|
return `${DEFAULT_FILTER_CONJUNCTION}(${filterQuery})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterQuery;
|
return filterQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,6 +307,7 @@ export class ApiRestService {
|
|||||||
const countOpenedBrackets = (filterQuery.match(/\(/g) || []).length;
|
const countOpenedBrackets = (filterQuery.match(/\(/g) || []).length;
|
||||||
const countClosedBrackets = (filterQuery.match(/\)/g) || []).length;
|
const countClosedBrackets = (filterQuery.match(/\)/g) || []).length;
|
||||||
const diff = countOpenedBrackets - countClosedBrackets;
|
const diff = countOpenedBrackets - countClosedBrackets;
|
||||||
|
|
||||||
if (diff !== 0) {
|
if (diff !== 0) {
|
||||||
const hint =
|
const hint =
|
||||||
diff > 0
|
diff > 0
|
||||||
@ -309,8 +315,10 @@ export class ApiRestService {
|
|||||||
: `${Math.abs(diff)} close bracket${
|
: `${Math.abs(diff)} close bracket${
|
||||||
Math.abs(diff) > 1 ? 's are' : ' is'
|
Math.abs(diff) > 1 ? 's are' : ' is'
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
throw Error(`'filter' invalid. ${hint} missing in the query`);
|
throw Error(`'filter' invalid. ${hint} missing in the query`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +326,7 @@ export class ApiRestService {
|
|||||||
let parenthesisCounter = 0;
|
let parenthesisCounter = 0;
|
||||||
const predicates: string[] = [];
|
const predicates: string[] = [];
|
||||||
let currentPredicates = '';
|
let currentPredicates = '';
|
||||||
|
|
||||||
for (const c of filterQuery) {
|
for (const c of filterQuery) {
|
||||||
if (c === '(') {
|
if (c === '(') {
|
||||||
parenthesisCounter++;
|
parenthesisCounter++;
|
||||||
@ -337,6 +346,7 @@ export class ApiRestService {
|
|||||||
if (currentPredicates.length) {
|
if (currentPredicates.length) {
|
||||||
predicates.push(currentPredicates);
|
predicates.push(currentPredicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
return predicates;
|
return predicates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,11 +355,13 @@ export class ApiRestService {
|
|||||||
const match = filterQuery.match(
|
const match = filterQuery.match(
|
||||||
`^(${Object.values(CONJUNCTIONS).join('|')})((.+))$`,
|
`^(${Object.values(CONJUNCTIONS).join('|')})((.+))$`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const conjunction = match[1];
|
const conjunction = match[1];
|
||||||
const subResult = this.parseFilterQueryContent(filterQuery).map((elem) =>
|
const subResult = this.parseFilterQueryContent(filterQuery).map((elem) =>
|
||||||
this.parseStringFilter(elem, objectMetadataItem),
|
this.parseStringFilter(elem, objectMetadataItem),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (conjunction === CONJUNCTIONS.not) {
|
if (conjunction === CONJUNCTIONS.not) {
|
||||||
if (subResult.length > 1) {
|
if (subResult.length > 1) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -360,8 +372,10 @@ export class ApiRestService {
|
|||||||
} else {
|
} else {
|
||||||
result[conjunction] = subResult;
|
result[conjunction] = subResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.parseSimpleFilter(filterQuery, objectMetadataItem);
|
return this.parseSimpleFilter(filterQuery, objectMetadataItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,6 +390,7 @@ export class ApiRestService {
|
|||||||
}
|
}
|
||||||
const [fieldAndComparator, value] = filterString.split(':');
|
const [fieldAndComparator, value] = filterString.split(':');
|
||||||
const [field, comparator] = fieldAndComparator.replace(']', '').split('[');
|
const [field, comparator] = fieldAndComparator.replace(']', '').split('[');
|
||||||
|
|
||||||
if (!Object.keys(FILTER_COMPARATORS).includes(comparator)) {
|
if (!Object.keys(FILTER_COMPARATORS).includes(comparator)) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`'filter' invalid for '${filterString}', comparator ${comparator} not in ${Object.keys(
|
`'filter' invalid for '${filterString}', comparator ${comparator} not in ${Object.keys(
|
||||||
@ -384,9 +399,11 @@ export class ApiRestService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const fields = field.split('.');
|
const fields = field.split('.');
|
||||||
|
|
||||||
this.checkFields(objectMetadataItem, fields, 'filter');
|
this.checkFields(objectMetadataItem, fields, 'filter');
|
||||||
const fieldType = this.getFieldType(objectMetadataItem, fields[0]);
|
const fieldType = this.getFieldType(objectMetadataItem, fields[0]);
|
||||||
const formattedValue = this.formatFieldValue(value, fieldType);
|
const formattedValue = this.formatFieldValue(value, fieldType);
|
||||||
|
|
||||||
return fields.reverse().reduce(
|
return fields.reverse().reduce(
|
||||||
(acc, currentValue) => {
|
(acc, currentValue) => {
|
||||||
return { [currentValue]: acc };
|
return { [currentValue]: acc };
|
||||||
@ -402,35 +419,42 @@ export class ApiRestService {
|
|||||||
if (fieldType === 'BOOLEAN') {
|
if (fieldType === 'BOOLEAN') {
|
||||||
return value.toLowerCase() === 'true';
|
return value.toLowerCase() === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseFilter(request, objectMetadataItem) {
|
parseFilter(request, objectMetadataItem) {
|
||||||
const parsedObjectId = this.parseObject(request)[1];
|
const parsedObjectId = this.parseObject(request)[1];
|
||||||
|
|
||||||
if (parsedObjectId) {
|
if (parsedObjectId) {
|
||||||
return { id: { eq: parsedObjectId } };
|
return { id: { eq: parsedObjectId } };
|
||||||
}
|
}
|
||||||
const rawFilterQuery = request.query.filter;
|
const rawFilterQuery = request.query.filter;
|
||||||
|
|
||||||
if (typeof rawFilterQuery !== 'string') {
|
if (typeof rawFilterQuery !== 'string') {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
this.checkFilterQuery(rawFilterQuery);
|
this.checkFilterQuery(rawFilterQuery);
|
||||||
const filterQuery = this.addDefaultConjunctionIfMissing(rawFilterQuery);
|
const filterQuery = this.addDefaultConjunctionIfMissing(rawFilterQuery);
|
||||||
|
|
||||||
return this.parseStringFilter(filterQuery, objectMetadataItem);
|
return this.parseStringFilter(filterQuery, objectMetadataItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseOrderBy(request, objectMetadataItem) {
|
parseOrderBy(request, objectMetadataItem) {
|
||||||
//?order_by=field_1[AscNullsFirst],field_2[DescNullsLast],field_3
|
//?order_by=field_1[AscNullsFirst],field_2[DescNullsLast],field_3
|
||||||
const orderByQuery = request.query.order_by;
|
const orderByQuery = request.query.order_by;
|
||||||
|
|
||||||
if (typeof orderByQuery !== 'string') {
|
if (typeof orderByQuery !== 'string') {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const orderByItems = orderByQuery.split(',');
|
const orderByItems = orderByQuery.split(',');
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
for (const orderByItem of orderByItems) {
|
for (const orderByItem of orderByItems) {
|
||||||
// orderByItem -> field_1[AscNullsFirst]
|
// orderByItem -> field_1[AscNullsFirst]
|
||||||
if (orderByItem.includes('[') && orderByItem.includes(']')) {
|
if (orderByItem.includes('[') && orderByItem.includes(']')) {
|
||||||
const [field, direction] = orderByItem.replace(']', '').split('[');
|
const [field, direction] = orderByItem.replace(']', '').split('[');
|
||||||
|
|
||||||
// field -> field_1 ; direction -> AscNullsFirst
|
// field -> field_1 ; direction -> AscNullsFirst
|
||||||
if (!(direction in OrderByDirection)) {
|
if (!(direction in OrderByDirection)) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -448,6 +472,7 @@ export class ApiRestService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.checkFields(objectMetadataItem, Object.keys(result), 'order_by');
|
this.checkFields(objectMetadataItem, Object.keys(result), 'order_by');
|
||||||
|
|
||||||
return <RecordOrderBy>result;
|
return <RecordOrderBy>result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,21 +507,26 @@ export class ApiRestService {
|
|||||||
|
|
||||||
parseLimit(request) {
|
parseLimit(request) {
|
||||||
const limitQuery = request.query.limit;
|
const limitQuery = request.query.limit;
|
||||||
|
|
||||||
if (typeof limitQuery !== 'string') {
|
if (typeof limitQuery !== 'string') {
|
||||||
return 60;
|
return 60;
|
||||||
}
|
}
|
||||||
const limitParsed = parseInt(limitQuery);
|
const limitParsed = parseInt(limitQuery);
|
||||||
|
|
||||||
if (!Number.isInteger(limitParsed)) {
|
if (!Number.isInteger(limitParsed)) {
|
||||||
throw Error(`limit '${limitQuery}' is invalid. Should be an integer`);
|
throw Error(`limit '${limitQuery}' is invalid. Should be an integer`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return limitParsed;
|
return limitParsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseCursor(request) {
|
parseCursor(request) {
|
||||||
const cursorQuery = request.query.last_cursor;
|
const cursorQuery = request.query.last_cursor;
|
||||||
|
|
||||||
if (typeof cursorQuery !== 'string') {
|
if (typeof cursorQuery !== 'string') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cursorQuery;
|
return cursorQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,6 +541,7 @@ export class ApiRestService {
|
|||||||
|
|
||||||
parseObject(request) {
|
parseObject(request) {
|
||||||
const queryAction = request.path.replace('/rest/', '').split('/');
|
const queryAction = request.path.replace('/rest/', '').split('/');
|
||||||
|
|
||||||
if (queryAction.length > 2) {
|
if (queryAction.length > 2) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies`,
|
`Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies`,
|
||||||
@ -519,14 +550,17 @@ export class ApiRestService {
|
|||||||
if (queryAction.length === 1) {
|
if (queryAction.length === 1) {
|
||||||
return [queryAction[0], undefined];
|
return [queryAction[0], undefined];
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryAction;
|
return queryAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractWorkspaceId(request: Request) {
|
extractWorkspaceId(request: Request) {
|
||||||
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw Error('missing authentication token');
|
throw Error('missing authentication token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return verify(token, this.environmentService.getAccessTokenSecret())[
|
return verify(token, this.environmentService.getAccessTokenSecret())[
|
||||||
'workspaceId'
|
'workspaceId'
|
||||||
];
|
];
|
||||||
@ -537,6 +571,7 @@ export class ApiRestService {
|
|||||||
typeof request.query.depth === 'string'
|
typeof request.query.depth === 'string'
|
||||||
? parseInt(request.query.depth)
|
? parseInt(request.query.depth)
|
||||||
: DEFAULT_DEPTH_VALUE;
|
: DEFAULT_DEPTH_VALUE;
|
||||||
|
|
||||||
if (!ALLOWED_DEPTH_VALUES.includes(depth)) {
|
if (!ALLOWED_DEPTH_VALUES.includes(depth)) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`'depth=${depth}' parameter invalid. Allowed values are ${ALLOWED_DEPTH_VALUES.join(
|
`'depth=${depth}' parameter invalid. Allowed values are ${ALLOWED_DEPTH_VALUES.join(
|
||||||
@ -544,6 +579,7 @@ export class ApiRestService {
|
|||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return depth;
|
return depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,6 +619,7 @@ export class ApiRestService {
|
|||||||
objectMetadata.objectMetadataItem,
|
objectMetadata.objectMetadataItem,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.callGraphql(request, data);
|
return await this.callGraphql(request, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { data: { error: `${err}` } };
|
return { data: { error: `${err}` } };
|
||||||
@ -593,6 +630,7 @@ export class ApiRestService {
|
|||||||
try {
|
try {
|
||||||
const objectMetadata = await this.getObjectMetadata(request);
|
const objectMetadata = await this.getObjectMetadata(request);
|
||||||
const id = this.parseObject(request)[1];
|
const id = this.parseObject(request)[1];
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
@ -606,6 +644,7 @@ export class ApiRestService {
|
|||||||
id: this.parseObject(request)[1],
|
id: this.parseObject(request)[1],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.callGraphql(request, data);
|
return await this.callGraphql(request, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { data: { error: `${err}` } };
|
return { data: { error: `${err}` } };
|
||||||
@ -626,6 +665,7 @@ export class ApiRestService {
|
|||||||
data: request.body,
|
data: request.body,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.callGraphql(request, data);
|
return await this.callGraphql(request, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { data: { error: `${err}` } };
|
return { data: { error: `${err}` } };
|
||||||
@ -637,6 +677,7 @@ export class ApiRestService {
|
|||||||
const objectMetadata = await this.getObjectMetadata(request);
|
const objectMetadata = await this.getObjectMetadata(request);
|
||||||
const depth = this.computeDepth(request);
|
const depth = this.computeDepth(request);
|
||||||
const id = this.parseObject(request)[1];
|
const id = this.parseObject(request)[1];
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
@ -655,6 +696,7 @@ export class ApiRestService {
|
|||||||
data: request.body,
|
data: request.body,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.callGraphql(request, data);
|
return await this.callGraphql(request, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { data: { error: `${err}` } };
|
return { data: { error: `${err}` } };
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export class AuthResolver {
|
|||||||
const { exists } = await this.authService.checkUserExists(
|
const { exists } = await this.authService.checkUserExists(
|
||||||
checkUserExistsInput.email,
|
checkUserExistsInput.email,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { exists };
|
return { exists };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,14 @@ import { GoogleStrategy } from 'src/core/auth/strategies/google.auth.strategy';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class GoogleProviderEnabledGuard implements CanActivate {
|
export class GoogleProviderEnabledGuard implements CanActivate {
|
||||||
constructor(private readonly environmentService: EnvironmentService) {}
|
constructor(private readonly environmentService: EnvironmentService) {}
|
||||||
|
|
||||||
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
if (!this.environmentService.isAuthGoogleEnabled()) {
|
if (!this.environmentService.isAuthGoogleEnabled()) {
|
||||||
throw new NotFoundException('Google auth is not enabled');
|
throw new NotFoundException('Google auth is not enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
new GoogleStrategy(this.environmentService);
|
new GoogleStrategy(this.environmentService);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,10 +89,12 @@ export class AuthService {
|
|||||||
const existingUser = await this.userRepository.findOneBy({
|
const existingUser = await this.userRepository.findOneBy({
|
||||||
email: email,
|
email: email,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(!existingUser, 'This user already exists', ForbiddenException);
|
assert(!existingUser, 'This user already exists', ForbiddenException);
|
||||||
|
|
||||||
if (password) {
|
if (password) {
|
||||||
const isPasswordValid = PASSWORD_REGEX.test(password);
|
const isPasswordValid = PASSWORD_REGEX.test(password);
|
||||||
|
|
||||||
assert(isPasswordValid, 'Password too weak', BadRequestException);
|
assert(isPasswordValid, 'Password too weak', BadRequestException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +117,7 @@ export class AuthService {
|
|||||||
domainName: '',
|
domainName: '',
|
||||||
inviteHash: v4(),
|
inviteHash: v4(),
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace = await this.workspaceRepository.save(workspaceToCreate);
|
workspace = await this.workspaceRepository.save(workspaceToCreate);
|
||||||
await this.workspaceManagerService.init(workspace.id);
|
await this.workspaceManagerService.init(workspace.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export class TokenService {
|
|||||||
|
|
||||||
async generateAccessToken(userId: string): Promise<AuthToken> {
|
async generateAccessToken(userId: string): Promise<AuthToken> {
|
||||||
const expiresIn = this.environmentService.getAccessTokenExpiresIn();
|
const expiresIn = this.environmentService.getAccessTokenExpiresIn();
|
||||||
|
|
||||||
assert(expiresIn, '', InternalServerErrorException);
|
assert(expiresIn, '', InternalServerErrorException);
|
||||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ export class TokenService {
|
|||||||
async generateRefreshToken(userId: string): Promise<AuthToken> {
|
async generateRefreshToken(userId: string): Promise<AuthToken> {
|
||||||
const secret = this.environmentService.getRefreshTokenSecret();
|
const secret = this.environmentService.getRefreshTokenSecret();
|
||||||
const expiresIn = this.environmentService.getRefreshTokenExpiresIn();
|
const expiresIn = this.environmentService.getRefreshTokenExpiresIn();
|
||||||
|
|
||||||
assert(expiresIn, '', InternalServerErrorException);
|
assert(expiresIn, '', InternalServerErrorException);
|
||||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||||
|
|
||||||
@ -94,6 +96,7 @@ export class TokenService {
|
|||||||
async generateLoginToken(email: string): Promise<AuthToken> {
|
async generateLoginToken(email: string): Promise<AuthToken> {
|
||||||
const secret = this.environmentService.getLoginTokenSecret();
|
const secret = this.environmentService.getLoginTokenSecret();
|
||||||
const expiresIn = this.environmentService.getLoginTokenExpiresIn();
|
const expiresIn = this.environmentService.getLoginTokenExpiresIn();
|
||||||
|
|
||||||
assert(expiresIn, '', InternalServerErrorException);
|
assert(expiresIn, '', InternalServerErrorException);
|
||||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||||
const jwtPayload = {
|
const jwtPayload = {
|
||||||
@ -122,6 +125,7 @@ export class TokenService {
|
|||||||
};
|
};
|
||||||
const secret = this.environmentService.getAccessTokenSecret();
|
const secret = this.environmentService.getAccessTokenSecret();
|
||||||
let expiresIn: string | number;
|
let expiresIn: string | number;
|
||||||
|
|
||||||
if (expiresAt) {
|
if (expiresAt) {
|
||||||
expiresIn = Math.floor(
|
expiresIn = Math.floor(
|
||||||
(new Date(expiresAt).getTime() - new Date().getTime()) / 1000,
|
(new Date(expiresAt).getTime() - new Date().getTime()) / 1000,
|
||||||
@ -134,6 +138,7 @@ export class TokenService {
|
|||||||
expiresIn,
|
expiresIn,
|
||||||
jwtid: apiKeyId,
|
jwtid: apiKeyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { token };
|
return { token };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
|||||||
picture: photos?.[0]?.value,
|
picture: photos?.[0]?.value,
|
||||||
workspaceInviteHash: state.workspaceInviteHash,
|
workspaceInviteHash: state.workspaceInviteHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
done(null, user);
|
done(null, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
const workspace = await this.workspaceRepository.findOneBy({
|
const workspace = await this.workspaceRepository.findOneBy({
|
||||||
id: payload.workspaceId ?? payload.sub,
|
id: payload.workspaceId ?? payload.sub,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
@ -66,6 +67,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let user;
|
let user;
|
||||||
|
|
||||||
if (payload.workspaceId) {
|
if (payload.workspaceId) {
|
||||||
user = await this.userRepository.findOneBy({
|
user = await this.userRepository.findOneBy({
|
||||||
id: payload.sub,
|
id: payload.sub,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { FileService } from 'src/core/file/services/file.service';
|
|||||||
@Controller('files')
|
@Controller('files')
|
||||||
export class FileController {
|
export class FileController {
|
||||||
constructor(private readonly fileService: FileService) {}
|
constructor(private readonly fileService: FileService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serve files from local storage
|
* Serve files from local storage
|
||||||
* We recommend using an s3 bucket for production
|
* We recommend using an s3 bucket for production
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export class FileUploadService {
|
|||||||
const name = `${id}${ext ? `.${ext}` : ''}`;
|
const name = `${id}${ext ? `.${ext}` : ''}`;
|
||||||
|
|
||||||
const cropSizes = settings.storage.imageCropSizes[fileFolder];
|
const cropSizes = settings.storage.imageCropSizes[fileFolder];
|
||||||
|
|
||||||
if (!cropSizes) {
|
if (!cropSizes) {
|
||||||
throw new Error(`No crop sizes found for ${fileFolder}`);
|
throw new Error(`No crop sizes found for ${fileFolder}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export class BeforeCreateOneRefreshToken<T extends RefreshToken>
|
|||||||
// FIXME: These fields should be autogenerated, we need to run a migration for this
|
// FIXME: These fields should be autogenerated, we need to run a migration for this
|
||||||
instance.input.id = uuidv4();
|
instance.input.id = uuidv4();
|
||||||
instance.input.updatedAt = new Date();
|
instance.input.updatedAt = new Date();
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,6 +72,7 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
const user = await this.userRepository.findOneBy({
|
const user = await this.userRepository.findOneBy({
|
||||||
id: userId,
|
id: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(user, 'User not found');
|
assert(user, 'User not found');
|
||||||
|
|
||||||
await this.userRepository.delete(user.id);
|
await this.userRepository.delete(user.id);
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const getHMACKey = (email?: string, key?: string | null) => {
|
|||||||
if (!email || !key) return null;
|
if (!email || !key) return null;
|
||||||
|
|
||||||
const hmac = crypto.createHmac('sha256', key);
|
const hmac = crypto.createHmac('sha256', key);
|
||||||
|
|
||||||
return hmac.update(email).digest('hex');
|
return hmac.update(email).digest('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,7 +48,9 @@ export class UserResolver {
|
|||||||
const user = await this.userService.findById(id, {
|
const user = await this.userService.findById(id, {
|
||||||
relations: [{ name: 'defaultWorkspace', query: {} }],
|
relations: [{ name: 'defaultWorkspace', query: {} }],
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(user, 'User not found');
|
assert(user, 'User not found');
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +69,7 @@ export class UserResolver {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const key = this.environmentService.getSupportFrontHMACKey();
|
const key = this.environmentService.getSupportFrontHMACKey();
|
||||||
|
|
||||||
return getHMACKey(parent.email, key);
|
return getHMACKey(parent.email, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
|
|
||||||
async deleteWorkspace(id: string) {
|
async deleteWorkspace(id: string) {
|
||||||
const workspace = await this.workspaceRepository.findOneBy({ id });
|
const workspace = await this.workspaceRepository.findOneBy({ id });
|
||||||
|
|
||||||
assert(workspace, 'Workspace not found');
|
assert(workspace, 'Workspace not found');
|
||||||
|
|
||||||
await this.workspaceManagerService.delete(id);
|
await this.workspaceManagerService.delete(id);
|
||||||
|
|||||||
@ -27,7 +27,9 @@ export class WorkspaceResolver {
|
|||||||
@Query(() => Workspace)
|
@Query(() => Workspace)
|
||||||
async currentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
async currentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
||||||
const workspace = await this.workspaceService.findById(id);
|
const workspace = await this.workspaceService.findById(id);
|
||||||
|
|
||||||
assert(workspace, 'User not found');
|
assert(workspace, 'User not found');
|
||||||
|
|
||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export class DataSeedDemoWorkspaceCommand extends CommandRunner {
|
|||||||
logging: true,
|
logging: true,
|
||||||
schema: 'public',
|
schema: 'public',
|
||||||
});
|
});
|
||||||
|
|
||||||
await dataSource.initialize();
|
await dataSource.initialize();
|
||||||
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds();
|
const demoWorkspaceIds = this.environmentService.getDemoWorkspaceIds();
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ export class DataSeedDemoWorkspaceCommand extends CommandRunner {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,12 +43,14 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
|||||||
logging: true,
|
logging: true,
|
||||||
schema: 'public',
|
schema: 'public',
|
||||||
});
|
});
|
||||||
|
|
||||||
await dataSource.initialize();
|
await dataSource.initialize();
|
||||||
|
|
||||||
await seedCoreSchema(dataSource, this.workspaceId);
|
await seedCoreSchema(dataSource, this.workspaceId);
|
||||||
await seedMetadataSchema(dataSource);
|
await seedMetadataSchema(dataSource);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export const seedCoreSchema = async (
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) => {
|
) => {
|
||||||
const schemaName = 'core';
|
const schemaName = 'core';
|
||||||
|
|
||||||
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
@ -28,6 +29,7 @@ export const deleteCoreSchema = async (
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) => {
|
) => {
|
||||||
const schemaName = 'core';
|
const schemaName = 'core';
|
||||||
|
|
||||||
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
||||||
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
// deleteWorkspaces should be last
|
// deleteWorkspaces should be last
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export const seedCoreSchema = async (
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) => {
|
) => {
|
||||||
const schemaName = 'core';
|
const schemaName = 'core';
|
||||||
|
|
||||||
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
await seedWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
await seedUsers(workspaceDataSource, schemaName, workspaceId);
|
||||||
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
@ -28,6 +29,7 @@ export const deleteCoreSchema = async (
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) => {
|
) => {
|
||||||
const schemaName = 'core';
|
const schemaName = 'core';
|
||||||
|
|
||||||
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
await deleteUsersByWorkspace(workspaceDataSource, schemaName, workspaceId);
|
||||||
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
await deleteFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||||
// deleteWorkspaces should be last
|
// deleteWorkspaces should be last
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { seedWebhookFieldMetadata } from 'src/database/typeorm-seeds/metadata/fi
|
|||||||
|
|
||||||
export const seedMetadataSchema = async (workspaceDataSource: DataSource) => {
|
export const seedMetadataSchema = async (workspaceDataSource: DataSource) => {
|
||||||
const schemaName = 'metadata';
|
const schemaName = 'metadata';
|
||||||
|
|
||||||
await seedDataSource(workspaceDataSource, schemaName);
|
await seedDataSource(workspaceDataSource, schemaName);
|
||||||
await seedObjectMetadata(workspaceDataSource, schemaName);
|
await seedObjectMetadata(workspaceDataSource, schemaName);
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { DataSource, DataSourceOptions } from 'typeorm';
|
|||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
config();
|
config();
|
||||||
const configService = new ConfigService();
|
const configService = new ConfigService();
|
||||||
|
|
||||||
export const typeORMCoreModuleOptions: TypeOrmModuleOptions = {
|
export const typeORMCoreModuleOptions: TypeOrmModuleOptions = {
|
||||||
url: configService.get<string>('PG_DATABASE_URL'),
|
url: configService.get<string>('PG_DATABASE_URL'),
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { DataSource, DataSourceOptions } from 'typeorm';
|
|||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
config();
|
config();
|
||||||
const configService = new ConfigService();
|
const configService = new ConfigService();
|
||||||
|
|
||||||
export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = {
|
export const typeORMMetadataModuleOptions: TypeOrmModuleOptions = {
|
||||||
url: configService.get<string>('PG_DATABASE_URL'),
|
url: configService.get<string>('PG_DATABASE_URL'),
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
|
|||||||
@ -1,18 +1,29 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
export class AddEnumOptions1700663879152 implements MigrationInterface {
|
export class AddEnumOptions1700663879152 implements MigrationInterface {
|
||||||
name = 'AddEnumOptions1700663879152'
|
name = 'AddEnumOptions1700663879152';
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "enums" TO "options"`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`);
|
`ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "enums" TO "options"`,
|
||||||
await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" ADD "options" jsonb`);
|
);
|
||||||
}
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`,
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
);
|
||||||
await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" ADD "options" text array`);
|
`ALTER TABLE "metadata"."fieldMetadata" ADD "options" jsonb`,
|
||||||
await queryRunner.query(`ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "options" TO "enums"`);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."fieldMetadata" DROP COLUMN "options"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."fieldMetadata" ADD "options" text array`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."fieldMetadata" RENAME COLUMN "options" TO "enums"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,11 +11,13 @@ export class OptionalJwtAuthGuard extends AuthGuard(['jwt']) {
|
|||||||
|
|
||||||
getRequest(context: ExecutionContext) {
|
getRequest(context: ExecutionContext) {
|
||||||
const request = getRequest(context);
|
const request = getRequest(context);
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRequest(err, user, info) {
|
handleRequest(err, user, info) {
|
||||||
if (err || info) return null;
|
if (err || info) return null;
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,26 +10,31 @@ class TestClass {
|
|||||||
describe('CastToLogLevelArray Decorator', () => {
|
describe('CastToLogLevelArray Decorator', () => {
|
||||||
it('should cast "log" to ["log"]', () => {
|
it('should cast "log" to ["log"]', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { logLevels: 'log' });
|
const transformedClass = plainToClass(TestClass, { logLevels: 'log' });
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toStrictEqual(['log']);
|
expect(transformedClass.logLevels).toStrictEqual(['log']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast "error" to ["error"]', () => {
|
it('should cast "error" to ["error"]', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { logLevels: 'error' });
|
const transformedClass = plainToClass(TestClass, { logLevels: 'error' });
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toStrictEqual(['error']);
|
expect(transformedClass.logLevels).toStrictEqual(['error']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast "warn" to ["warn"]', () => {
|
it('should cast "warn" to ["warn"]', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { logLevels: 'warn' });
|
const transformedClass = plainToClass(TestClass, { logLevels: 'warn' });
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toStrictEqual(['warn']);
|
expect(transformedClass.logLevels).toStrictEqual(['warn']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast "debug" to ["debug"]', () => {
|
it('should cast "debug" to ["debug"]', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { logLevels: 'debug' });
|
const transformedClass = plainToClass(TestClass, { logLevels: 'debug' });
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toStrictEqual(['debug']);
|
expect(transformedClass.logLevels).toStrictEqual(['debug']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast "verbose" to ["verbose"]', () => {
|
it('should cast "verbose" to ["verbose"]', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { logLevels: 'verbose' });
|
const transformedClass = plainToClass(TestClass, { logLevels: 'verbose' });
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toStrictEqual(['verbose']);
|
expect(transformedClass.logLevels).toStrictEqual(['verbose']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,6 +42,7 @@ describe('CastToLogLevelArray Decorator', () => {
|
|||||||
const transformedClass = plainToClass(TestClass, {
|
const transformedClass = plainToClass(TestClass, {
|
||||||
logLevels: 'verbose,error,warn',
|
logLevels: 'verbose,error,warn',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toStrictEqual([
|
expect(transformedClass.logLevels).toStrictEqual([
|
||||||
'verbose',
|
'verbose',
|
||||||
'error',
|
'error',
|
||||||
@ -46,6 +52,7 @@ describe('CastToLogLevelArray Decorator', () => {
|
|||||||
|
|
||||||
it('should cast "toto" to undefined', () => {
|
it('should cast "toto" to undefined', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { logLevels: 'toto' });
|
const transformedClass = plainToClass(TestClass, { logLevels: 'toto' });
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toBeUndefined();
|
expect(transformedClass.logLevels).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,6 +60,7 @@ describe('CastToLogLevelArray Decorator', () => {
|
|||||||
const transformedClass = plainToClass(TestClass, {
|
const transformedClass = plainToClass(TestClass, {
|
||||||
logLevels: 'verbose,error,toto',
|
logLevels: 'verbose,error,toto',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(transformedClass.logLevels).toBeUndefined();
|
expect(transformedClass.logLevels).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,21 +10,25 @@ class TestClass {
|
|||||||
describe('CastToPositiveNumber Decorator', () => {
|
describe('CastToPositiveNumber Decorator', () => {
|
||||||
it('should cast number to number', () => {
|
it('should cast number to number', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { numberProperty: 123 });
|
const transformedClass = plainToClass(TestClass, { numberProperty: 123 });
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(123);
|
expect(transformedClass.numberProperty).toBe(123);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast string to number', () => {
|
it('should cast string to number', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { numberProperty: '123' });
|
const transformedClass = plainToClass(TestClass, { numberProperty: '123' });
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(123);
|
expect(transformedClass.numberProperty).toBe(123);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast null to undefined', () => {
|
it('should cast null to undefined', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { numberProperty: null });
|
const transformedClass = plainToClass(TestClass, { numberProperty: null });
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(undefined);
|
expect(transformedClass.numberProperty).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cast negative number to undefined', () => {
|
it('should cast negative number to undefined', () => {
|
||||||
const transformedClass = plainToClass(TestClass, { numberProperty: -12 });
|
const transformedClass = plainToClass(TestClass, { numberProperty: -12 });
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(undefined);
|
expect(transformedClass.numberProperty).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,6 +36,7 @@ describe('CastToPositiveNumber Decorator', () => {
|
|||||||
const transformedClass = plainToClass(TestClass, {
|
const transformedClass = plainToClass(TestClass, {
|
||||||
numberProperty: undefined,
|
numberProperty: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(undefined);
|
expect(transformedClass.numberProperty).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -39,6 +44,7 @@ describe('CastToPositiveNumber Decorator', () => {
|
|||||||
const transformedClass = plainToClass(TestClass, {
|
const transformedClass = plainToClass(TestClass, {
|
||||||
numberProperty: 'toto',
|
numberProperty: 'toto',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(undefined);
|
expect(transformedClass.numberProperty).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,6 +52,7 @@ describe('CastToPositiveNumber Decorator', () => {
|
|||||||
const transformedClass = plainToClass(TestClass, {
|
const transformedClass = plainToClass(TestClass, {
|
||||||
numberProperty: '-123',
|
numberProperty: '-123',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(transformedClass.numberProperty).toBe(undefined);
|
expect(transformedClass.numberProperty).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,5 +13,6 @@ const toBoolean = (value: any) => {
|
|||||||
if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
|
if (['false', 'off', 'no', '0'].includes(value.toLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,5 +10,6 @@ const toNumber = (value: any) => {
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return isNaN(+value) ? undefined : toNumber(+value);
|
return isNaN(+value) ? undefined : toNumber(+value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
export class IsAWSRegionConstraint implements ValidatorConstraintInterface {
|
export class IsAWSRegionConstraint implements ValidatorConstraintInterface {
|
||||||
validate(region: string) {
|
validate(region: string) {
|
||||||
const regex = /^[a-z]{2}-[a-z]+-\d{1}$/;
|
const regex = /^[a-z]{2}-[a-z]+-\d{1}$/;
|
||||||
|
|
||||||
return regex.test(region); // Returns true if region matches regex
|
return regex.test(region); // Returns true if region matches regex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export class IsDurationConstraint implements ValidatorConstraintInterface {
|
|||||||
validate(duration: string) {
|
validate(duration: string) {
|
||||||
const regex =
|
const regex =
|
||||||
/^-?[0-9]+(.[0-9]+)?(m(illiseconds?)?|s(econds?)?|h((ou)?rs?)?|d(ays?)?|w(eeks?)?|M(onths?)?|y(ears?)?)?$/;
|
/^-?[0-9]+(.[0-9]+)?(m(illiseconds?)?|s(econds?)?|h((ou)?rs?)?|d(ays?)?|w(eeks?)?|M(onths?)?|y(ears?)?)?$/;
|
||||||
|
|
||||||
return regex.test(duration); // Returns true if duration matches regex
|
return regex.test(duration); // Returns true if duration matches regex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -159,6 +159,7 @@ export const validate = (config: Record<string, unknown>) => {
|
|||||||
const validatedConfig = plainToClass(EnvironmentVariables, config);
|
const validatedConfig = plainToClass(EnvironmentVariables, config);
|
||||||
|
|
||||||
const errors = validateSync(validatedConfig);
|
const errors = validateSync(validatedConfig);
|
||||||
|
|
||||||
assert(!errors.length, errors.toString());
|
assert(!errors.length, errors.toString());
|
||||||
|
|
||||||
return validatedConfig;
|
return validatedConfig;
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export class FileStorageModule {
|
|||||||
provide: STORAGE_DRIVER,
|
provide: STORAGE_DRIVER,
|
||||||
useFactory: async (...args: any[]) => {
|
useFactory: async (...args: any[]) => {
|
||||||
const config = await options.useFactory(...args);
|
const config = await options.useFactory(...args);
|
||||||
|
|
||||||
return config?.type === 's3'
|
return config?.type === 's3'
|
||||||
? new S3Driver(config.options)
|
? new S3Driver(config.options)
|
||||||
: new LocalDriver(config.options);
|
: new LocalDriver(config.options);
|
||||||
|
|||||||
@ -67,6 +67,7 @@ const loggerModuleFactory = async (
|
|||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
): Promise<LoggerModuleOptions> => {
|
): Promise<LoggerModuleOptions> => {
|
||||||
const type = environmentService.getLoggerDriver();
|
const type = environmentService.getLoggerDriver();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case LoggerDriver.Console: {
|
case LoggerDriver.Console: {
|
||||||
return {
|
return {
|
||||||
@ -100,6 +101,7 @@ const messageQueueModuleFactory = async (
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageQueueType.PgBoss: {
|
case MessageQueueType.PgBoss: {
|
||||||
const connectionString = environmentService.getPGDatabaseUrl();
|
const connectionString = environmentService.getPGDatabaseUrl();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: MessageQueueType.PgBoss,
|
type: MessageQueueType.PgBoss,
|
||||||
options: {
|
options: {
|
||||||
@ -110,6 +112,7 @@ const messageQueueModuleFactory = async (
|
|||||||
case MessageQueueType.BullMQ: {
|
case MessageQueueType.BullMQ: {
|
||||||
const host = environmentService.getRedisHost();
|
const host = environmentService.getRedisHost();
|
||||||
const port = environmentService.getRedisPort();
|
const port = environmentService.getRedisPort();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: MessageQueueType.BullMQ,
|
type: MessageQueueType.BullMQ,
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export class LoggerModule {
|
|||||||
provide: LOGGER_DRIVER,
|
provide: LOGGER_DRIVER,
|
||||||
useFactory: async (...args: any[]) => {
|
useFactory: async (...args: any[]) => {
|
||||||
const config = await options.useFactory(...args);
|
const config = await options.useFactory(...args);
|
||||||
|
|
||||||
return config?.type === LoggerDriver.Console
|
return config?.type === LoggerDriver.Console
|
||||||
? new ConsoleLogger()
|
? new ConsoleLogger()
|
||||||
: new SentryDriver(config.options);
|
: new SentryDriver(config.options);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export class MemoryStorageDefaultSerializer<T>
|
|||||||
if (typeof item !== 'string') {
|
if (typeof item !== 'string') {
|
||||||
throw new Error('DefaultSerializer can only serialize strings');
|
throw new Error('DefaultSerializer can only serialize strings');
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export class BullMQDriver implements MessageQueueDriver {
|
|||||||
async stop() {
|
async stop() {
|
||||||
const workers = Object.values(this.workerMap);
|
const workers = Object.values(this.workerMap);
|
||||||
const queues = Object.values(this.queueMap);
|
const queues = Object.values(this.queueMap);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
...queues.map((q) => q.close()),
|
...queues.map((q) => q.close()),
|
||||||
...workers.map((w) => w.close()),
|
...workers.map((w) => w.close()),
|
||||||
@ -40,6 +41,7 @@ export class BullMQDriver implements MessageQueueDriver {
|
|||||||
const worker = new Worker(queueName, async (job) => {
|
const worker = new Worker(queueName, async (job) => {
|
||||||
await handler(job as { data: T; id: string });
|
await handler(job as { data: T; id: string });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.workerMap[queueName] = worker;
|
this.workerMap[queueName] = worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export class PgBossDriver implements MessageQueueDriver {
|
|||||||
constructor(options: PgBossDriverOptions) {
|
constructor(options: PgBossDriverOptions) {
|
||||||
this.pgBoss = new PgBoss(options);
|
this.pgBoss = new PgBoss(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop() {
|
async stop() {
|
||||||
await this.pgBoss.stop();
|
await this.pgBoss.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,11 +30,15 @@ export class MessageQueueModule {
|
|||||||
provide: QUEUE_DRIVER,
|
provide: QUEUE_DRIVER,
|
||||||
useFactory: async (...args: any[]) => {
|
useFactory: async (...args: any[]) => {
|
||||||
const config = await options.useFactory(...args);
|
const config = await options.useFactory(...args);
|
||||||
|
|
||||||
if (config.type === MessageQueueType.PgBoss) {
|
if (config.type === MessageQueueType.PgBoss) {
|
||||||
const boss = new PgBossDriver(config.options);
|
const boss = new PgBossDriver(config.options);
|
||||||
|
|
||||||
await boss.init();
|
await boss.init();
|
||||||
|
|
||||||
return boss;
|
return boss;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BullMQDriver(config.options);
|
return new BullMQDriver(config.options);
|
||||||
},
|
},
|
||||||
inject: options.inject || [],
|
inject: options.inject || [],
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const bootstrap = async () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const loggerService = app.get(LoggerService);
|
const loggerService = app.get(LoggerService);
|
||||||
|
|
||||||
app.useLogger(loggerService);
|
app.useLogger(loggerService);
|
||||||
app.useLogger(app.get(EnvironmentService).getLogLevels());
|
app.useLogger(app.get(EnvironmentService).getLogLevels());
|
||||||
|
|
||||||
|
|||||||
@ -129,6 +129,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
if (position > acc) {
|
if (position > acc) {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, -1);
|
}, -1);
|
||||||
|
|
||||||
|
|||||||
@ -16,11 +16,13 @@ export class BeforeCreateOneField<T extends CreateFieldInput>
|
|||||||
context: any,
|
context: any,
|
||||||
): Promise<CreateOneInputType<T>> {
|
): Promise<CreateOneInputType<T>> {
|
||||||
const workspaceId = context?.req?.user?.workspace?.id;
|
const workspaceId = context?.req?.user?.workspace?.id;
|
||||||
|
|
||||||
if (!workspaceId) {
|
if (!workspaceId) {
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.input.workspaceId = workspaceId;
|
instance.input.workspaceId = workspaceId;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ describe('generateTargetColumnMap', () => {
|
|||||||
false,
|
false,
|
||||||
'name',
|
'name',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(textMap).toEqual({ value: 'name' });
|
expect(textMap).toEqual({ value: 'name' });
|
||||||
|
|
||||||
const linkMap = generateTargetColumnMap(
|
const linkMap = generateTargetColumnMap(
|
||||||
@ -17,6 +18,7 @@ describe('generateTargetColumnMap', () => {
|
|||||||
false,
|
false,
|
||||||
'website',
|
'website',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(linkMap).toEqual({ label: 'websiteLabel', url: 'websiteUrl' });
|
expect(linkMap).toEqual({ label: 'websiteLabel', url: 'websiteUrl' });
|
||||||
|
|
||||||
const currencyMap = generateTargetColumnMap(
|
const currencyMap = generateTargetColumnMap(
|
||||||
@ -24,6 +26,7 @@ describe('generateTargetColumnMap', () => {
|
|||||||
true,
|
true,
|
||||||
'price',
|
'price',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(currencyMap).toEqual({
|
expect(currencyMap).toEqual({
|
||||||
amountMicros: '_priceAmountMicros',
|
amountMicros: '_priceAmountMicros',
|
||||||
currencyCode: '_priceCurrencyCode',
|
currencyCode: '_priceCurrencyCode',
|
||||||
|
|||||||
@ -40,6 +40,7 @@ describe('serializeDefaultValue', () => {
|
|||||||
|
|
||||||
it('should handle Date static default value', () => {
|
it('should handle Date static default value', () => {
|
||||||
const date = new Date('2023-01-01');
|
const date = new Date('2023-01-01');
|
||||||
|
|
||||||
expect(serializeDefaultValue(date)).toBe(`'${date.toISOString()}'`);
|
expect(serializeDefaultValue(date)).toBe(`'${date.toISOString()}'`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export class BeforeCreateOneObject<T extends CreateObjectInput>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
instance.input.workspaceId = workspaceId;
|
instance.input.workspaceId = workspaceId;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -197,6 +197,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
||||||
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
|
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
@ -282,6 +283,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
("objectMetadataId", "type", "name")
|
("objectMetadataId", "type", "name")
|
||||||
VALUES ('${createdObjectMetadata.id}', 'table', 'All ${createdObjectMetadata.namePlural}') RETURNING *`,
|
VALUES ('${createdObjectMetadata.id}', 'table', 'All ${createdObjectMetadata.namePlural}') RETURNING *`,
|
||||||
);
|
);
|
||||||
|
|
||||||
createdObjectMetadata.fields.map(async (field, index) => {
|
createdObjectMetadata.fields.map(async (field, index) => {
|
||||||
if (field.name === 'id') {
|
if (field.name === 'id') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export class BeforeCreateOneRelation<T extends CreateRelationInput>
|
|||||||
}
|
}
|
||||||
|
|
||||||
instance.input.workspaceId = workspaceId;
|
instance.input.workspaceId = workspaceId;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,6 +97,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
|
|
||||||
const objectMetadataMap = objectMetadataEntries.reduce((acc, curr) => {
|
const objectMetadataMap = objectMetadataEntries.reduce((acc, curr) => {
|
||||||
acc[curr.id] = curr;
|
acc[curr.id] = curr;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as { [key: string]: ObjectMetadataEntity });
|
}, {} as { [key: string]: ObjectMetadataEntity });
|
||||||
|
|
||||||
@ -169,6 +170,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
||||||
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
|
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@ -94,11 +94,13 @@ export class WorkspaceMigrationFactory {
|
|||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
action: WorkspaceMigrationColumnActionType.CREATE,
|
||||||
fieldMetadata: FieldMetadataInterface,
|
fieldMetadata: FieldMetadataInterface,
|
||||||
): WorkspaceMigrationColumnAction[];
|
): WorkspaceMigrationColumnAction[];
|
||||||
|
|
||||||
createColumnActions(
|
createColumnActions(
|
||||||
action: WorkspaceMigrationColumnActionType.ALTER,
|
action: WorkspaceMigrationColumnActionType.ALTER,
|
||||||
previousFieldMetadata: FieldMetadataInterface,
|
previousFieldMetadata: FieldMetadataInterface,
|
||||||
nextFieldMetadata: FieldMetadataInterface,
|
nextFieldMetadata: FieldMetadataInterface,
|
||||||
): WorkspaceMigrationColumnAction[];
|
): WorkspaceMigrationColumnAction[];
|
||||||
|
|
||||||
createColumnActions(
|
createColumnActions(
|
||||||
action:
|
action:
|
||||||
| WorkspaceMigrationColumnActionType.CREATE
|
| WorkspaceMigrationColumnActionType.CREATE
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export class WorkspaceMigrationService {
|
|||||||
const insertedStandardMigrationsMapByName =
|
const insertedStandardMigrationsMapByName =
|
||||||
insertedStandardMigrations.reduce((acc, migration) => {
|
insertedStandardMigrations.reduce((acc, migration) => {
|
||||||
acc[migration.name] = migration;
|
acc[migration.name] = migration;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export class ScalarsExplorerService {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.scalarImplementations = scalars.reduce((acc, scalar) => {
|
this.scalarImplementations = scalars.reduce((acc, scalar) => {
|
||||||
acc[scalar.name] = scalar;
|
acc[scalar.name] = scalar;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
@ -25,6 +26,7 @@ export class ScalarsExplorerService {
|
|||||||
|
|
||||||
for (const typeName in typeMap) {
|
for (const typeName in typeMap) {
|
||||||
const type = typeMap[typeName];
|
const type = typeMap[typeName];
|
||||||
|
|
||||||
if (isScalarType(type) && !typeName.startsWith('__')) {
|
if (isScalarType(type) && !typeName.startsWith('__')) {
|
||||||
usedScalarNames.push(type.name);
|
usedScalarNames.push(type.name);
|
||||||
}
|
}
|
||||||
@ -40,6 +42,7 @@ export class ScalarsExplorerService {
|
|||||||
|
|
||||||
for (const scalarName of usedScalarNames) {
|
for (const scalarName of usedScalarNames) {
|
||||||
const scalarImplementation = this.getScalarImplementation(scalarName);
|
const scalarImplementation = this.getScalarImplementation(scalarName);
|
||||||
|
|
||||||
if (scalarImplementation) {
|
if (scalarImplementation) {
|
||||||
scalarResolvers[scalarName] = scalarImplementation;
|
scalarResolvers[scalarName] = scalarImplementation;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ describe('getResolverName', () => {
|
|||||||
|
|
||||||
it('should throw an error for an unknown resolver type', () => {
|
it('should throw an error for an unknown resolver type', () => {
|
||||||
const unknownType = 'unknownType';
|
const unknownType = 'unknownType';
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getResolverName(
|
getResolverName(
|
||||||
metadata,
|
metadata,
|
||||||
|
|||||||
@ -18,14 +18,17 @@ export const demoObjectsPrefillData = async (
|
|||||||
id: object.id,
|
id: object.id,
|
||||||
fields: object.fields.reduce((acc, field) => {
|
fields: object.fields.reduce((acc, field) => {
|
||||||
acc[field.name] = field.id;
|
acc[field.name] = field.id;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// TODO: udnerstand why only with this createQueryRunner transaction below works
|
// TODO: udnerstand why only with this createQueryRunner transaction below works
|
||||||
const queryRunner = workspaceDataSource.createQueryRunner();
|
const queryRunner = workspaceDataSource.createQueryRunner();
|
||||||
|
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
|
|
||||||
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
|
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const tableName = 'opportunity';
|
|||||||
|
|
||||||
const getRandomProbability = () => {
|
const getRandomProbability = () => {
|
||||||
const firstDigit = Math.floor(Math.random() * 9) + 1;
|
const firstDigit = Math.floor(Math.random() * 9) + 1;
|
||||||
|
|
||||||
return firstDigit / 10;
|
return firstDigit / 10;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ const getRandomPipelineStepId = (pipelineStepIds: { id: string }[]) =>
|
|||||||
|
|
||||||
const generateRandomAmountMicros = () => {
|
const generateRandomAmountMicros = () => {
|
||||||
const firstDigit = Math.floor(Math.random() * 9) + 1;
|
const firstDigit = Math.floor(Math.random() * 9) + 1;
|
||||||
|
|
||||||
return firstDigit * 10000000000;
|
return firstDigit * 10000000000;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export const viewPrefillData = async (
|
|||||||
|
|
||||||
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
||||||
acc[view.name] = view.id;
|
acc[view.name] = view.id;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@ -16,9 +16,11 @@ export const standardObjectsPrefillData = async (
|
|||||||
id: object.id,
|
id: object.id,
|
||||||
fields: object.fields.reduce((acc, field) => {
|
fields: object.fields.reduce((acc, field) => {
|
||||||
acc[field.name] = field.id;
|
acc[field.name] = field.id;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export const viewPrefillData = async (
|
|||||||
|
|
||||||
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
||||||
acc[view.name] = view.id;
|
acc[view.name] = view.id;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export class MetadataParser {
|
|||||||
|
|
||||||
if (objectMetadata) {
|
if (objectMetadata) {
|
||||||
const fields = Object.values(fieldMetadata);
|
const fields = Object.values(fieldMetadata);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...objectMetadata,
|
...objectMetadata,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@ -33,9 +33,11 @@ export const mapObjectMetadataByUniqueIdentifier = (
|
|||||||
...curr,
|
...curr,
|
||||||
fields: curr.fields.reduce((acc, curr) => {
|
fields: curr.fields.reduce((acc, curr) => {
|
||||||
acc[curr.name] = curr;
|
acc[curr.name] = curr;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -182,6 +182,7 @@ export class WorkspaceManagerService {
|
|||||||
const createdObjectMetadataByNameSingular = createdObjectMetadata.reduce(
|
const createdObjectMetadataByNameSingular = createdObjectMetadata.reduce(
|
||||||
(acc, curr) => {
|
(acc, curr) => {
|
||||||
acc[curr.nameSingular] = curr;
|
acc[curr.nameSingular] = curr;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
@ -326,6 +327,7 @@ export class WorkspaceManagerService {
|
|||||||
if (value === null || typeof value !== 'object') {
|
if (value === null || typeof value !== 'object') {
|
||||||
return [key, value];
|
return [key, value];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [key, filterIgnoredProperties(value, fieldPropertiesToIgnore)];
|
return [key, filterIgnoredProperties(value, fieldPropertiesToIgnore)];
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -346,6 +348,7 @@ export class WorkspaceManagerService {
|
|||||||
// We only handle CHANGE here as REMOVE and CREATE are handled earlier.
|
// We only handle CHANGE here as REMOVE and CREATE are handled earlier.
|
||||||
if (diff.type === 'CHANGE') {
|
if (diff.type === 'CHANGE') {
|
||||||
const property = diff.path[0];
|
const property = diff.path[0];
|
||||||
|
|
||||||
objectsToUpdate[objectInDB.id] = {
|
objectsToUpdate[objectInDB.id] = {
|
||||||
...objectsToUpdate[objectInDB.id],
|
...objectsToUpdate[objectInDB.id],
|
||||||
[property]: diff.value,
|
[property]: diff.value,
|
||||||
@ -357,12 +360,14 @@ export class WorkspaceManagerService {
|
|||||||
if (diff.type === 'CREATE') {
|
if (diff.type === 'CREATE') {
|
||||||
const fieldName = diff.path[0];
|
const fieldName = diff.path[0];
|
||||||
const fieldMetadata = standardObjectFields[fieldName];
|
const fieldMetadata = standardObjectFields[fieldName];
|
||||||
|
|
||||||
fieldsToCreate.push(fieldMetadata);
|
fieldsToCreate.push(fieldMetadata);
|
||||||
}
|
}
|
||||||
if (diff.type === 'CHANGE') {
|
if (diff.type === 'CHANGE') {
|
||||||
const fieldName = diff.path[0];
|
const fieldName = diff.path[0];
|
||||||
const property = diff.path[diff.path.length - 1];
|
const property = diff.path[diff.path.length - 1];
|
||||||
const fieldMetadata = objectInDBFields[fieldName];
|
const fieldMetadata = objectInDBFields[fieldName];
|
||||||
|
|
||||||
fieldsToUpdate[fieldMetadata.id] = {
|
fieldsToUpdate[fieldMetadata.id] = {
|
||||||
...fieldsToUpdate[fieldMetadata.id],
|
...fieldsToUpdate[fieldMetadata.id],
|
||||||
[property]: diff.value,
|
[property]: diff.value,
|
||||||
@ -371,6 +376,7 @@ export class WorkspaceManagerService {
|
|||||||
if (diff.type === 'REMOVE') {
|
if (diff.type === 'REMOVE') {
|
||||||
const fieldName = diff.path[0];
|
const fieldName = diff.path[0];
|
||||||
const fieldMetadata = objectInDBFields[fieldName];
|
const fieldMetadata = objectInDBFields[fieldName];
|
||||||
|
|
||||||
fieldsToDelete.push(fieldMetadata);
|
fieldsToDelete.push(fieldMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,6 +435,7 @@ export class WorkspaceManagerService {
|
|||||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await workspaceDataSource.query(
|
await workspaceDataSource.query(
|
||||||
`comment on schema ${schemaName} is e'@graphql({"max_rows": 60})'`,
|
`comment on schema ${schemaName} is e'@graphql({"max_rows": 60})'`,
|
||||||
);
|
);
|
||||||
@ -460,6 +467,7 @@ export class WorkspaceManagerService {
|
|||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* We are prefilling a few demo objects with data to make it easier for the user to get started.
|
* We are prefilling a few demo objects with data to make it easier for the user to get started.
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { ArgsAliasFactory } from './args-alias.factory';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ArgsStringFactory {
|
export class ArgsStringFactory {
|
||||||
constructor(private readonly argsAliasFactory: ArgsAliasFactory) {}
|
constructor(private readonly argsAliasFactory: ArgsAliasFactory) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
initialArgs: Record<string, any> | undefined,
|
initialArgs: Record<string, any> | undefined,
|
||||||
fieldMetadataCollection: FieldMetadataInterface[],
|
fieldMetadataCollection: FieldMetadataInterface[],
|
||||||
|
|||||||
@ -4,6 +4,7 @@ describe('stringifyWithoutKeyQuote', () => {
|
|||||||
test('should stringify object correctly without quotes around keys', () => {
|
test('should stringify object correctly without quotes around keys', () => {
|
||||||
const obj = { name: 'John', age: 30, isAdmin: false };
|
const obj = { name: 'John', age: 30, isAdmin: false };
|
||||||
const result = stringifyWithoutKeyQuote(obj);
|
const result = stringifyWithoutKeyQuote(obj);
|
||||||
|
|
||||||
expect(result).toBe('{name:"John",age:30,isAdmin:false}');
|
expect(result).toBe('{name:"John",age:30,isAdmin:false}');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ describe('stringifyWithoutKeyQuote', () => {
|
|||||||
address: { city: 'New York', zipCode: 10001 },
|
address: { city: 'New York', zipCode: 10001 },
|
||||||
};
|
};
|
||||||
const result = stringifyWithoutKeyQuote(obj);
|
const result = stringifyWithoutKeyQuote(obj);
|
||||||
|
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'{name:"John",age:30,address:{city:"New York",zipCode:10001}}',
|
'{name:"John",age:30,address:{city:"New York",zipCode:10001}}',
|
||||||
);
|
);
|
||||||
@ -26,6 +28,7 @@ describe('stringifyWithoutKeyQuote', () => {
|
|||||||
hobbies: ['reading', 'movies', 'hiking'],
|
hobbies: ['reading', 'movies', 'hiking'],
|
||||||
};
|
};
|
||||||
const result = stringifyWithoutKeyQuote(obj);
|
const result = stringifyWithoutKeyQuote(obj);
|
||||||
|
|
||||||
expect(result).toBe(
|
expect(result).toBe(
|
||||||
'{name:"John",age:30,hobbies:["reading","movies","hiking"]}',
|
'{name:"John",age:30,hobbies:["reading","movies","hiking"]}',
|
||||||
);
|
);
|
||||||
@ -34,6 +37,7 @@ describe('stringifyWithoutKeyQuote', () => {
|
|||||||
test('should handle empty objects', () => {
|
test('should handle empty objects', () => {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
const result = stringifyWithoutKeyQuote(obj);
|
const result = stringifyWithoutKeyQuote(obj);
|
||||||
|
|
||||||
expect(result).toBe('{}');
|
expect(result).toBe('{}');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -41,6 +45,7 @@ describe('stringifyWithoutKeyQuote', () => {
|
|||||||
const num = 10;
|
const num = 10;
|
||||||
const str = 'Hello';
|
const str = 'Hello';
|
||||||
const bool = false;
|
const bool = false;
|
||||||
|
|
||||||
expect(stringifyWithoutKeyQuote(num)).toBe('10');
|
expect(stringifyWithoutKeyQuote(num)).toBe('10');
|
||||||
expect(stringifyWithoutKeyQuote(str)).toBe('"Hello"');
|
expect(stringifyWithoutKeyQuote(str)).toBe('"Hello"');
|
||||||
expect(stringifyWithoutKeyQuote(bool)).toBe('false');
|
expect(stringifyWithoutKeyQuote(bool)).toBe('false');
|
||||||
|
|||||||
@ -63,6 +63,7 @@ const parseValueNode = (
|
|||||||
case Kind.OBJECT:
|
case Kind.OBJECT:
|
||||||
return valueNode.fields.reduce((obj, field) => {
|
return valueNode.fields.reduce((obj, field) => {
|
||||||
obj[field.name.value] = parseValueNode(field.value, variables);
|
obj[field.name.value] = parseValueNode(field.value, variables);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { ObjectTypeName, RootTypeFactory } from './root-type.factory';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class MutationTypeFactory {
|
export class MutationTypeFactory {
|
||||||
constructor(private readonly rootTypeFactory: RootTypeFactory) {}
|
constructor(private readonly rootTypeFactory: RootTypeFactory) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
workspaceResolverMethodNames: WorkspaceResolverBuilderMutationMethodNames[],
|
workspaceResolverMethodNames: WorkspaceResolverBuilderMutationMethodNames[],
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { ObjectTypeName, RootTypeFactory } from './root-type.factory';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryTypeFactory {
|
export class QueryTypeFactory {
|
||||||
constructor(private readonly rootTypeFactory: RootTypeFactory) {}
|
constructor(private readonly rootTypeFactory: RootTypeFactory) {}
|
||||||
|
|
||||||
create(
|
create(
|
||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
workspaceResolverMethodNames: WorkspaceResolverBuilderQueryMethodNames[],
|
workspaceResolverMethodNames: WorkspaceResolverBuilderQueryMethodNames[],
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export const BigFloatScalarType = new GraphQLScalarType({
|
|||||||
if (ast.kind === Kind.FLOAT || ast.kind === Kind.INT) {
|
if (ast.kind === Kind.FLOAT || ast.kind === Kind.INT) {
|
||||||
return String(ast.value);
|
return String(ast.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,18 +7,21 @@ export const CursorScalarType = new GraphQLScalarType({
|
|||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
throw new Error('Cursor must be a string');
|
throw new Error('Cursor must be a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
parseValue(value) {
|
parseValue(value) {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
throw new Error('Cursor must be a string');
|
throw new Error('Cursor must be a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
parseLiteral(ast) {
|
parseLiteral(ast) {
|
||||||
if (ast.kind !== Kind.STRING) {
|
if (ast.kind !== Kind.STRING) {
|
||||||
throw new Error('Cursor must be a string');
|
throw new Error('Cursor must be a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ast.value;
|
return ast.value;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,6 +14,7 @@ describe('getFieldMetadataType', () => {
|
|||||||
|
|
||||||
it('should throw an error for an unknown type', () => {
|
it('should throw an error for an unknown type', () => {
|
||||||
const unknownType = 'unknownType';
|
const unknownType = 'unknownType';
|
||||||
|
|
||||||
expect(() => getFieldMetadataType(unknownType)).toThrow(
|
expect(() => getFieldMetadataType(unknownType)).toThrow(
|
||||||
`Unknown type ${unknownType}`,
|
`Unknown type ${unknownType}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -48,6 +48,7 @@ describe('getResolverArgs', () => {
|
|||||||
// Test for an unknown resolver type
|
// Test for an unknown resolver type
|
||||||
it('should throw an error for an unknown resolver type', () => {
|
it('should throw an error for an unknown resolver type', () => {
|
||||||
const unknownType = 'unknownType';
|
const unknownType = 'unknownType';
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
getResolverArgs(unknownType as WorkspaceResolverBuilderMethodNames),
|
getResolverArgs(unknownType as WorkspaceResolverBuilderMethodNames),
|
||||||
).toThrow(`Unknown resolver type: ${unknownType}`);
|
).toThrow(`Unknown resolver type: ${unknownType}`);
|
||||||
|
|||||||
@ -73,6 +73,7 @@ export class WorkspaceFactory {
|
|||||||
objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
workspaceResolverBuilderMethodNames,
|
workspaceResolverBuilderMethodNames,
|
||||||
);
|
);
|
||||||
|
|
||||||
usedScalarNames =
|
usedScalarNames =
|
||||||
this.scalarsExplorerService.getUsedScalarNames(autoGeneratedSchema);
|
this.scalarsExplorerService.getUsedScalarNames(autoGeneratedSchema);
|
||||||
typeDefs = printSchema(autoGeneratedSchema);
|
typeDefs = printSchema(autoGeneratedSchema);
|
||||||
|
|||||||
107
server/yarn.lock
107
server/yarn.lock
@ -1270,7 +1270,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.5.0"
|
tslib "^2.5.0"
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0":
|
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||||
@ -2622,6 +2622,51 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
|
resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12"
|
||||||
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
|
integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==
|
||||||
|
|
||||||
|
"@stylistic/eslint-plugin-js@1.5.0", "@stylistic/eslint-plugin-js@^1.5.0":
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.0.tgz#d502d2470f114a4a2b682930ade6ca4eeebaa9f8"
|
||||||
|
integrity sha512-TuGQv1bsIshkbJUInCewp4IUWy24W5RFiVNMV0quPSkuZ8gsYoqq6kLHvvaxpjxN9TvwSoOIwnhgrYKei2Tgcw==
|
||||||
|
dependencies:
|
||||||
|
acorn "^8.11.2"
|
||||||
|
escape-string-regexp "^4.0.0"
|
||||||
|
eslint-visitor-keys "^3.4.3"
|
||||||
|
espree "^9.6.1"
|
||||||
|
graphemer "^1.4.0"
|
||||||
|
|
||||||
|
"@stylistic/eslint-plugin-jsx@1.5.0":
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.5.0.tgz#f8fcb23dcc75b455650426887b24711c4831bee6"
|
||||||
|
integrity sha512-sqFdA1mS0jwovAatS8xFAiwxPbcy69S2AUjrGMxyhxaKbELPjvqbxPYJL+35ylT0xqirUlm118xZIFDooC8koQ==
|
||||||
|
dependencies:
|
||||||
|
"@stylistic/eslint-plugin-js" "^1.5.0"
|
||||||
|
estraverse "^5.3.0"
|
||||||
|
|
||||||
|
"@stylistic/eslint-plugin-plus@1.5.0":
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.5.0.tgz#49ad5560bb6b699c1b5d2940586a4048ebc35b1c"
|
||||||
|
integrity sha512-+A4qXFuM6V7x25Hj+xqfVIUbEckG+MUSvL6m83M6YtRq3d5zLW+giKKEL7eSCAw12MwnoDwPcEhqIJK6BRDR3w==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/utils" "^6.13.2"
|
||||||
|
|
||||||
|
"@stylistic/eslint-plugin-ts@1.5.0":
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-1.5.0.tgz#ed84ffe3c2809748337079b6309f2906ff268e02"
|
||||||
|
integrity sha512-OusNGWRXnOV+ywnoXmBFoMtU6Ig/MX1bEu5Jigqmy2cIT8GRMMn7jUl/bXevkv2o66MYnC7PT1Q/3GvN7t0/eg==
|
||||||
|
dependencies:
|
||||||
|
"@stylistic/eslint-plugin-js" "1.5.0"
|
||||||
|
"@typescript-eslint/utils" "^6.13.2"
|
||||||
|
graphemer "^1.4.0"
|
||||||
|
|
||||||
|
"@stylistic/eslint-plugin@^1.5.0":
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-1.5.0.tgz#d89c813c9f2121a33e1e6d3d3c521ff015964e61"
|
||||||
|
integrity sha512-XmlB5nxk06nlnx1/ka0l+WNqHcjnnXfDts4ZaCvrpCY/6l8lNtHwLwdCKF/UpBYNuRWI/HLWCTtQc0jjfwrfBA==
|
||||||
|
dependencies:
|
||||||
|
"@stylistic/eslint-plugin-js" "1.5.0"
|
||||||
|
"@stylistic/eslint-plugin-jsx" "1.5.0"
|
||||||
|
"@stylistic/eslint-plugin-plus" "1.5.0"
|
||||||
|
"@stylistic/eslint-plugin-ts" "1.5.0"
|
||||||
|
|
||||||
"@tokenizer/token@^0.3.0":
|
"@tokenizer/token@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
||||||
@ -2868,7 +2913,7 @@
|
|||||||
expect "^28.0.0"
|
expect "^28.0.0"
|
||||||
pretty-format "^28.0.0"
|
pretty-format "^28.0.0"
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||||
version "7.0.15"
|
version "7.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||||
@ -3086,7 +3131,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
|
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
|
||||||
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
|
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
|
||||||
|
|
||||||
"@types/semver@^7.3.12":
|
"@types/semver@^7.3.12", "@types/semver@^7.5.0":
|
||||||
version "7.5.6"
|
version "7.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339"
|
||||||
integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==
|
integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==
|
||||||
@ -3184,6 +3229,14 @@
|
|||||||
"@typescript-eslint/types" "5.62.0"
|
"@typescript-eslint/types" "5.62.0"
|
||||||
"@typescript-eslint/visitor-keys" "5.62.0"
|
"@typescript-eslint/visitor-keys" "5.62.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/scope-manager@6.13.2":
|
||||||
|
version "6.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz#5fa4e4adace028dafac212c770640b94e7b61052"
|
||||||
|
integrity sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types" "6.13.2"
|
||||||
|
"@typescript-eslint/visitor-keys" "6.13.2"
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@5.62.0":
|
"@typescript-eslint/type-utils@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
|
||||||
@ -3199,6 +3252,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
|
||||||
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
|
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
|
||||||
|
|
||||||
|
"@typescript-eslint/types@6.13.2":
|
||||||
|
version "6.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.13.2.tgz#c044aac24c2f6cefb8e921e397acad5417dd0ae6"
|
||||||
|
integrity sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.62.0":
|
"@typescript-eslint/typescript-estree@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
|
||||||
@ -3212,6 +3270,19 @@
|
|||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree@6.13.2":
|
||||||
|
version "6.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz#ae556ee154c1acf025b48d37c3ef95a1d55da258"
|
||||||
|
integrity sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types" "6.13.2"
|
||||||
|
"@typescript-eslint/visitor-keys" "6.13.2"
|
||||||
|
debug "^4.3.4"
|
||||||
|
globby "^11.1.0"
|
||||||
|
is-glob "^4.0.3"
|
||||||
|
semver "^7.5.4"
|
||||||
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/utils@5.62.0":
|
"@typescript-eslint/utils@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
||||||
@ -3226,6 +3297,19 @@
|
|||||||
eslint-scope "^5.1.1"
|
eslint-scope "^5.1.1"
|
||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
|
|
||||||
|
"@typescript-eslint/utils@^6.13.2":
|
||||||
|
version "6.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.13.2.tgz#8eb89e53adc6d703a879b131e528807245486f89"
|
||||||
|
integrity sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==
|
||||||
|
dependencies:
|
||||||
|
"@eslint-community/eslint-utils" "^4.4.0"
|
||||||
|
"@types/json-schema" "^7.0.12"
|
||||||
|
"@types/semver" "^7.5.0"
|
||||||
|
"@typescript-eslint/scope-manager" "6.13.2"
|
||||||
|
"@typescript-eslint/types" "6.13.2"
|
||||||
|
"@typescript-eslint/typescript-estree" "6.13.2"
|
||||||
|
semver "^7.5.4"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@5.62.0":
|
"@typescript-eslint/visitor-keys@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
||||||
@ -3234,6 +3318,14 @@
|
|||||||
"@typescript-eslint/types" "5.62.0"
|
"@typescript-eslint/types" "5.62.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys@6.13.2":
|
||||||
|
version "6.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz#e0a4a80cf842bb08e6127b903284166ac4a5594c"
|
||||||
|
integrity sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types" "6.13.2"
|
||||||
|
eslint-visitor-keys "^3.4.1"
|
||||||
|
|
||||||
"@ungap/structured-clone@^1.2.0":
|
"@ungap/structured-clone@^1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||||
@ -3435,7 +3527,7 @@ acorn-walk@^8.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f"
|
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f"
|
||||||
integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==
|
integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==
|
||||||
|
|
||||||
acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0:
|
acorn@^8.11.2, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0:
|
||||||
version "8.11.2"
|
version "8.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
|
||||||
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
|
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
|
||||||
@ -4981,7 +5073,7 @@ estraverse@^4.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||||
|
|
||||||
estraverse@^5.1.0, estraverse@^5.2.0:
|
estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
|
||||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||||
@ -8717,6 +8809,11 @@ tree-kill@1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||||
|
|
||||||
|
ts-api-utils@^1.0.1:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331"
|
||||||
|
integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==
|
||||||
|
|
||||||
ts-essentials@^7.0.3:
|
ts-essentials@^7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38"
|
resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38"
|
||||||
|
|||||||
Reference in New Issue
Block a user