manifest
Version:
The backend for AI code editors
337 lines (336 loc) • 15.4 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CrudService = void 0;
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const common_1 = require("@nestjs/common");
const common_2 = require("../../../../common/src");
const entity_service_1 = require("../../entity/services/entity.service");
const types_1 = require("../../../../types/src");
const constants_1 = require("../../constants");
const pagination_service_1 = require("./pagination.service");
const validation_service_1 = require("../../validation/services/validation.service");
const relationship_service_1 = require("../../entity/services/relationship.service");
const entity_manifest_service_1 = require("../../manifest/services/entity-manifest.service");
let CrudService = class CrudService {
constructor(entityService, entityManifestService, paginationService, validationService, relationshipService) {
this.entityService = entityService;
this.entityManifestService = entityManifestService;
this.paginationService = paginationService;
this.validationService = validationService;
this.relationshipService = relationshipService;
}
async findAll({ entitySlug, queryParams, fullVersion }) {
const entityManifest = this.entityManifestService.getEntityManifest({
slug: entitySlug,
fullVersion
});
const entityMetadata = this.entityService.getEntityMetadata({
className: entityManifest.className
});
const entityRepository = this.entityService.getEntityRepository({ entityMetadata });
const query = entityRepository.createQueryBuilder('entity');
query.select(this.getVisibleProps({
props: entityManifest.properties,
fullVersion
}));
this.loadRelations({
query,
entityMetadata,
relationships: entityManifest.relationships,
requestedRelations: queryParams?.relations?.toString().split(',')
});
this.filterQuery({
query,
queryParams,
entityManifest
});
if (queryParams?.orderBy) {
if (queryParams.orderBy !== 'id' &&
!entityManifest.properties.find((prop) => prop.name === queryParams.orderBy && !prop.hidden)) {
throw new common_1.HttpException(`Property ${queryParams.orderBy} does not exist in ${entitySlug} and thus cannot be used for ordering`, common_1.HttpStatus.BAD_REQUEST);
}
query.orderBy(`entity.${queryParams.orderBy}`, queryParams.order === 'DESC' ? 'DESC' : 'ASC');
}
else {
query.addSelect('entity.createdAt').orderBy('entity.createdAt', 'DESC');
}
return this.paginationService.paginate({
query,
currentPage: parseInt(queryParams?.page, 10) || 1,
resultsPerPage: parseInt(queryParams?.perPage, 10) || constants_1.DEFAULT_RESULTS_PER_PAGE
});
}
async findSelectOptions({ entitySlug, queryParams }) {
const items = await this.findAll({
entitySlug,
queryParams: Object.assign({}, queryParams, { perPage: -1 })
});
const entityManifest = this.entityManifestService.getEntityManifest({
slug: entitySlug
});
return items.data.map((item) => ({
id: item.id,
label: item[entityManifest.mainProp]
}));
}
async findOne({ entitySlug, id, queryParams, fullVersion }) {
const entityManifest = this.entityManifestService.getEntityManifest({
slug: entitySlug,
fullVersion
});
if (!entityManifest.single && !id) {
throw new Error('Id is required for collections.');
}
const entityMetadata = this.entityService.getEntityMetadata({
className: entityManifest.className
});
const query = this.entityService
.getEntityRepository({ entityMetadata })
.createQueryBuilder('entity')
.select(this.getVisibleProps({
props: entityManifest.properties,
fullVersion
}));
if (id) {
query.where('entity.id = :id', { id });
}
this.loadRelations({
query,
entityMetadata,
relationships: entityManifest.relationships,
requestedRelations: queryParams?.relations?.toString().split(',')
});
const item = await query.getOne();
if (!item) {
throw new common_1.NotFoundException('Item not found');
}
return item;
}
async store(entitySlug, itemDto) {
const repository = this.entityService.getEntityRepository({ entitySlug });
const entityManifest = this.entityManifestService.getEntityManifest({
slug: entitySlug,
fullVersion: true
});
const newItem = this.createWithDefaults({
repository,
entityManifest,
itemDto
});
const relationItems = await this.relationshipService.fetchRelationItemsFromDto({
itemDto,
relationships: entityManifest.relationships
.filter((r) => r.type !== 'one-to-many')
.filter((r) => r.type !== 'many-to-many' || r.owningSide)
});
entityManifest.properties
.filter((prop) => prop.type === types_1.PropType.Password)
.forEach((prop) => {
if (newItem[prop.name]) {
newItem[prop.name] = bcryptjs_1.default.hashSync(itemDto['password'], constants_1.SALT_ROUNDS);
}
});
const errors = this.validationService.validate(newItem, entityManifest);
if (errors.length) {
throw new common_1.HttpException(errors, common_1.HttpStatus.BAD_REQUEST);
}
return repository.save({ ...newItem, ...relationItems });
}
async storeEmpty(entitySlug) {
const entityRepository = this.entityService.getEntityRepository({ entitySlug });
return entityRepository.save({});
}
async update({ entitySlug, id, itemDto, partialReplacement }) {
const entityManifest = this.entityManifestService.getEntityManifest({
slug: entitySlug,
fullVersion: true
});
if (!entityManifest.single && !id) {
throw new Error('Id is required for collections.');
}
const entityRepository = this.entityService.getEntityRepository({ entitySlug });
const findParams = id ? { where: { id } } : { where: {} };
const item = await entityRepository.findOne(findParams);
if (!item) {
throw new common_1.NotFoundException('Item not found');
}
if (entityManifest.authenticable) {
delete item['password'];
}
const relationItems = await this.relationshipService.fetchRelationItemsFromDto({
itemDto,
relationships: entityManifest.relationships
.filter((r) => r.type !== 'one-to-many')
.filter((r) => r.type !== 'many-to-many' || r.owningSide),
emptyMissing: !partialReplacement
});
if (partialReplacement) {
itemDto = { ...item, ...itemDto };
Object.keys(relationItems).forEach((key) => {
if (relationItems[key] === undefined ||
relationItems[key]?.length === 0) {
delete relationItems[key];
}
});
}
const updatedItem = entityRepository.create({
id: item.id,
...itemDto
});
if (entityManifest.authenticable && itemDto.password) {
updatedItem.password = bcryptjs_1.default.hashSync(itemDto['password'], constants_1.SALT_ROUNDS);
}
const errors = this.validationService.validate(updatedItem, entityManifest, {
isUpdate: true
});
if (errors.length) {
throw new common_1.HttpException(errors, common_1.HttpStatus.BAD_REQUEST);
}
return entityRepository.save({ ...updatedItem, ...relationItems });
}
async delete(entitySlug, id) {
const entityRepository = this.entityService.getEntityRepository({
entitySlug
});
const oneToManyRelationships = this.entityManifestService
.getEntityManifest({
slug: entitySlug
})
.relationships.filter((r) => r.type === 'one-to-many');
const item = await entityRepository.findOne({
where: { id },
relations: oneToManyRelationships.map((r) => r.name)
});
if (!item) {
throw new common_1.NotFoundException('Item not found');
}
if (oneToManyRelationships.length) {
oneToManyRelationships.forEach((relationship) => {
const relatedItems = item[relationship.name];
if (relatedItems.length) {
throw new common_1.HttpException(`Cannot delete item as it has related ${relationship.name}. Please delete the related items first.`, common_1.HttpStatus.BAD_REQUEST);
}
});
}
await entityRepository.delete(id);
return item;
}
createWithDefaults({ repository, entityManifest, itemDto }) {
const newItem = repository.create(itemDto);
entityManifest.properties.forEach((prop) => {
if (prop.default && typeof newItem[prop.name] === 'undefined') {
newItem[prop.name] = prop.default;
}
});
return newItem;
}
getVisibleProps({ props, alias = 'entity', fullVersion }) {
const visibleProps = [`${alias}.id`];
props
.filter((prop) => prop.name !== 'password')
.filter((prop) => fullVersion || !prop.hidden)
.forEach((prop) => visibleProps.push(`${alias}.${prop.name}`));
return visibleProps;
}
loadRelations({ query, entityMetadata, relationships, requestedRelations, alias = 'entity' }) {
entityMetadata.relations.forEach((relationMetadata) => {
const relationshipManifest = relationships.find((relationship) => relationship.name === relationMetadata.propertyName);
if (!relationshipManifest.eager &&
!requestedRelations?.includes(relationMetadata.propertyName)) {
return;
}
const aliasName = (0, common_2.camelize)([alias, relationMetadata.propertyName]);
query.leftJoin(`${alias}.${relationMetadata.propertyName}`, aliasName);
const relationEntityManifest = this.entityManifestService.getEntityManifest({
className: relationMetadata.inverseEntityMetadata.targetName
});
query.addSelect(this.getVisibleProps({
props: relationEntityManifest.properties,
alias: aliasName
}));
const relationEntityMetadata = this.entityService.getEntityMetadata({
className: relationMetadata.inverseEntityMetadata.targetName
});
if (relationEntityMetadata.relations.length) {
query = this.loadRelations({
query,
entityMetadata: relationEntityMetadata,
relationships: relationEntityManifest.relationships,
requestedRelations: requestedRelations
?.filter((requestedRelation) => requestedRelation !== relationMetadata.propertyName)
.map((requestedRelation) => requestedRelation.replace(`${relationMetadata.propertyName}.`, '')),
alias: aliasName
});
}
});
return query;
}
filterQuery({ query, queryParams, entityManifest }) {
Object.entries(queryParams || {})
.filter(([key]) => !constants_1.QUERY_PARAMS_RESERVED_WORDS.includes(key))
.forEach(([key, value], index) => {
const suffix = Object.values(types_1.WhereKeySuffix)
.reverse()
.find((suffix) => key.includes(suffix));
if (!suffix) {
throw new common_1.HttpException('Query param key should include an operator suffix like _eq, _gt, _lt, _in, etc.', common_1.HttpStatus.BAD_REQUEST);
}
const operator = (0, common_2.getRecordKeyByValue)(types_1.whereOperatorKeySuffix, suffix);
const propName = key.replace(suffix, '');
const prop = entityManifest.properties.find((prop) => prop.name === propName && !prop.hidden);
const relation = entityManifest.relationships.find((relationship) => relationship.name === propName.split('.')[0]);
if (!prop && !relation) {
throw new common_1.HttpException(`Property ${propName} does not exist in ${entityManifest.className}`, common_1.HttpStatus.BAD_REQUEST);
}
let whereKey;
if (relation) {
const aliasName = (0, common_2.camelize)(['entity', relation.name]);
whereKey = `${aliasName}.${propName.split('.')[1]}`;
}
else {
whereKey = `entity.${propName}`;
}
if (prop && prop.type === types_1.PropType.Boolean) {
if (value === 'true') {
value = '1';
}
else if (value === 'false') {
value = '0';
}
}
if (operator === types_1.WhereOperator.In) {
const inValues = value.split(',');
query.andWhere(`${whereKey} ${operator} (:...value_${index})`, {
[`value_${index}`]: inValues
});
}
else {
query.andWhere(`${whereKey} ${operator} :value_${index}`, {
[`value_${index}`]: value
});
}
});
return query;
}
};
exports.CrudService = CrudService;
exports.CrudService = CrudService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [entity_service_1.EntityService,
entity_manifest_service_1.EntityManifestService,
pagination_service_1.PaginationService,
validation_service_1.ValidationService,
relationship_service_1.RelationshipService])
], CrudService);