@solidxai/core 0.1.4 → 0.1.5-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/dist/constants/error-messages.d.ts +1 -0
- package/dist/constants/error-messages.d.ts.map +1 -1
- package/dist/constants/error-messages.js +1 -0
- package/dist/constants/error-messages.js.map +1 -1
- package/dist/constants.d.ts +3 -3
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +12 -12
- package/dist/constants.js.map +1 -1
- package/dist/controllers/otp-authentication.controller.d.ts +1 -4
- package/dist/controllers/otp-authentication.controller.d.ts.map +1 -1
- package/dist/controllers/otp-authentication.controller.js +1 -1
- package/dist/controllers/role-metadata.controller.d.ts +1 -0
- package/dist/controllers/role-metadata.controller.d.ts.map +1 -1
- package/dist/controllers/role-metadata.controller.js +15 -0
- package/dist/controllers/role-metadata.controller.js.map +1 -1
- package/dist/controllers/view-metadata.controller.d.ts +1 -0
- package/dist/controllers/view-metadata.controller.d.ts.map +1 -1
- package/dist/dtos/create-email-template.dto.d.ts.map +1 -1
- package/dist/dtos/create-email-template.dto.js.map +1 -1
- package/dist/dtos/create-list-of-values.dto.d.ts.map +1 -1
- package/dist/dtos/create-list-of-values.dto.js.map +1 -1
- package/dist/dtos/create-menu-item-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-menu-item-metadata.dto.js.map +1 -1
- package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-role-metadata.dto.js.map +1 -1
- package/dist/dtos/create-saved-filters.dto.d.ts +1 -0
- package/dist/dtos/create-saved-filters.dto.d.ts.map +1 -1
- package/dist/dtos/create-saved-filters.dto.js +8 -1
- package/dist/dtos/create-saved-filters.dto.js.map +1 -1
- package/dist/dtos/create-scheduled-job.dto.d.ts.map +1 -1
- package/dist/dtos/create-scheduled-job.dto.js.map +1 -1
- package/dist/dtos/create-security-rule.dto.d.ts.map +1 -1
- package/dist/dtos/create-security-rule.dto.js.map +1 -1
- package/dist/dtos/create-sms-template.dto.d.ts.map +1 -1
- package/dist/dtos/create-sms-template.dto.js.map +1 -1
- package/dist/dtos/create-view-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-view-metadata.dto.js.map +1 -1
- package/dist/dtos/otp-sign-in.dto.d.ts +1 -1
- package/dist/dtos/otp-sign-in.dto.d.ts.map +1 -1
- package/dist/dtos/otp-sign-in.dto.js +2 -2
- package/dist/dtos/otp-sign-in.dto.js.map +1 -1
- package/dist/dtos/otp-sign-up.dto.d.ts +2 -2
- package/dist/dtos/otp-sign-up.dto.d.ts.map +1 -1
- package/dist/dtos/otp-sign-up.dto.js +2 -2
- package/dist/dtos/otp-sign-up.dto.js.map +1 -1
- package/dist/dtos/resolve-s3-url.dto.d.ts +2 -5
- package/dist/dtos/resolve-s3-url.dto.d.ts.map +1 -1
- package/dist/dtos/resolve-s3-url.dto.js +1 -13
- package/dist/dtos/resolve-s3-url.dto.js.map +1 -1
- package/dist/dtos/sign-up.dto.d.ts.map +1 -1
- package/dist/dtos/sign-up.dto.js.map +1 -1
- package/dist/dtos/update-email-template.dto.d.ts.map +1 -1
- package/dist/dtos/update-email-template.dto.js.map +1 -1
- package/dist/dtos/update-list-of-values.dto.d.ts.map +1 -1
- package/dist/dtos/update-list-of-values.dto.js.map +1 -1
- package/dist/dtos/update-menu-item-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/update-menu-item-metadata.dto.js.map +1 -1
- package/dist/dtos/update-saved-filters.dto.d.ts +1 -0
- package/dist/dtos/update-saved-filters.dto.d.ts.map +1 -1
- package/dist/dtos/update-saved-filters.dto.js +10 -1
- package/dist/dtos/update-saved-filters.dto.js.map +1 -1
- package/dist/dtos/update-scheduled-job.dto.d.ts.map +1 -1
- package/dist/dtos/update-scheduled-job.dto.js.map +1 -1
- package/dist/dtos/update-security-rule.dto.d.ts.map +1 -1
- package/dist/dtos/update-security-rule.dto.js.map +1 -1
- package/dist/dtos/update-sms-template.dto.d.ts.map +1 -1
- package/dist/dtos/update-sms-template.dto.js.map +1 -1
- package/dist/dtos/update-view-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/update-view-metadata.dto.js.map +1 -1
- package/dist/entities/chatter-message-details.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message-details.entity.js +1 -0
- package/dist/entities/chatter-message-details.entity.js.map +1 -1
- package/dist/entities/chatter-message.entity.d.ts.map +1 -1
- package/dist/entities/chatter-message.entity.js +1 -0
- package/dist/entities/chatter-message.entity.js.map +1 -1
- package/dist/entities/common.entity.js +4 -4
- package/dist/entities/common.entity.js.map +1 -1
- package/dist/entities/legacy-common.entity.js +4 -4
- package/dist/entities/legacy-common.entity.js.map +1 -1
- package/dist/entities/saved-filters.entity.d.ts +1 -0
- package/dist/entities/saved-filters.entity.d.ts.map +1 -1
- package/dist/entities/saved-filters.entity.js +6 -1
- package/dist/entities/saved-filters.entity.js.map +1 -1
- package/dist/entities/user.entity.d.ts +1 -0
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +6 -1
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts +2 -0
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js +33 -23
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts +3 -0
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js +36 -23
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/security.helper.js +1 -0
- package/dist/helpers/security.helper.js.map +1 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/repository/solid-base.repository.d.ts +10 -1
- package/dist/repository/solid-base.repository.d.ts.map +1 -1
- package/dist/repository/solid-base.repository.js +109 -0
- package/dist/repository/solid-base.repository.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts +2 -0
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +142 -72
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/permission-metadata-seeder.service.d.ts +1 -1
- package/dist/seeders/permission-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/permission-metadata-seeder.service.js +1 -1
- package/dist/seeders/permission-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +23 -25
- package/dist/services/authentication.service.d.ts +22 -8
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +230 -214
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/crud-helper.service.d.ts +4 -0
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +66 -32
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +7 -4
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/file/disk-file.service.d.ts +0 -2
- package/dist/services/file/disk-file.service.d.ts.map +1 -1
- package/dist/services/file/disk-file.service.js +7 -16
- package/dist/services/file/disk-file.service.js.map +1 -1
- package/dist/services/file/index.d.ts +1 -0
- package/dist/services/file/index.d.ts.map +1 -1
- package/dist/services/file/index.js +1 -0
- package/dist/services/file/index.js.map +1 -1
- package/dist/services/file/storage-path-builder.d.ts +17 -0
- package/dist/services/file/storage-path-builder.d.ts.map +1 -0
- package/dist/{seeders/sms-template-seeder.service.js → services/file/storage-path-builder.js} +45 -35
- package/dist/services/file/storage-path-builder.js.map +1 -0
- package/dist/services/media.service.d.ts +1 -1
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +45 -6
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.js +46 -7
- package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
- package/dist/services/module-metadata.service.d.ts +4 -6
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +16 -14
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/setting.service.d.ts +3 -2
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +7 -4
- package/dist/services/setting.service.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +24 -2
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +8 -6
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +1 -0
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +25 -1
- package/dist/services/view-metadata.service.js.map +1 -1
- package/dist/solid-core.module.d.ts +3 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +45 -9
- package/dist/solid-core.module.js.map +1 -1
- package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -1
- package/dist/testing/adapters/ui/playwright-adapter.js +35 -2
- package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -1
- package/dist/transformers/typeorm/local-date-time-transformer.d.ts +4 -3
- package/dist/transformers/typeorm/local-date-time-transformer.d.ts.map +1 -1
- package/dist/transformers/typeorm/local-date-time-transformer.js +20 -2
- package/dist/transformers/typeorm/local-date-time-transformer.js.map +1 -1
- package/package.json +8 -2
- package/src/constants/error-messages.ts +1 -0
- package/src/constants.ts +3 -3
- package/src/controllers/role-metadata.controller.ts +26 -18
- package/src/dtos/create-email-template.dto.ts +7 -0
- package/src/dtos/create-list-of-values.dto.ts +7 -0
- package/src/dtos/create-menu-item-metadata.dto.ts +12 -1
- package/src/dtos/create-role-metadata.dto.ts +9 -0
- package/src/dtos/create-saved-filters.dto.ts +7 -0
- package/src/dtos/create-scheduled-job.dto.ts +14 -0
- package/src/dtos/create-security-rule.dto.ts +6 -0
- package/src/dtos/create-sms-template.dto.ts +6 -0
- package/src/dtos/create-view-metadata.dto.ts +11 -0
- package/src/dtos/otp-sign-in.dto.ts +3 -3
- package/src/dtos/otp-sign-up.dto.ts +3 -3
- package/src/dtos/resolve-s3-url.dto.ts +2 -12
- package/src/dtos/sign-up.dto.ts +0 -2
- package/src/dtos/update-email-template.dto.ts +6 -0
- package/src/dtos/update-list-of-values.dto.ts +8 -0
- package/src/dtos/update-menu-item-metadata.dto.ts +12 -0
- package/src/dtos/update-saved-filters.dto.ts +5 -1
- package/src/dtos/update-scheduled-job.dto.ts +15 -0
- package/src/dtos/update-security-rule.dto.ts +7 -0
- package/src/dtos/update-sms-template.dto.ts +32 -32
- package/src/dtos/update-view-metadata.dto.ts +12 -0
- package/src/entities/chatter-message-details.entity.ts +1 -0
- package/src/entities/chatter-message.entity.ts +1 -0
- package/src/entities/common.entity.ts +5 -5
- package/src/entities/legacy-common.entity.ts +5 -5
- package/src/entities/saved-filters.entity.ts +3 -0
- package/src/entities/user.entity.ts +4 -1
- package/src/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.ts +43 -32
- package/src/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.ts +45 -33
- package/src/helpers/security.helper.ts +1 -1
- package/src/index.ts +0 -4
- package/src/repository/solid-base.repository.ts +172 -1
- package/src/seeders/module-metadata-seeder.service.ts +189 -127
- package/src/seeders/permission-metadata-seeder.service.ts +1 -4
- package/src/seeders/seed-data/solid-core-metadata.json +30 -32
- package/src/services/authentication.service.ts +273 -269
- package/src/services/crud-helper.service.ts +79 -36
- package/src/services/crud.service.ts +9 -4
- package/src/services/field-metadata.service.ts +0 -71
- package/src/services/file/disk-file.service.ts +8 -18
- package/src/services/file/index.ts +1 -0
- package/src/services/file/storage-path-builder.ts +56 -0
- package/src/services/media.service.ts +13 -7
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +1 -1
- package/src/services/mediaStorageProviders/file-storage-provider.ts +13 -8
- package/src/services/module-metadata.service.ts +18 -15
- package/src/services/setting.service.ts +5 -3
- package/src/services/settings/default-settings-provider.service.ts +5 -3
- package/src/services/view-metadata.service.ts +29 -1
- package/src/solid-core.module.ts +16 -12
- package/src/testing/adapters/ui/playwright-adapter.ts +1 -1
- package/src/transformers/typeorm/local-date-time-transformer.ts +21 -3
- package/dist/passport-strategies/local.strategy.d.ts +0 -15
- package/dist/passport-strategies/local.strategy.d.ts.map +0 -1
- package/dist/passport-strategies/local.strategy.js +0 -44
- package/dist/passport-strategies/local.strategy.js.map +0 -1
- package/dist/seeders/email-template-seeder.service.d.ts +0 -10
- package/dist/seeders/email-template-seeder.service.d.ts.map +0 -1
- package/dist/seeders/email-template-seeder.service.js +0 -84
- package/dist/seeders/email-template-seeder.service.js.map +0 -1
- package/dist/seeders/sms-template-seeder.service.d.ts +0 -10
- package/dist/seeders/sms-template-seeder.service.d.ts.map +0 -1
- package/dist/seeders/sms-template-seeder.service.js.map +0 -1
- package/dist/seeders/user-seeder.service.d.ts +0 -10
- package/dist/seeders/user-seeder.service.d.ts.map +0 -1
- package/dist/seeders/user-seeder.service.js +0 -44
- package/dist/seeders/user-seeder.service.js.map +0 -1
- package/src/passport-strategies/local.strategy.ts +0 -28
- package/src/seeders/email-template-seeder.service.ts +0 -49
- package/src/seeders/sms-template-seeder.service.ts +0 -50
- package/src/seeders/user-seeder.service.ts +0 -33
- package/src/workflow.readme.md +0 -25
|
@@ -3,7 +3,7 @@ import { BasicFilterDto } from "../dtos/basic-filters.dto";
|
|
|
3
3
|
import { classify } from "@angular-devkit/core/src/utils/strings";
|
|
4
4
|
import { ActiveUserData } from "src/interfaces/active-user-data.interface";
|
|
5
5
|
import { SolidRegistry } from "src/helpers/solid-registry";
|
|
6
|
-
import { Logger } from "@nestjs/common";
|
|
6
|
+
import { BadRequestException, Logger } from "@nestjs/common";
|
|
7
7
|
import { ERROR_MESSAGES } from "src/constants/error-messages";
|
|
8
8
|
|
|
9
9
|
export enum FilterCombinator {
|
|
@@ -64,88 +64,102 @@ export class CrudHelperService {
|
|
|
64
64
|
const primaryFilterObj = normalizedFilters[key];
|
|
65
65
|
const normalizedPrimaryFilterObj = this.normalizeObjectKeys(primaryFilterObj);
|
|
66
66
|
|
|
67
|
+
const [rawField, funcAlias] = key.split(':');
|
|
68
|
+
|
|
67
69
|
// Get the operator or field from the key
|
|
68
70
|
const operatorOrField = Object.keys(normalizedPrimaryFilterObj)[0];
|
|
69
71
|
// if the key is an operator, then build the query based on the operator
|
|
70
72
|
if (operatorOrField.startsWith('$')) {
|
|
71
73
|
const operator = operatorOrField;
|
|
72
|
-
|
|
74
|
+
let columnExpression: string | undefined;
|
|
75
|
+
if (funcAlias) {
|
|
76
|
+
try {
|
|
77
|
+
columnExpression = this.buildDateGranularityExpression(this.getDriver(selectQb), `${alias}.${rawField}`, funcAlias);
|
|
78
|
+
} catch {
|
|
79
|
+
throw new BadRequestException(`Unsupported field function '${funcAlias}'. Supported functions are: day, week, month, year.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.buildOperatorQuery(qb, alias, rawField, normalizedPrimaryFilterObj, operator, columnExpression);
|
|
73
83
|
return;
|
|
74
84
|
}
|
|
75
85
|
else { // Recursively call the applyFilters method to handle nested conditions
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
if (funcAlias) {
|
|
87
|
+
throw new BadRequestException(`Function alias ':${funcAlias}' is not valid on relation field '${rawField}'. It can only be applied to scalar fields.`);
|
|
88
|
+
}
|
|
89
|
+
const joinField = `${alias}.${rawField}`;
|
|
90
|
+
if (!this.isRelationJoined(selectQb, joinField)) selectQb.leftJoin(joinField, rawField);
|
|
91
|
+
this.applyFilters(qb, primaryFilterObj, rawField, selectQb);
|
|
79
92
|
}
|
|
80
93
|
});
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
private buildOperatorQuery(qb: any, alias: string, field: string, normalizedPrimaryOperatorObj: any, operator: string) {
|
|
97
|
+
private buildOperatorQuery(qb: any, alias: string, field: string, normalizedPrimaryOperatorObj: any, operator: string, columnExpression?: string) {
|
|
85
98
|
const uniqueFieldAlias = `${alias}_${field}_${Math.floor(Math.random() * 1000)}`;
|
|
99
|
+
const colExpr = columnExpression ?? `${alias}.${field}`;
|
|
86
100
|
switch (operator) {
|
|
87
101
|
case '$eq':
|
|
88
|
-
qb.andWhere(`${
|
|
102
|
+
qb.andWhere(`${colExpr} = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eq });
|
|
89
103
|
break;
|
|
90
104
|
case '$eqi':
|
|
91
|
-
qb.andWhere(`LOWER(${
|
|
105
|
+
qb.andWhere(`LOWER(${colExpr}) = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eqi.toLowerCase() });
|
|
92
106
|
break;
|
|
93
107
|
case '$ne':
|
|
94
|
-
qb.andWhere(`${
|
|
108
|
+
qb.andWhere(`${colExpr} != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$ne });
|
|
95
109
|
break;
|
|
96
110
|
case '$nei':
|
|
97
|
-
qb.andWhere(`LOWER(${
|
|
111
|
+
qb.andWhere(`LOWER(${colExpr}) != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$nei.toLowerCase() });
|
|
98
112
|
break;
|
|
99
113
|
case '$gt':
|
|
100
|
-
qb.andWhere(`${
|
|
114
|
+
qb.andWhere(`${colExpr} > :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gt });
|
|
101
115
|
break;
|
|
102
116
|
case '$gte':
|
|
103
|
-
qb.andWhere(`${
|
|
117
|
+
qb.andWhere(`${colExpr} >= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gte });
|
|
104
118
|
break;
|
|
105
119
|
case '$lt':
|
|
106
|
-
qb.andWhere(`${
|
|
120
|
+
qb.andWhere(`${colExpr} < :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lt });
|
|
107
121
|
break;
|
|
108
122
|
case '$lte':
|
|
109
|
-
qb.andWhere(`${
|
|
123
|
+
qb.andWhere(`${colExpr} <= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lte });
|
|
110
124
|
break;
|
|
111
125
|
case '$in':
|
|
112
|
-
qb.andWhere(`${
|
|
126
|
+
qb.andWhere(`${colExpr} IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$in });
|
|
113
127
|
break;
|
|
114
128
|
case '$notIn':
|
|
115
|
-
qb.andWhere(`${
|
|
129
|
+
qb.andWhere(`${colExpr} NOT IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$notIn });
|
|
116
130
|
break;
|
|
117
131
|
case '$contains':
|
|
118
|
-
qb.andWhere(`${
|
|
132
|
+
qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$contains}%` });
|
|
119
133
|
break;
|
|
120
134
|
case '$notContains':
|
|
121
|
-
qb.andWhere(`${
|
|
135
|
+
qb.andWhere(`${colExpr} NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContains}%` });
|
|
122
136
|
break;
|
|
123
137
|
case '$containsi':
|
|
124
|
-
qb.andWhere(`LOWER(${
|
|
138
|
+
qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$containsi.toLowerCase()}%` });
|
|
125
139
|
break;
|
|
126
140
|
case '$notContainsi':
|
|
127
|
-
qb.andWhere(`LOWER(${
|
|
141
|
+
qb.andWhere(`LOWER(${colExpr}) NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContainsi.toLowerCase()}%` });
|
|
128
142
|
break;
|
|
129
143
|
case '$null':
|
|
130
|
-
qb.andWhere(`${
|
|
144
|
+
qb.andWhere(`${colExpr} IS NULL`);
|
|
131
145
|
break;
|
|
132
146
|
case '$notNull':
|
|
133
|
-
qb.andWhere(`${
|
|
147
|
+
qb.andWhere(`${colExpr} IS NOT NULL`);
|
|
134
148
|
break;
|
|
135
149
|
case '$between':
|
|
136
|
-
qb.andWhere(`${
|
|
150
|
+
qb.andWhere(`${colExpr} BETWEEN :${uniqueFieldAlias}0 AND :${uniqueFieldAlias}1`, { [`${uniqueFieldAlias}0`]: normalizedPrimaryOperatorObj.$between[0], [`${uniqueFieldAlias}1`]: normalizedPrimaryOperatorObj.$between[1] });
|
|
137
151
|
break;
|
|
138
152
|
case '$startsWith':
|
|
139
|
-
qb.andWhere(`${
|
|
153
|
+
qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWith}%` });
|
|
140
154
|
break;
|
|
141
155
|
case '$startsWithi':
|
|
142
|
-
qb.andWhere(`LOWER(${
|
|
156
|
+
qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWithi.toLowerCase()}%` });
|
|
143
157
|
break;
|
|
144
158
|
case '$endsWith':
|
|
145
|
-
qb.andWhere(`${
|
|
159
|
+
qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWith}` });
|
|
146
160
|
break;
|
|
147
161
|
case '$endsWithi':
|
|
148
|
-
qb.andWhere(`LOWER(${
|
|
162
|
+
qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWithi.toLowerCase()}` });
|
|
149
163
|
break;
|
|
150
164
|
default:
|
|
151
165
|
throw new Error(`Operator ${operator} is not supported`);
|
|
@@ -588,6 +602,27 @@ export class CrudHelperService {
|
|
|
588
602
|
}
|
|
589
603
|
}
|
|
590
604
|
|
|
605
|
+
private getGroupFieldValues(
|
|
606
|
+
group: any,
|
|
607
|
+
groupByFields: string[],
|
|
608
|
+
groupAliasMap: Record<string, string>
|
|
609
|
+
): Array<{ rawVal: any; alias: string; granularity?: string }> {
|
|
610
|
+
return groupByFields
|
|
611
|
+
.map(field => {
|
|
612
|
+
const parts = field.split(':');
|
|
613
|
+
const granularity = parts[1];
|
|
614
|
+
const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
|
|
615
|
+
const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
|
|
616
|
+
return { rawVal, alias, granularity };
|
|
617
|
+
})
|
|
618
|
+
.filter(({ rawVal }) => rawVal !== undefined && rawVal !== null);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private normalizeGroupValue(value: any, granularity?: string): any {
|
|
622
|
+
if (!granularity) return value;
|
|
623
|
+
return this.formatGroupValue(value, 'YYYY-MM-DD');
|
|
624
|
+
}
|
|
625
|
+
|
|
591
626
|
getGroupName(
|
|
592
627
|
group: any,
|
|
593
628
|
aggregateAliases: Set<string>,
|
|
@@ -595,22 +630,28 @@ export class CrudHelperService {
|
|
|
595
630
|
groupAliasMap: Record<string, string>,
|
|
596
631
|
groupFormatMap: Record<string, string | undefined>
|
|
597
632
|
): string {
|
|
598
|
-
const
|
|
599
|
-
.map(field => {
|
|
600
|
-
const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
|
|
601
|
-
const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
|
|
602
|
-
return this.formatGroupValue(rawVal, groupFormatMap[alias]);
|
|
603
|
-
})
|
|
604
|
-
.filter(v => v !== undefined && v !== null);
|
|
633
|
+
const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
|
|
605
634
|
|
|
606
|
-
if (
|
|
635
|
+
if (fieldValues.length === 0) {
|
|
607
636
|
return Object.keys(group)
|
|
608
637
|
.filter(key => !this.isAggregateFieldKey(key, aggregateAliases))
|
|
609
638
|
.map(key => group[key])
|
|
610
639
|
.join('_');
|
|
611
640
|
}
|
|
612
641
|
|
|
613
|
-
return
|
|
642
|
+
return fieldValues
|
|
643
|
+
.map(({ rawVal, alias }) => this.formatGroupValue(rawVal, groupFormatMap[alias]))
|
|
644
|
+
.join('_');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
getGroupValue(
|
|
648
|
+
group: any,
|
|
649
|
+
groupByFields: string[],
|
|
650
|
+
groupAliasMap: Record<string, string>
|
|
651
|
+
): any {
|
|
652
|
+
const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
|
|
653
|
+
if (fieldValues.length === 1) return this.normalizeGroupValue(fieldValues[0].rawVal, fieldValues[0].granularity);
|
|
654
|
+
return fieldValues.map(({ rawVal, granularity }) => this.normalizeGroupValue(rawVal, granularity)).join('_');
|
|
614
655
|
}
|
|
615
656
|
|
|
616
657
|
createGroupRecords(group: any, aggregateAliases: Set<string>, groupData: any, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
|
|
@@ -622,6 +663,7 @@ export class CrudHelperService {
|
|
|
622
663
|
}
|
|
623
664
|
createGroupMeta(group: any, aggregateAliases: Set<string>, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
|
|
624
665
|
const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
|
|
666
|
+
const groupValue = this.getGroupValue(group, groupByFields, groupAliasMap);
|
|
625
667
|
const groupAggregateValues = {}
|
|
626
668
|
for (const key in group) {
|
|
627
669
|
if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, aggregateAliases)) {
|
|
@@ -631,6 +673,7 @@ export class CrudHelperService {
|
|
|
631
673
|
}
|
|
632
674
|
return {
|
|
633
675
|
groupName,
|
|
676
|
+
groupValue,
|
|
634
677
|
...groupAggregateValues
|
|
635
678
|
};
|
|
636
679
|
}
|
|
@@ -129,8 +129,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
|
|
133
|
-
const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate);
|
|
132
|
+
private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false, entityId?: number) {
|
|
133
|
+
const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate, entityId);
|
|
134
134
|
const validationErrors = fieldManager.validate(dto, files);
|
|
135
135
|
const errors = (validationErrors instanceof Promise) ? await validationErrors : validationErrors;
|
|
136
136
|
if (errors.length > 0) {
|
|
@@ -211,7 +211,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
211
211
|
// 2. Loop through the fields with a switch statement
|
|
212
212
|
// 3. Handle the fields based on field type
|
|
213
213
|
for (const field of fieldsToProcess) {
|
|
214
|
-
const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate);
|
|
214
|
+
const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate, id);
|
|
215
215
|
updateDto = transformed.dto;
|
|
216
216
|
hasMediaFields = transformed.hasMediaFields;
|
|
217
217
|
}
|
|
@@ -221,6 +221,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
221
221
|
const mergedEntity = this.repo.merge(entity, updateDto);
|
|
222
222
|
const savedEntity = await this.repo.save(mergedEntity) as T;
|
|
223
223
|
|
|
224
|
+
//FIXME: Skip the many-to-many, and instead fire differential updates and avoid loading the entire association graph for the ids
|
|
225
|
+
|
|
224
226
|
// 6. Save the media
|
|
225
227
|
if (hasMediaFields) {
|
|
226
228
|
await this.saveMedia(model, files, savedEntity);
|
|
@@ -287,7 +289,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
291
|
|
|
290
|
-
private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
|
|
292
|
+
private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false, entityId?: number) {
|
|
291
293
|
const commonOptions = { required: fieldMetadata.required && !isPartialUpdate, fieldName: fieldMetadata.name, isUpdate };
|
|
292
294
|
switch (fieldMetadata.type) {
|
|
293
295
|
case SolidFieldType.shortText: {
|
|
@@ -378,6 +380,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
378
380
|
entityManager,
|
|
379
381
|
inverseFieldName: fieldMetadata.relationCoModelFieldName,
|
|
380
382
|
inverseRelationCoModelFieldName: fieldMetadata.name,
|
|
383
|
+
entityId,
|
|
381
384
|
}
|
|
382
385
|
return new OneToManyRelationFieldCrudManager(oneToManyOptions);
|
|
383
386
|
}
|
|
@@ -390,6 +393,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
390
393
|
isInverseSide: false,
|
|
391
394
|
entityManager,
|
|
392
395
|
fieldName: fieldMetadata.name,
|
|
396
|
+
entityId,
|
|
393
397
|
}
|
|
394
398
|
return new ManyToManyRelationFieldCrudManager(manyToManyOptions);
|
|
395
399
|
}
|
|
@@ -402,6 +406,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
|
|
|
402
406
|
entityManager,
|
|
403
407
|
fieldName: fieldMetadata.relationCoModelFieldName,
|
|
404
408
|
relationCoModelFieldName: fieldMetadata.name,
|
|
409
|
+
entityId,
|
|
405
410
|
}
|
|
406
411
|
return new ManyToManyRelationFieldCrudManager(inverseManyToManyOptions);
|
|
407
412
|
}
|
|
@@ -1290,77 +1290,6 @@ export class FieldMetadataService implements OnApplicationBootstrap {
|
|
|
1290
1290
|
return fieldObject;
|
|
1291
1291
|
}
|
|
1292
1292
|
|
|
1293
|
-
// async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
|
|
1294
|
-
|
|
1295
|
-
// const { modelName, fieldName, fieldValue, s3KeyFieldName } = resolveS3UrlDto;
|
|
1296
|
-
|
|
1297
|
-
// // ------------------------------------------------
|
|
1298
|
-
// // 1. Load model metadata
|
|
1299
|
-
// // ------------------------------------------------
|
|
1300
|
-
// const modelRepo = this.dataSource.getRepository(ModelMetadata);
|
|
1301
|
-
// const model = await modelRepo.findOne({
|
|
1302
|
-
// where: { singularName: modelName },
|
|
1303
|
-
// relations: ['fields']
|
|
1304
|
-
// });
|
|
1305
|
-
|
|
1306
|
-
// if (!model) {
|
|
1307
|
-
// throw new NotFoundException(`Model ${modelName} not found`);
|
|
1308
|
-
// }
|
|
1309
|
-
|
|
1310
|
-
// // ------------------------------------------------
|
|
1311
|
-
// // 2. Validate the field we are filtering by
|
|
1312
|
-
// // ------------------------------------------------
|
|
1313
|
-
// const filterFieldMeta = model.fields.find(f => f.name === fieldName);
|
|
1314
|
-
// if (!filterFieldMeta) {
|
|
1315
|
-
// throw new NotFoundException(
|
|
1316
|
-
// `Field ${fieldName} not found in model ${modelName}`
|
|
1317
|
-
// );
|
|
1318
|
-
// }
|
|
1319
|
-
|
|
1320
|
-
// // ------------------------------------------------
|
|
1321
|
-
// // 3. Load the actual entity repository
|
|
1322
|
-
// // ------------------------------------------------
|
|
1323
|
-
// const entityRepo = this.dataSource.getRepository(model.singularName);
|
|
1324
|
-
|
|
1325
|
-
// // ------------------------------------------------
|
|
1326
|
-
// // 4. Query using fieldName = fieldValue
|
|
1327
|
-
// // ------------------------------------------------
|
|
1328
|
-
// const record = await entityRepo.findOne({
|
|
1329
|
-
// where: { [fieldName]: fieldValue }
|
|
1330
|
-
// });
|
|
1331
|
-
|
|
1332
|
-
// if (!record) {
|
|
1333
|
-
// throw new NotFoundException(
|
|
1334
|
-
// `${modelName} record not found for ${fieldName}="${fieldValue}"`
|
|
1335
|
-
// );
|
|
1336
|
-
// }
|
|
1337
|
-
|
|
1338
|
-
// // ------------------------------------------------
|
|
1339
|
-
// // 5. Extract S3 key from s3KeyFieldName
|
|
1340
|
-
// // ------------------------------------------------
|
|
1341
|
-
// const s3Key = record[s3KeyFieldName];
|
|
1342
|
-
|
|
1343
|
-
// if (!s3Key) {
|
|
1344
|
-
// throw new NotFoundException(
|
|
1345
|
-
// `Field "${s3KeyFieldName}" has no value in ${modelName}.${fieldName}="${fieldValue}"`
|
|
1346
|
-
// );
|
|
1347
|
-
// }
|
|
1348
|
-
|
|
1349
|
-
// // ------------------------------------------------
|
|
1350
|
-
// // 6. Generate signed or public URL
|
|
1351
|
-
// // ------------------------------------------------
|
|
1352
|
-
// let url = "";
|
|
1353
|
-
|
|
1354
|
-
// // TODO - get
|
|
1355
|
-
// if (resolveS3UrlDto.isPrivate == "true") {
|
|
1356
|
-
// const expiryInSeconds = 60 * 60;
|
|
1357
|
-
// url = await this.fileService.getSignedUrl(s3Key, expiryInSeconds, resolveS3UrlDto.bucketName);
|
|
1358
|
-
// } else {
|
|
1359
|
-
// url = `https://${resolveS3UrlDto.bucketName}.s3.${this.configService.get('S3_AWS_REGION_NAME')}.amazonaws.com/${s3Key}`;
|
|
1360
|
-
// }
|
|
1361
|
-
// return { url: url }
|
|
1362
|
-
// }
|
|
1363
|
-
|
|
1364
1293
|
async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
|
|
1365
1294
|
let url = "";
|
|
1366
1295
|
const normalizedKey = this.normalizeS3Key(resolveS3UrlDto.s3Key);
|
|
@@ -9,25 +9,17 @@ import { IFileService, WriteOptions, CopyOptions, UrlOptions } from './file-serv
|
|
|
9
9
|
/**
|
|
10
10
|
* Disk-based implementation of IFileService.
|
|
11
11
|
* Handles file operations on the local filesystem.
|
|
12
|
+
* Callers are responsible for providing complete paths (use DiskStoragePathBuilder for primary storage paths).
|
|
12
13
|
*/
|
|
13
14
|
@Injectable()
|
|
14
15
|
export class DiskFileService implements IFileService {
|
|
15
16
|
private readonly logger = new Logger(DiskFileService.name);
|
|
16
|
-
private readonly fileStorageDir: string;
|
|
17
17
|
private readonly baseUrl: string;
|
|
18
18
|
|
|
19
19
|
constructor() {
|
|
20
|
-
this.fileStorageDir = process.env.AB_MEDIA_FILE_STORAGE_DIR || 'uploads';
|
|
21
20
|
this.baseUrl = process.env.BASE_URL || '';
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
private resolvePath(filePath: string): string {
|
|
25
|
-
if (path.isAbsolute(filePath)) {
|
|
26
|
-
return filePath;
|
|
27
|
-
}
|
|
28
|
-
return `${this.fileStorageDir}/${filePath}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
/**
|
|
32
24
|
* Read file contents as Buffer
|
|
33
25
|
*/
|
|
@@ -50,10 +42,9 @@ export class DiskFileService implements IFileService {
|
|
|
50
42
|
* @returns Public URL of the written file
|
|
51
43
|
*/
|
|
52
44
|
async write(filePath: string, data: Buffer | string, options?: WriteOptions): Promise<string> {
|
|
53
|
-
|
|
54
|
-
await
|
|
55
|
-
|
|
56
|
-
return `${this.baseUrl}/${resolvedPath}`;
|
|
45
|
+
await this.ensureDirectoryExists(filePath);
|
|
46
|
+
await fsPromises.writeFile(filePath, data);
|
|
47
|
+
return `${this.baseUrl}/${filePath}`;
|
|
57
48
|
}
|
|
58
49
|
|
|
59
50
|
/**
|
|
@@ -62,12 +53,11 @@ export class DiskFileService implements IFileService {
|
|
|
62
53
|
* @returns Public URL of the written file
|
|
63
54
|
*/
|
|
64
55
|
async writeStream(filePath: string, stream: Readable, options?: WriteOptions): Promise<string> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const writeStream = fs.createWriteStream(resolvedPath);
|
|
56
|
+
await this.ensureDirectoryExists(filePath);
|
|
57
|
+
const writeStream = fs.createWriteStream(filePath);
|
|
68
58
|
await pipeline(stream, writeStream);
|
|
69
|
-
this.logger.debug(`File saved via stream: ${
|
|
70
|
-
return `${this.baseUrl}/${
|
|
59
|
+
this.logger.debug(`File saved via stream: ${filePath}`);
|
|
60
|
+
return `${this.baseUrl}/${filePath}`;
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
/**
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Injectable, Provider } from '@nestjs/common';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from '../settings/default-settings-provider.service';
|
|
4
|
+
|
|
5
|
+
export const FILE_STORAGE_PATH_BUILDER = Symbol('FILE_STORAGE_PATH_BUILDER');
|
|
6
|
+
|
|
7
|
+
export interface IStoragePathBuilder {
|
|
8
|
+
build(fileName: string): string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class DiskStoragePathBuilder implements IStoragePathBuilder {
|
|
13
|
+
private readonly base: string;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.base = process.env.AB_MEDIA_FILE_STORAGE_DIR || DEFAULT_MEDIA_FILE_STORAGE_DIR;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
build(fileName: string): string {
|
|
20
|
+
if (path.isAbsolute(fileName) || fileName.startsWith(`${this.base}/`)) {
|
|
21
|
+
return fileName;
|
|
22
|
+
}
|
|
23
|
+
return `${this.base}/${fileName}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Injectable()
|
|
28
|
+
export class S3StoragePathBuilder implements IStoragePathBuilder {
|
|
29
|
+
private readonly bucket: string;
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
this.bucket = process.env.S3_DEFAULT_BUCKET ?? '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
build(fileName: string): string {
|
|
36
|
+
if (fileName.includes(':')) {
|
|
37
|
+
return fileName;
|
|
38
|
+
}
|
|
39
|
+
return `${this.bucket}:${fileName}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const StoragePathBuilderFactory: Provider = {
|
|
44
|
+
provide: FILE_STORAGE_PATH_BUILDER,
|
|
45
|
+
useFactory: (disk: DiskStoragePathBuilder, s3: S3StoragePathBuilder): IStoragePathBuilder => {
|
|
46
|
+
const defaultService = process.env.DEFAULT_FILE_SERVICE ?? 'disk';
|
|
47
|
+
switch (defaultService.toLowerCase()) {
|
|
48
|
+
case 's3':
|
|
49
|
+
return s3;
|
|
50
|
+
case 'disk':
|
|
51
|
+
default:
|
|
52
|
+
return disk;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
inject: [DiskStoragePathBuilder, S3StoragePathBuilder],
|
|
56
|
+
};
|
|
@@ -2,6 +2,8 @@ import { forwardRef, Inject, Injectable, NotFoundException } from '@nestjs/commo
|
|
|
2
2
|
import { ModuleRef } from "@nestjs/core";
|
|
3
3
|
import { InjectEntityManager } from '@nestjs/typeorm';
|
|
4
4
|
import { EntityManager, In } from 'typeorm';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
|
|
5
7
|
import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
|
|
6
8
|
|
|
7
9
|
import { ConfigService } from '@nestjs/config';
|
|
@@ -52,7 +54,7 @@ export class MediaService extends CRUDService<Media> {
|
|
|
52
54
|
|
|
53
55
|
for (const media of data.records) {
|
|
54
56
|
if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
|
|
55
|
-
media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${
|
|
57
|
+
media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePathForDisk(media.relativeUri)}`;
|
|
56
58
|
} else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
|
|
57
59
|
media.relativeUri = this.getAwsS3FullFilePath(
|
|
58
60
|
media.relativeUri,
|
|
@@ -63,7 +65,7 @@ export class MediaService extends CRUDService<Media> {
|
|
|
63
65
|
}
|
|
64
66
|
// data.records.forEach((media: Media) => {
|
|
65
67
|
// if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
|
|
66
|
-
// media.relativeUri = `${process.env.BASE_URL}/${
|
|
68
|
+
// media.relativeUri = `${process.env.BASE_URL}/${this.getFileSysytemFullFilePath(media.relativeUri)}`;
|
|
67
69
|
// } else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
|
|
68
70
|
// media.relativeUri = this.getAwsS3FullFilePath(
|
|
69
71
|
// media.relativeUri,
|
|
@@ -78,7 +80,7 @@ export class MediaService extends CRUDService<Media> {
|
|
|
78
80
|
for (const group of data.groupRecords) {
|
|
79
81
|
for (const media of group.groupData.records) {
|
|
80
82
|
if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
|
|
81
|
-
media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${
|
|
83
|
+
media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePathForDisk(media.relativeUri)}`;
|
|
82
84
|
}
|
|
83
85
|
else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
|
|
84
86
|
media.relativeUri = this.getAwsS3FullFilePath(media.relativeUri, media.mediaStorageProviderMetadata.bucketName, media.mediaStorageProviderMetadata.region);
|
|
@@ -129,7 +131,7 @@ export class MediaService extends CRUDService<Media> {
|
|
|
129
131
|
|
|
130
132
|
switch (createDto.mediaStorageProviderMetadata.type) {
|
|
131
133
|
case MediaStorageProviderType.Filesystem:
|
|
132
|
-
const fileStoragePath =
|
|
134
|
+
const fileStoragePath = this.getFullFilePathForDisk(this.getFileName(file));
|
|
133
135
|
await this.diskFileService.copy(file.path, fileStoragePath);
|
|
134
136
|
createDto['relativeUri'] = this.getFileName(file);
|
|
135
137
|
break;
|
|
@@ -189,9 +191,13 @@ export class MediaService extends CRUDService<Media> {
|
|
|
189
191
|
}
|
|
190
192
|
//TODO: Move this to a app builder config
|
|
191
193
|
|
|
192
|
-
private
|
|
193
|
-
const
|
|
194
|
-
|
|
194
|
+
private getFullFilePathForDisk(fileName: string): string {
|
|
195
|
+
const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
|
|
196
|
+
|| DEFAULT_MEDIA_FILE_STORAGE_DIR;
|
|
197
|
+
if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
|
|
198
|
+
return fileName;
|
|
199
|
+
}
|
|
200
|
+
return `${base}/${fileName}`;
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
private getAwsS3FullFilePath(awsMediaurl: string, bucketName: string, regionName: string): string {
|
|
@@ -43,7 +43,7 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
43
43
|
const region = this.getEffectiveRegion(storageMeta.region);
|
|
44
44
|
if (storageMeta.isPublic === false) {
|
|
45
45
|
// Generate signed URL
|
|
46
|
-
const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60;
|
|
46
|
+
const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60;
|
|
47
47
|
m['_full_url'] = await this.s3FileService.getUrl(`${storageMeta?.bucketName}:${m.relativeUri}`, { expiresIn: expiryInSeconds, region });
|
|
48
48
|
} else {
|
|
49
49
|
// Public S3 or local filesystem: use normal URL
|
|
@@ -7,7 +7,9 @@ import { MediaStorageProvider } from "src/interfaces";
|
|
|
7
7
|
import { MediaRepository } from "src/repository/media.repository";
|
|
8
8
|
import { DiskFileService } from "src/services/file";
|
|
9
9
|
import { Readable } from "stream";
|
|
10
|
+
import * as path from "path";
|
|
10
11
|
import { SettingService } from "../setting.service";
|
|
12
|
+
import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
|
|
11
13
|
import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
|
|
12
14
|
|
|
13
15
|
@Injectable()
|
|
@@ -34,7 +36,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
34
36
|
// media.forEach(m => {
|
|
35
37
|
// });
|
|
36
38
|
for (const m of media) {
|
|
37
|
-
m['_full_url'] = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${
|
|
39
|
+
m['_full_url'] = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePath(m.relativeUri)}`;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
|
|
@@ -48,7 +50,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
48
50
|
const result: Media[] = [];
|
|
49
51
|
for (const file of files) {
|
|
50
52
|
// Store the file in the configured file storage directory
|
|
51
|
-
const fileStoragePath =
|
|
53
|
+
const fileStoragePath = this.getFullFilePath(this.getFileName(file));
|
|
52
54
|
await this.fileService.copy(file.path, fileStoragePath);
|
|
53
55
|
await this.fileService.delete(file.path);
|
|
54
56
|
|
|
@@ -78,7 +80,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
78
80
|
for (const pair of streamPairs) {
|
|
79
81
|
const stream = pair[0];
|
|
80
82
|
const fileName = pair[1];
|
|
81
|
-
await this.fileService.writeStream(
|
|
83
|
+
await this.fileService.writeStream(this.getFullFilePath(fileName), stream);
|
|
82
84
|
const mediaEntity = await this.mediaRepository.createMedia({
|
|
83
85
|
//@ts-ignore
|
|
84
86
|
entityId: entity.id,
|
|
@@ -102,16 +104,19 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
|
|
|
102
104
|
this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
|
|
103
105
|
|
|
104
106
|
for (const media of existingMedia) {
|
|
105
|
-
await this.fileService.delete(
|
|
107
|
+
await this.fileService.delete(this.getFullFilePath(media.relativeUri));
|
|
106
108
|
}
|
|
107
109
|
// existingMedia.forEach(media => {
|
|
108
110
|
// });
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
private
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
private getFullFilePath(fileName: string): string {
|
|
114
|
+
const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
|
|
115
|
+
|| DEFAULT_MEDIA_FILE_STORAGE_DIR;
|
|
116
|
+
if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
|
|
117
|
+
return fileName;
|
|
118
|
+
}
|
|
119
|
+
return `${base}/${fileName}`;
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
private getFileName(file: Express.Multer.File): string {
|