@appweaver/core 1.0.0
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/LICENSE +1 -0
- package/README.md +7 -0
- package/app/application.d.ts +43 -0
- package/app/application.js +112 -0
- package/app/create-app.d.ts +32 -0
- package/app/create-app.js +38 -0
- package/app/index.d.ts +2 -0
- package/app/index.js +18 -0
- package/app/lifecycle-manager.d.ts +20 -0
- package/app/lifecycle-manager.js +42 -0
- package/app/load-modules.d.ts +10 -0
- package/app/load-modules.js +50 -0
- package/app/load-providers.d.ts +8 -0
- package/app/load-providers.js +33 -0
- package/cache/cache-service.d.ts +72 -0
- package/cache/cache-service.js +202 -0
- package/cache/cache.d.ts +12 -0
- package/cache/cache.js +184 -0
- package/cache/caching.d.ts +8 -0
- package/cache/caching.js +72 -0
- package/cache/eviction/eviction-index.d.ts +35 -0
- package/cache/eviction/eviction-index.js +2 -0
- package/cache/eviction/fifo-eviction-index.d.ts +7 -0
- package/cache/eviction/fifo-eviction-index.js +28 -0
- package/cache/eviction/index.d.ts +4 -0
- package/cache/eviction/index.js +20 -0
- package/cache/eviction/lfu-eviction-index.d.ts +8 -0
- package/cache/eviction/lfu-eviction-index.js +82 -0
- package/cache/eviction/lru-eviction-index.d.ts +8 -0
- package/cache/eviction/lru-eviction-index.js +81 -0
- package/cache/index.d.ts +2 -0
- package/cache/index.js +18 -0
- package/cache/memory-cache.d.ts +4 -0
- package/cache/memory-cache.js +12 -0
- package/cache/redis-cache.d.ts +4 -0
- package/cache/redis-cache.js +12 -0
- package/context/context.d.ts +2 -0
- package/context/context.js +13 -0
- package/context/dependency-injection.d.ts +98 -0
- package/context/dependency-injection.js +276 -0
- package/context/index.d.ts +2 -0
- package/context/index.js +18 -0
- package/database/create-client.d.ts +11 -0
- package/database/create-client.js +142 -0
- package/database/index.d.ts +1 -0
- package/database/index.js +17 -0
- package/database/prisma-database.d.ts +15 -0
- package/database/prisma-database.js +53 -0
- package/errors/error-handler.d.ts +3 -0
- package/errors/error-handler.js +24 -0
- package/errors/error-schema.d.ts +32 -0
- package/errors/error-schema.js +31 -0
- package/errors/http-error.d.ts +21 -0
- package/errors/http-error.js +33 -0
- package/errors/index.d.ts +3 -0
- package/errors/index.js +19 -0
- package/events/node-events.d.ts +7 -0
- package/events/node-events.js +39 -0
- package/export/export-service.d.ts +19 -0
- package/export/export-service.js +154 -0
- package/export/index.d.ts +1 -0
- package/export/index.js +17 -0
- package/factory/create-model.d.ts +2 -0
- package/factory/create-model.js +366 -0
- package/factory/create-policy.d.ts +2 -0
- package/factory/create-policy.js +16 -0
- package/factory/create-routes.d.ts +2 -0
- package/factory/create-routes.js +21 -0
- package/factory/create-service.d.ts +3 -0
- package/factory/create-service.js +97 -0
- package/factory/index.d.ts +4 -0
- package/factory/index.js +20 -0
- package/health/health-routes.d.ts +2 -0
- package/health/health-routes.js +36 -0
- package/health/health-schema.d.ts +45 -0
- package/health/health-schema.js +52 -0
- package/health/health-service.d.ts +38 -0
- package/health/health-service.js +82 -0
- package/health/index.d.ts +2 -0
- package/health/index.js +18 -0
- package/index.d.ts +15 -0
- package/index.js +31 -0
- package/mailer/email-service.d.ts +36 -0
- package/mailer/email-service.js +89 -0
- package/mailer/index.d.ts +1 -0
- package/mailer/index.js +17 -0
- package/mailer/json-mailer.d.ts +7 -0
- package/mailer/json-mailer.js +32 -0
- package/mailer/smtp-mailer.d.ts +7 -0
- package/mailer/smtp-mailer.js +43 -0
- package/memory/in-memory.d.ts +24 -0
- package/memory/in-memory.js +164 -0
- package/memory/redis.d.ts +26 -0
- package/memory/redis.js +158 -0
- package/package.json +68 -0
- package/prisma/client/browser.d.ts +34 -0
- package/prisma/client/browser.js +56 -0
- package/prisma/client/client.d.ts +53 -0
- package/prisma/client/client.js +70 -0
- package/prisma/client/commonInputTypes.d.ts +395 -0
- package/prisma/client/commonInputTypes.js +11 -0
- package/prisma/client/enums.d.ts +1 -0
- package/prisma/client/enums.js +11 -0
- package/prisma/client/internal/class.d.ts +187 -0
- package/prisma/client/internal/class.js +85 -0
- package/prisma/client/internal/prismaNamespace.d.ts +987 -0
- package/prisma/client/internal/prismaNamespace.js +193 -0
- package/prisma/client/internal/prismaNamespaceBrowser.d.ts +125 -0
- package/prisma/client/internal/prismaNamespaceBrowser.js +164 -0
- package/prisma/client/models/ApiKey.d.ts +1167 -0
- package/prisma/client/models/ApiKey.js +2 -0
- package/prisma/client/models/File.d.ts +1248 -0
- package/prisma/client/models/File.js +2 -0
- package/prisma/client/models/OneTimeToken.d.ts +1083 -0
- package/prisma/client/models/OneTimeToken.js +2 -0
- package/prisma/client/models/Permission.d.ts +1209 -0
- package/prisma/client/models/Permission.js +2 -0
- package/prisma/client/models/Role.d.ts +1209 -0
- package/prisma/client/models/Role.js +2 -0
- package/prisma/client/models/Seeder.d.ts +1023 -0
- package/prisma/client/models/Seeder.js +2 -0
- package/prisma/client/models.d.ts +7 -0
- package/prisma/client/models.js +2 -0
- package/queue/bull-queue.d.ts +37 -0
- package/queue/bull-queue.js +207 -0
- package/queue/memory-queue.d.ts +54 -0
- package/queue/memory-queue.js +283 -0
- package/resource/index.d.ts +4 -0
- package/resource/index.js +20 -0
- package/resource/resource-loader.d.ts +26 -0
- package/resource/resource-loader.js +161 -0
- package/resource/resource-routes.d.ts +3 -0
- package/resource/resource-routes.js +174 -0
- package/resource/resource-schema.d.ts +33 -0
- package/resource/resource-schema.js +186 -0
- package/resource/resource-service.d.ts +89 -0
- package/resource/resource-service.js +772 -0
- package/resources.d.ts +9 -0
- package/resources.js +24 -0
- package/scheduler/cron-scheduler.d.ts +12 -0
- package/scheduler/cron-scheduler.js +73 -0
- package/security/account/account-routes.d.ts +2 -0
- package/security/account/account-routes.js +104 -0
- package/security/account/account-schema.d.ts +290 -0
- package/security/account/account-schema.js +166 -0
- package/security/account/account-service.d.ts +88 -0
- package/security/account/account-service.js +218 -0
- package/security/account/index.d.ts +3 -0
- package/security/account/index.js +19 -0
- package/security/api-key/api-key-auth.d.ts +4 -0
- package/security/api-key/api-key-auth.js +66 -0
- package/security/api-key/index.d.ts +1 -0
- package/security/api-key/index.js +17 -0
- package/security/auth-routes.d.ts +2 -0
- package/security/auth-routes.js +80 -0
- package/security/auth-schema.d.ts +215 -0
- package/security/auth-schema.js +129 -0
- package/security/auth-service.d.ts +117 -0
- package/security/auth-service.js +272 -0
- package/security/auth.d.ts +3 -0
- package/security/auth.js +127 -0
- package/security/basic/basic-auth.d.ts +4 -0
- package/security/basic/basic-auth.js +32 -0
- package/security/basic/index.d.ts +1 -0
- package/security/basic/index.js +17 -0
- package/security/create-auth-resources.d.ts +6 -0
- package/security/create-auth-resources.js +129 -0
- package/security/helper.d.ts +127 -0
- package/security/helper.js +329 -0
- package/security/index.d.ts +4 -0
- package/security/index.js +20 -0
- package/security/jwt/index.d.ts +2 -0
- package/security/jwt/index.js +18 -0
- package/security/jwt/jwt-auth.d.ts +4 -0
- package/security/jwt/jwt-auth.js +57 -0
- package/security/jwt/jwt-keys.d.ts +7 -0
- package/security/jwt/jwt-keys.js +43 -0
- package/security/oauth2/create-oauth2-plugin.d.ts +17 -0
- package/security/oauth2/create-oauth2-plugin.js +85 -0
- package/security/oauth2/index.d.ts +3 -0
- package/security/oauth2/index.js +19 -0
- package/security/oauth2/oauth2-custom.d.ts +7 -0
- package/security/oauth2/oauth2-custom.js +40 -0
- package/security/oauth2/oauth2-facebook.d.ts +1 -0
- package/security/oauth2/oauth2-facebook.js +30 -0
- package/security/oauth2/oauth2-google.d.ts +1 -0
- package/security/oauth2/oauth2-google.js +28 -0
- package/security/oauth2/oauth2-schema.d.ts +10 -0
- package/security/oauth2/oauth2-schema.js +53 -0
- package/security/recaptcha/index.d.ts +2 -0
- package/security/recaptcha/index.js +18 -0
- package/security/recaptcha/recaptcha-verify.d.ts +1 -0
- package/security/recaptcha/recaptcha-verify.js +33 -0
- package/security/recaptcha/recaptcha.d.ts +2 -0
- package/security/recaptcha/recaptcha.js +22 -0
- package/security/resources/api-key/model.d.ts +2 -0
- package/security/resources/api-key/model.js +63 -0
- package/security/resources/api-key/policy.d.ts +2 -0
- package/security/resources/api-key/policy.js +26 -0
- package/security/resources/api-key/routes.d.ts +2 -0
- package/security/resources/api-key/routes.js +15 -0
- package/security/resources/api-key/service.d.ts +3 -0
- package/security/resources/api-key/service.js +33 -0
- package/security/resources/one-time-token/model.d.ts +2 -0
- package/security/resources/one-time-token/model.js +32 -0
- package/security/resources/permission/model.d.ts +2 -0
- package/security/resources/permission/model.js +16 -0
- package/security/resources/role/model.d.ts +2 -0
- package/security/resources/role/model.js +22 -0
- package/security/store/database-security-store.d.ts +5 -0
- package/security/store/database-security-store.js +51 -0
- package/security/store/redis-security-store.d.ts +5 -0
- package/security/store/redis-security-store.js +32 -0
- package/seeder/create-seeder.d.ts +21 -0
- package/seeder/create-seeder.js +17 -0
- package/seeder/index.d.ts +2 -0
- package/seeder/index.js +18 -0
- package/seeder/resources/seeder/model.d.ts +2 -0
- package/seeder/resources/seeder/model.js +38 -0
- package/seeder/seeder.d.ts +20 -0
- package/seeder/seeder.js +175 -0
- package/server/create-server.d.ts +20 -0
- package/server/create-server.js +160 -0
- package/server/index.d.ts +4 -0
- package/server/index.js +20 -0
- package/server/info-route.d.ts +2 -0
- package/server/info-route.js +18 -0
- package/server/info-schema.d.ts +37 -0
- package/server/info-schema.js +19 -0
- package/server/register-model.d.ts +2 -0
- package/server/register-model.js +12 -0
- package/server/register-plugin.d.ts +2 -0
- package/server/register-plugin.js +13 -0
- package/server/register-route.d.ts +3 -0
- package/server/register-route.js +74 -0
- package/server/schemas.d.ts +3 -0
- package/server/schemas.js +30 -0
- package/server/swagger.d.ts +3 -0
- package/server/swagger.js +122 -0
- package/storage/file-route.d.ts +2 -0
- package/storage/file-route.js +39 -0
- package/storage/file-schema.d.ts +9 -0
- package/storage/file-schema.js +42 -0
- package/storage/file-service.d.ts +81 -0
- package/storage/file-service.js +384 -0
- package/storage/filesystem-storage.d.ts +10 -0
- package/storage/filesystem-storage.js +128 -0
- package/storage/index.d.ts +2 -0
- package/storage/index.js +18 -0
- package/storage/resources/file/model.d.ts +2 -0
- package/storage/resources/file/model.js +75 -0
- package/types/application-context.d.ts +20 -0
- package/types/application-context.js +2 -0
- package/types/auth.d.ts +31 -0
- package/types/auth.js +2 -0
- package/types/fastify-extensions.d.ts +24 -0
- package/types/fastify-extensions.js +4 -0
- package/types/generated.d.ts +193 -0
- package/types/generated.js +3 -0
- package/types/index.d.ts +4 -0
- package/types/index.js +20 -0
- package/types/server.d.ts +4 -0
- package/types/server.js +2 -0
- package/utils/export-util.d.ts +8 -0
- package/utils/export-util.js +25 -0
- package/utils/file-util.d.ts +64 -0
- package/utils/file-util.js +141 -0
- package/utils/index.d.ts +3 -0
- package/utils/index.js +19 -0
- package/utils/schema-util.d.ts +20 -0
- package/utils/schema-util.js +37 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResourceService = void 0;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
const common_1 = require("@appweaver/common");
|
|
6
|
+
const context_1 = require("../context");
|
|
7
|
+
const security_1 = require("../security");
|
|
8
|
+
const cache_1 = require("../cache");
|
|
9
|
+
const errors_1 = require("../errors");
|
|
10
|
+
class ResourceService {
|
|
11
|
+
modelName;
|
|
12
|
+
/** @internal */
|
|
13
|
+
_db = (0, context_1.inject)(common_1.Database);
|
|
14
|
+
/** @internal */
|
|
15
|
+
_events = (0, context_1.inject)(common_1.Events);
|
|
16
|
+
/** @internal */
|
|
17
|
+
_cacheService = (0, context_1.inject)(cache_1.CacheService);
|
|
18
|
+
/** @internal */
|
|
19
|
+
_client;
|
|
20
|
+
constructor(modelName) {
|
|
21
|
+
this.modelName = modelName;
|
|
22
|
+
this._client = this._db.client()[(0, common_1.uncapitalize)(modelName)];
|
|
23
|
+
if (!this._client) {
|
|
24
|
+
throw new Error(`ResourceService initialized with invalid model name: ${modelName}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
get client() {
|
|
28
|
+
return this._client;
|
|
29
|
+
}
|
|
30
|
+
async find(id) {
|
|
31
|
+
const restrictions = await this.readRestrictions('find', id);
|
|
32
|
+
const includeRelations = this.mapRelationInclusions('find');
|
|
33
|
+
let resource;
|
|
34
|
+
try {
|
|
35
|
+
resource = await this._client.findFirst({
|
|
36
|
+
where: { id, ...restrictions },
|
|
37
|
+
include: includeRelations
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
throw new errors_1.HttpError(`${this._client.name} find error`, 500, e);
|
|
42
|
+
}
|
|
43
|
+
if (!resource || resource.id !== id) {
|
|
44
|
+
throw new errors_1.HttpError(`${this._client.name} data not found`, 404);
|
|
45
|
+
}
|
|
46
|
+
const access = await this.checkAccess('find', resource);
|
|
47
|
+
if (!access) {
|
|
48
|
+
throw new errors_1.HttpError(`${this._client.name} data access is forbidden`, 403);
|
|
49
|
+
}
|
|
50
|
+
this._events.emitResourceEvent(this._client.name, 'find', {
|
|
51
|
+
current: resource
|
|
52
|
+
});
|
|
53
|
+
return this.projectResource(resource);
|
|
54
|
+
}
|
|
55
|
+
async query(filter = {}, page = 1, size = 50, sort = '-createdAt,id') {
|
|
56
|
+
const restrictions = await this.readRestrictions('query', filter);
|
|
57
|
+
const textSearch = this.extractTextSearchQuery(filter);
|
|
58
|
+
const mappedFilter = this.mapQueryFilter(filter);
|
|
59
|
+
const query = { AND: [mappedFilter, textSearch, restrictions] };
|
|
60
|
+
const includeRelations = this.mapRelationInclusions('query');
|
|
61
|
+
const orderBy = this.mapSortValues(sort);
|
|
62
|
+
let resources;
|
|
63
|
+
let totalCount;
|
|
64
|
+
try {
|
|
65
|
+
[resources, totalCount] = await this._db.client().$transaction([
|
|
66
|
+
this._client.findMany({
|
|
67
|
+
where: { ...query },
|
|
68
|
+
include: includeRelations,
|
|
69
|
+
skip: (page - 1) * size,
|
|
70
|
+
take: size,
|
|
71
|
+
orderBy
|
|
72
|
+
}),
|
|
73
|
+
this._client.count({
|
|
74
|
+
where: { ...query }
|
|
75
|
+
})
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
throw new errors_1.HttpError(`${this._client.name} query error`, 500, e);
|
|
80
|
+
}
|
|
81
|
+
this._events.emitResourceEvent(this._client.name, 'query', {
|
|
82
|
+
current: resources
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
resultCount: resources.length,
|
|
86
|
+
totalCount,
|
|
87
|
+
items: resources.map((resource) => this.projectResource(resource))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async aggregate(filter = {}, select, dateField = 'createdAt', from, to, step, safeIncrement = true) {
|
|
91
|
+
const toDate = (0, date_fns_1.parseISO)(to ?? new Date().toISOString());
|
|
92
|
+
const fromDate = from ? (0, date_fns_1.parseISO)(from) : (0, date_fns_1.subDays)(toDate, 7);
|
|
93
|
+
const iterator = this.makeAggregationIterator(fromDate, toDate, step, safeIncrement);
|
|
94
|
+
const dateRanges = [];
|
|
95
|
+
let currentDate = (0, date_fns_1.parseISO)(fromDate.toISOString());
|
|
96
|
+
while (currentDate < toDate) {
|
|
97
|
+
const date = currentDate;
|
|
98
|
+
const median = iterator.addPeriod(date, (iterator.step - 1) / 2);
|
|
99
|
+
currentDate = iterator.addPeriod(currentDate, iterator.step);
|
|
100
|
+
dateRanges.push({ from: date, to: currentDate, median });
|
|
101
|
+
}
|
|
102
|
+
const aggregateOperations = this.mapAggregationValues(select);
|
|
103
|
+
const restrictions = await this.readRestrictions('aggregate', filter);
|
|
104
|
+
const textSearch = this.extractTextSearchQuery(filter);
|
|
105
|
+
const mappedFilter = this.mapQueryFilter(filter);
|
|
106
|
+
const query = { AND: [mappedFilter, textSearch, restrictions] };
|
|
107
|
+
let total = {};
|
|
108
|
+
let items = [];
|
|
109
|
+
try {
|
|
110
|
+
[total, items] = await this._db.client().$transaction(async (tx) => {
|
|
111
|
+
const txModel = tx[this._client.name];
|
|
112
|
+
const overall = await txModel.aggregate({
|
|
113
|
+
...aggregateOperations,
|
|
114
|
+
where: {
|
|
115
|
+
AND: [
|
|
116
|
+
query,
|
|
117
|
+
{
|
|
118
|
+
[dateField]: {
|
|
119
|
+
gte: fromDate,
|
|
120
|
+
lt: toDate
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Skip executing the same query if only one range value is generated
|
|
127
|
+
if (dateRanges.length === 1) {
|
|
128
|
+
return [overall, [overall]];
|
|
129
|
+
}
|
|
130
|
+
const ranges = await Promise.all(dateRanges.map((dateRange) => txModel.aggregate({
|
|
131
|
+
...aggregateOperations,
|
|
132
|
+
where: {
|
|
133
|
+
AND: [
|
|
134
|
+
query,
|
|
135
|
+
{
|
|
136
|
+
[dateField]: {
|
|
137
|
+
gte: dateRange.from,
|
|
138
|
+
lt: dateRange.to
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
})));
|
|
144
|
+
return [overall, ranges];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
throw new errors_1.HttpError('Error on results aggregation', 500, e);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
total: this.mapAggregationValues(total, true),
|
|
152
|
+
items: items.map((item, index) => ({
|
|
153
|
+
date: dateRanges[index].median,
|
|
154
|
+
result: this.mapAggregationValues(item, true)
|
|
155
|
+
}))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async create(data) {
|
|
159
|
+
const createdBy = this.createdByConnect();
|
|
160
|
+
const restrictions = await this.writeRestrictions('create', data);
|
|
161
|
+
const createData = (0, common_1.removeUndefined)({
|
|
162
|
+
...data,
|
|
163
|
+
...restrictions
|
|
164
|
+
});
|
|
165
|
+
const access = await this.checkAccess('create', createData);
|
|
166
|
+
if (!access) {
|
|
167
|
+
throw new errors_1.HttpError(`${this._client.name} create action is forbidden`, 403);
|
|
168
|
+
}
|
|
169
|
+
const sanitizedData = this.sanitizeData('create', createData);
|
|
170
|
+
const connectRelations = this.mapRelationActions('create', sanitizedData);
|
|
171
|
+
const includeRelations = this.mapRelationInclusions('create');
|
|
172
|
+
let resource;
|
|
173
|
+
try {
|
|
174
|
+
resource = await this._client.create({
|
|
175
|
+
data: {
|
|
176
|
+
...sanitizedData,
|
|
177
|
+
...connectRelations,
|
|
178
|
+
createdBy
|
|
179
|
+
},
|
|
180
|
+
include: includeRelations
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
throw new errors_1.HttpError(`${this._client.name} create error`, 500, e);
|
|
185
|
+
}
|
|
186
|
+
await this._cacheService.invalidateCache(this._client.name, 'create');
|
|
187
|
+
this._events.emitResourceEvent(this._client.name, 'create', {
|
|
188
|
+
current: resource
|
|
189
|
+
});
|
|
190
|
+
return this.projectResource(resource);
|
|
191
|
+
}
|
|
192
|
+
async update(id, data) {
|
|
193
|
+
const readRestrictions = await this.readRestrictions('update', {
|
|
194
|
+
id,
|
|
195
|
+
...data
|
|
196
|
+
});
|
|
197
|
+
const writeRestrictions = await this.writeRestrictions('update', {
|
|
198
|
+
id,
|
|
199
|
+
...data
|
|
200
|
+
});
|
|
201
|
+
const updateData = (0, common_1.removeUndefined)({
|
|
202
|
+
...data,
|
|
203
|
+
...writeRestrictions
|
|
204
|
+
});
|
|
205
|
+
const sanitizedData = this.sanitizeData('update', updateData);
|
|
206
|
+
const includeRelations = this.mapRelationInclusions('update');
|
|
207
|
+
let updateResource;
|
|
208
|
+
let resource;
|
|
209
|
+
try {
|
|
210
|
+
[updateResource, resource] = await this._db
|
|
211
|
+
.client()
|
|
212
|
+
.$transaction(async (tx) => {
|
|
213
|
+
const txModel = tx[this._client.name];
|
|
214
|
+
const current = await txModel.findFirst({
|
|
215
|
+
where: { id, ...readRestrictions },
|
|
216
|
+
include: includeRelations
|
|
217
|
+
});
|
|
218
|
+
if (!current || current.id !== id) {
|
|
219
|
+
throw new errors_1.HttpError(`${this._client.name} data not found`, 404);
|
|
220
|
+
}
|
|
221
|
+
const access = await this.checkAccess('update', current);
|
|
222
|
+
if (!access) {
|
|
223
|
+
throw new errors_1.HttpError(`${this._client.name} update action is forbidden`, 403);
|
|
224
|
+
}
|
|
225
|
+
const setRelations = this.mapRelationActions('update', sanitizedData, current);
|
|
226
|
+
const updated = await txModel.update({
|
|
227
|
+
where: { id },
|
|
228
|
+
include: includeRelations,
|
|
229
|
+
data: { ...sanitizedData, ...setRelations }
|
|
230
|
+
});
|
|
231
|
+
return [current, updated];
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
if (e instanceof errors_1.HttpError) {
|
|
236
|
+
throw e;
|
|
237
|
+
}
|
|
238
|
+
throw new errors_1.HttpError(`${this._client.name} update error`, 500, e);
|
|
239
|
+
}
|
|
240
|
+
await this._cacheService.invalidateCache(this._client.name, 'update');
|
|
241
|
+
this._events.emitResourceEvent(this._client.name, 'update', {
|
|
242
|
+
previous: updateResource,
|
|
243
|
+
current: resource
|
|
244
|
+
});
|
|
245
|
+
return this.projectResource(resource);
|
|
246
|
+
}
|
|
247
|
+
async delete(id) {
|
|
248
|
+
const restrictions = await this.readRestrictions('delete', id);
|
|
249
|
+
let resource;
|
|
250
|
+
try {
|
|
251
|
+
resource = await this._db.client().$transaction(async (tx) => {
|
|
252
|
+
const txModel = tx[this._client.name];
|
|
253
|
+
const current = await txModel.findFirst({
|
|
254
|
+
where: { id, ...restrictions }
|
|
255
|
+
});
|
|
256
|
+
if (!current || current.id !== id) {
|
|
257
|
+
throw new errors_1.HttpError(`${this._client.name} data not found`, 404);
|
|
258
|
+
}
|
|
259
|
+
const access = await this.checkAccess('delete', current);
|
|
260
|
+
if (!access) {
|
|
261
|
+
throw new errors_1.HttpError(`${this._client.name} delete action is forbidden`, 403);
|
|
262
|
+
}
|
|
263
|
+
const includeRelations = this.mapRelationInclusions('delete');
|
|
264
|
+
return await txModel.delete({
|
|
265
|
+
where: { id },
|
|
266
|
+
include: includeRelations
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
if (e instanceof errors_1.HttpError) {
|
|
272
|
+
throw e;
|
|
273
|
+
}
|
|
274
|
+
throw new errors_1.HttpError(`${this._client.name} delete error`, 500, e);
|
|
275
|
+
}
|
|
276
|
+
await this._cacheService.invalidateCache(this._client.name, 'delete');
|
|
277
|
+
this._events.emitResourceEvent(this._client.name, 'delete', {
|
|
278
|
+
current: resource
|
|
279
|
+
});
|
|
280
|
+
return this.projectResource(resource);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* This method should be overridden with custom logic for restricting read
|
|
284
|
+
* operations on specific data for currently logged-in user and other
|
|
285
|
+
* authorization rules. The returned object will be applied as a filter on
|
|
286
|
+
* all actions (except create action) which will prevent unwanted data access
|
|
287
|
+
* and modifications. This method can also cancel current action by throwing
|
|
288
|
+
* an error, recommended is {@link HttpError } with appropriate HTTP error
|
|
289
|
+
* code.
|
|
290
|
+
*
|
|
291
|
+
* @param action The called action method on this service (find, query,
|
|
292
|
+
* aggregate, update, or delete)
|
|
293
|
+
* @param data The passed data to the called function, can be number or
|
|
294
|
+
* object. If the data is a type of number, then it represents the resource
|
|
295
|
+
* id, otherwise it depends on the action and can be one of the following:
|
|
296
|
+
*
|
|
297
|
+
* - query and aggregate -> filter object
|
|
298
|
+
* - update -> combined id and the data object (i.e. { id, ...data })
|
|
299
|
+
*
|
|
300
|
+
* For other actions (find and delete) it represents the resource id.
|
|
301
|
+
* @return The filter containing additional query restrictions.
|
|
302
|
+
*/
|
|
303
|
+
async readRestrictions(action, data) {
|
|
304
|
+
return {};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* This method should be overridden with custom logic for restricting write
|
|
308
|
+
* operations on specific data for currently logged-in users and other
|
|
309
|
+
* authorization rules. The returned object will be applied as a filter on
|
|
310
|
+
* all actions (create and update) to ensure that users can only modify
|
|
311
|
+
* data they are authorized to access. This method can also cancel the
|
|
312
|
+
* current action by throwing an error, with the recommended type being
|
|
313
|
+
* {@link HttpError} with the appropriate HTTP error code.
|
|
314
|
+
*
|
|
315
|
+
* @param action The called action method on this service (create or update)
|
|
316
|
+
* @param data The passed data to the called function, which should be an
|
|
317
|
+
* object representing the resource data to be created or updated. For
|
|
318
|
+
* update operations, this object will also include the resource ID.
|
|
319
|
+
*
|
|
320
|
+
* @return A partial object containing additional data restrictions to be
|
|
321
|
+
* applied, or an empty object if no restrictions are necessary.
|
|
322
|
+
*/
|
|
323
|
+
async writeRestrictions(action, data) {
|
|
324
|
+
return {};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* This method should provide logic for checking the access permissions for
|
|
328
|
+
* the provided resource object. If the resource should not be accessible by
|
|
329
|
+
* a currently authenticated user or other logic, this method should return
|
|
330
|
+
* false. Otherwise, it returns true and continues with the request execution.
|
|
331
|
+
*
|
|
332
|
+
* @param action The called action method on this service (find, query,
|
|
333
|
+
* aggregate, create, update, or delete)
|
|
334
|
+
* @param resource The resource object that is being check for access.
|
|
335
|
+
* @returns True if the access for resource is granted, false otherwise.
|
|
336
|
+
*/
|
|
337
|
+
async checkAccess(action, resource) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Constructs a query object for performing a text search on resources in the
|
|
342
|
+
* database based on the provided search text. This method will transform the
|
|
343
|
+
* search text into a format suitable for text search functionality, returning
|
|
344
|
+
* a query object that can be used to filter results.
|
|
345
|
+
*
|
|
346
|
+
* @param searchText The text string used for searching resources.
|
|
347
|
+
* @returns A query object that represents the conditions for the text
|
|
348
|
+
* search operation. This will be used by the database query methods to
|
|
349
|
+
* retrieve matching resources.
|
|
350
|
+
*/
|
|
351
|
+
textSearchQuery(searchText) {
|
|
352
|
+
return {};
|
|
353
|
+
}
|
|
354
|
+
extractTextSearchQuery(filter) {
|
|
355
|
+
if (filter.searchText) {
|
|
356
|
+
const searchQuery = this.textSearchQuery(filter.searchText);
|
|
357
|
+
delete filter.searchText;
|
|
358
|
+
return searchQuery;
|
|
359
|
+
}
|
|
360
|
+
return {};
|
|
361
|
+
}
|
|
362
|
+
mapSortValues(sort) {
|
|
363
|
+
const sortMap = {};
|
|
364
|
+
const resourceModel = (0, context_1.injectModel)(this._client.name);
|
|
365
|
+
const parts = sort.split(',');
|
|
366
|
+
for (const part of parts) {
|
|
367
|
+
let path = part.trim();
|
|
368
|
+
const order = path.startsWith('-') ? 'desc' : 'asc';
|
|
369
|
+
path = path.replace(/[-+]/g, '');
|
|
370
|
+
// Skip the default sort by createdAt field if not configured
|
|
371
|
+
if (resourceModel.config.audit?.createdAt === false &&
|
|
372
|
+
path === 'createdAt') {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
// Map relations count field sort order.
|
|
376
|
+
if ((0, common_1.isCountField)(path)) {
|
|
377
|
+
path = path.replace('Count', '._count');
|
|
378
|
+
}
|
|
379
|
+
(0, common_1.setValue)(sortMap, path, order);
|
|
380
|
+
}
|
|
381
|
+
return Object.entries(sortMap).map(([key, value]) => ({ [key]: value }));
|
|
382
|
+
}
|
|
383
|
+
mapAggregationValues(select, isOutput = false) {
|
|
384
|
+
const aggregationMap = {};
|
|
385
|
+
for (const field in select) {
|
|
386
|
+
const operators = select[field];
|
|
387
|
+
for (const operator in operators) {
|
|
388
|
+
const value = operators[operator];
|
|
389
|
+
const path = isOutput
|
|
390
|
+
? `${operator}.${field.substring(1)}`
|
|
391
|
+
: `_${operator}.${field}`;
|
|
392
|
+
(0, common_1.setValue)(aggregationMap, path, value);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return aggregationMap;
|
|
396
|
+
}
|
|
397
|
+
makeAggregationIterator(fromDate, toDate, step, safeIncrement = true) {
|
|
398
|
+
let stepAmount = step;
|
|
399
|
+
let incrementFn = date_fns_1.addSeconds;
|
|
400
|
+
if (!stepAmount) {
|
|
401
|
+
const diffInSeconds = (0, date_fns_1.differenceInSeconds)(toDate, fromDate);
|
|
402
|
+
const diffInMonths = (0, date_fns_1.differenceInMonths)(toDate, fromDate);
|
|
403
|
+
const diffInYears = (0, date_fns_1.differenceInYears)(toDate, fromDate);
|
|
404
|
+
stepAmount = 1;
|
|
405
|
+
// 1-second step if the difference is less than or equal to 1 minute
|
|
406
|
+
if (diffInSeconds <= 60) {
|
|
407
|
+
incrementFn = date_fns_1.addSeconds;
|
|
408
|
+
}
|
|
409
|
+
// 1-minute step if the difference is less than or equal to 1 hour
|
|
410
|
+
else if (diffInSeconds <= 3600) {
|
|
411
|
+
incrementFn = date_fns_1.addMinutes;
|
|
412
|
+
}
|
|
413
|
+
// 1-hour step if the difference is less than or equal to 1 day
|
|
414
|
+
else if (diffInSeconds <= 86400) {
|
|
415
|
+
incrementFn = date_fns_1.addHours;
|
|
416
|
+
}
|
|
417
|
+
// 1-day step if the difference is less than or equal to 1 month
|
|
418
|
+
else if (diffInMonths <= 1) {
|
|
419
|
+
incrementFn = date_fns_1.addDays;
|
|
420
|
+
}
|
|
421
|
+
// 1-month step if the difference is less than or equal to 1 year
|
|
422
|
+
else if (diffInYears <= 1) {
|
|
423
|
+
incrementFn = date_fns_1.addMonths;
|
|
424
|
+
}
|
|
425
|
+
// 1-year step if the difference is equal to 1 year or more
|
|
426
|
+
else {
|
|
427
|
+
incrementFn = date_fns_1.addYears;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// A higher-order function that adjusts date increments to account for
|
|
431
|
+
// changes in daylight saving time (DST). When incrementing dates in time
|
|
432
|
+
// zones that observe DST, this function ensures that the resulting date
|
|
433
|
+
// remains consistent with the original date's time zone offset.
|
|
434
|
+
const dstAgnosticFn = (fn) => {
|
|
435
|
+
return (date, amount) => {
|
|
436
|
+
const endDate = fn(date, amount);
|
|
437
|
+
return (0, date_fns_1.addMinutes)(endDate, date.getTimezoneOffset() - endDate.getTimezoneOffset());
|
|
438
|
+
};
|
|
439
|
+
};
|
|
440
|
+
return {
|
|
441
|
+
addPeriod: dstAgnosticFn(safeIncrement ? incrementFn : date_fns_1.addSeconds),
|
|
442
|
+
step: stepAmount
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
projectResource(resource) {
|
|
446
|
+
const projectedResource = this.projectVirtualFields(resource);
|
|
447
|
+
if (!(0, common_1.isObject)(projectedResource['_count'])) {
|
|
448
|
+
return projectedResource;
|
|
449
|
+
}
|
|
450
|
+
// Create new relation count properties on the resource object
|
|
451
|
+
for (const [key, count] of Object.entries(projectedResource['_count'])) {
|
|
452
|
+
projectedResource[(0, common_1.countFieldName)(key)] = count;
|
|
453
|
+
}
|
|
454
|
+
delete projectedResource['_count'];
|
|
455
|
+
return projectedResource;
|
|
456
|
+
}
|
|
457
|
+
projectVirtualFields(resource, resourceName) {
|
|
458
|
+
if (!resource) {
|
|
459
|
+
return resource;
|
|
460
|
+
}
|
|
461
|
+
const projectedVirtual = { ...resource };
|
|
462
|
+
const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
|
|
463
|
+
const relationsModel = resourceModel?.relationsModel;
|
|
464
|
+
const filesModel = resourceModel?.filesModel;
|
|
465
|
+
// Set output or default value for virtual fields
|
|
466
|
+
for (const [fieldName, virtual] of Object.entries(resourceModel?.config?.virtual ?? {})) {
|
|
467
|
+
const outputValue = virtual.output?.value;
|
|
468
|
+
if ((0, common_1.isFunction)(outputValue)) {
|
|
469
|
+
projectedVirtual[fieldName] = outputValue(projectedVirtual);
|
|
470
|
+
}
|
|
471
|
+
else if (outputValue) {
|
|
472
|
+
projectedVirtual[fieldName] = outputValue;
|
|
473
|
+
}
|
|
474
|
+
else if (virtual.required !== false) {
|
|
475
|
+
projectedVirtual[fieldName] =
|
|
476
|
+
virtual.default ?? (0, common_1.defaultScalarValue)(virtual);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// Recursively project virtual fields for nested relational objects
|
|
480
|
+
for (const key in projectedVirtual) {
|
|
481
|
+
const value = projectedVirtual[key];
|
|
482
|
+
const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
|
|
483
|
+
const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
|
|
484
|
+
if ((0, common_1.isObject)(value) || ((0, common_1.isArray)(value) && (0, common_1.isObject)(value[0]))) {
|
|
485
|
+
const resourceName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
|
|
486
|
+
if (resourceName) {
|
|
487
|
+
projectedVirtual[key] = (0, common_1.isArray)(value)
|
|
488
|
+
? value.map((item) => this.projectVirtualFields(item, resourceName))
|
|
489
|
+
: this.projectVirtualFields(value, resourceName);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return projectedVirtual;
|
|
494
|
+
}
|
|
495
|
+
sanitizeData(action, data, resourceName) {
|
|
496
|
+
const sanitizedData = { ...data };
|
|
497
|
+
const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
|
|
498
|
+
const relationsModel = resourceModel?.relationsModel;
|
|
499
|
+
const filesModel = resourceModel?.filesModel;
|
|
500
|
+
// Delete virtual fields from data object to avoid database errors
|
|
501
|
+
for (const fieldName of Object.keys(resourceModel?.config?.virtual ?? {})) {
|
|
502
|
+
delete sanitizedData[fieldName];
|
|
503
|
+
}
|
|
504
|
+
// Map default values for hidden scalars if a property is required without
|
|
505
|
+
// a provided default value, and scalar values are not set yet, this is only
|
|
506
|
+
// required for create actions
|
|
507
|
+
for (const [fieldName, scalar] of Object.entries(resourceModel?.config?.scalars ?? {})) {
|
|
508
|
+
if (action === 'create' &&
|
|
509
|
+
scalar.hidden &&
|
|
510
|
+
scalar.required !== false &&
|
|
511
|
+
scalar.default === undefined &&
|
|
512
|
+
sanitizedData[fieldName] === undefined) {
|
|
513
|
+
sanitizedData[fieldName] = (0, common_1.defaultScalarValue)(scalar);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Recursively sanitize nested objects and arrays of objects
|
|
517
|
+
for (const key in sanitizedData) {
|
|
518
|
+
const value = sanitizedData[key];
|
|
519
|
+
const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
|
|
520
|
+
const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
|
|
521
|
+
if ((0, common_1.isObject)(value) || ((0, common_1.isArray)(value) && (0, common_1.isObject)(value[0]))) {
|
|
522
|
+
const resourceName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
|
|
523
|
+
if (resourceName) {
|
|
524
|
+
sanitizedData[key] = (0, common_1.isArray)(value)
|
|
525
|
+
? value.map((item) => this.sanitizeData(item, resourceName))
|
|
526
|
+
: this.sanitizeData(action, value, resourceName);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return sanitizedData;
|
|
531
|
+
}
|
|
532
|
+
mapQueryFilter(filter, resourceName) {
|
|
533
|
+
const queryFilter = {};
|
|
534
|
+
const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
|
|
535
|
+
const readModel = resourceModel?.readModel;
|
|
536
|
+
const relationsModel = resourceModel?.relationsModel;
|
|
537
|
+
const filesModel = resourceModel?.filesModel;
|
|
538
|
+
for (const key in filter) {
|
|
539
|
+
const value = filter[key];
|
|
540
|
+
const readSchema = (0, common_1.extractSchemaProperties)(readModel, key);
|
|
541
|
+
const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
|
|
542
|
+
const fileSchema = (0, common_1.extractSchemaProperties)(filesModel, key);
|
|
543
|
+
const isArrayType = readSchema?.type === 'array' ||
|
|
544
|
+
relationSchema?.type === 'array' ||
|
|
545
|
+
fileSchema?.type === 'array';
|
|
546
|
+
const isArrayValue = (0, common_1.isArray)(value);
|
|
547
|
+
// Recursively map nested objects and handle arrays of objects
|
|
548
|
+
if ((0, common_1.isObject)(value) || (isArrayValue && (0, common_1.isObject)(value[0]))) {
|
|
549
|
+
const resourceName = (0, common_1.extractResourceName)(relationSchema ?? fileSchema);
|
|
550
|
+
if (resourceName) {
|
|
551
|
+
queryFilter[key] = isArrayValue
|
|
552
|
+
? value.map((item) => this.mapQueryFilter(item, resourceName))
|
|
553
|
+
: this.mapQueryFilter(value, resourceName);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Map ID values for both single and array types of relationships
|
|
557
|
+
else if (relationSchema || fileSchema) {
|
|
558
|
+
const queryId = { id: isArrayValue ? { in: value } : value };
|
|
559
|
+
queryFilter[key] = isArrayType ? { some: queryId } : queryId;
|
|
560
|
+
}
|
|
561
|
+
// Map fields without relationships, supporting array types
|
|
562
|
+
else if (readSchema) {
|
|
563
|
+
if (isArrayType) {
|
|
564
|
+
queryFilter[key] = isArrayValue ? { hasSome: value } : { has: value };
|
|
565
|
+
}
|
|
566
|
+
else if (isArrayValue) {
|
|
567
|
+
// For date and numeric types, apply range filtering with inclusive
|
|
568
|
+
// intervals
|
|
569
|
+
if (value.length > 0 &&
|
|
570
|
+
value.length <= 2 &&
|
|
571
|
+
(['number', 'integer'].includes(readSchema.type) ||
|
|
572
|
+
['date', 'date-time'].includes(readSchema.format))) {
|
|
573
|
+
queryFilter[key] = {
|
|
574
|
+
gte: value[0],
|
|
575
|
+
lte: value[1]
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
// Map array values for inclusion checks
|
|
579
|
+
else {
|
|
580
|
+
queryFilter[key] = { in: value };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// If no query filter was defined, assign the original value
|
|
585
|
+
if (queryFilter[key] === undefined) {
|
|
586
|
+
queryFilter[key] = value;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return queryFilter;
|
|
590
|
+
}
|
|
591
|
+
mapRelationInclusions(action, resourceName) {
|
|
592
|
+
const inclusion = {};
|
|
593
|
+
const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name);
|
|
594
|
+
const relationConfig = resourceModel.config.relations;
|
|
595
|
+
const fileConfig = resourceModel.config.files;
|
|
596
|
+
const relationModelProps = (0, common_1.extractSchemaProperties)(resourceModel.relationsModel);
|
|
597
|
+
const fileModelProps = (0, common_1.extractSchemaProperties)(resourceModel.filesModel);
|
|
598
|
+
// Add relation and file fields to the inclusion map if the include type is
|
|
599
|
+
// satisfied or the requested action is not specified. Also, add the count
|
|
600
|
+
// aggregation actions for relations if configured.
|
|
601
|
+
for (const key of Object.keys({
|
|
602
|
+
...relationModelProps,
|
|
603
|
+
...fileModelProps
|
|
604
|
+
})) {
|
|
605
|
+
const relationField = relationConfig?.[key] || fileConfig?.[key];
|
|
606
|
+
if (relationField?.output?.count) {
|
|
607
|
+
inclusion._count = inclusion._count ?? { select: {} };
|
|
608
|
+
inclusion._count.select[key] = true;
|
|
609
|
+
}
|
|
610
|
+
// Check if the relation should be included based on the output type
|
|
611
|
+
if (this.shouldIncludeRelation(relationField?.output?.type, action)) {
|
|
612
|
+
inclusion[key] = this.buildNestedInclusion(relationField, action);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return inclusion;
|
|
616
|
+
}
|
|
617
|
+
buildNestedInclusion(relationField, action) {
|
|
618
|
+
const nestedIncludeConfig = relationField?.output?.include;
|
|
619
|
+
if (!nestedIncludeConfig || Object.keys(nestedIncludeConfig).length === 0) {
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
const nestedInclusion = {};
|
|
623
|
+
for (const [nestedKey, nestedOutput] of Object.entries(nestedIncludeConfig)) {
|
|
624
|
+
// Check if the nested relation should be included
|
|
625
|
+
if (this.shouldIncludeRelation(nestedOutput?.type, action)) {
|
|
626
|
+
// Recursively build nested inclusions
|
|
627
|
+
nestedInclusion[nestedKey] = this.buildNestedInclusion({ output: nestedOutput }, action);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return Object.keys(nestedInclusion).length > 0
|
|
631
|
+
? { include: nestedInclusion }
|
|
632
|
+
: true;
|
|
633
|
+
}
|
|
634
|
+
shouldIncludeRelation(outputType, action) {
|
|
635
|
+
if (outputType === 'none') {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
if (outputType === 'single' && action === 'query') {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
return !(outputType === 'multiple' && action && action !== 'query');
|
|
642
|
+
}
|
|
643
|
+
mapRelationActions(action, data, currentData) {
|
|
644
|
+
const relations = {};
|
|
645
|
+
const resourceModel = (0, context_1.injectModel)(this._client.name);
|
|
646
|
+
const readModel = resourceModel.readModel;
|
|
647
|
+
const relationsModel = resourceModel.relationsModel;
|
|
648
|
+
const relationsConfig = resourceModel.config.relations;
|
|
649
|
+
for (const key in data) {
|
|
650
|
+
let value = data[key];
|
|
651
|
+
const relationSchema = (0, common_1.extractSchemaProperties)(relationsModel, key);
|
|
652
|
+
if (!relationSchema) {
|
|
653
|
+
// Set empty array or undefined value for null array type fields
|
|
654
|
+
if (value === null) {
|
|
655
|
+
if ((0, common_1.extractSchemaProperties)(readModel, key)?.type === 'array') {
|
|
656
|
+
relations[key] = action === 'update' ? [] : undefined;
|
|
657
|
+
}
|
|
658
|
+
else if (action === 'create') {
|
|
659
|
+
relations[key] = undefined;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Skip mapping for non-relation fields
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const config = relationsConfig?.[key];
|
|
666
|
+
const uniqueKey = config?.input?.uniqueKey || 'id';
|
|
667
|
+
const isArrayType = relationSchema.type === 'array';
|
|
668
|
+
// Normalize array values to single values if a value type is not an array
|
|
669
|
+
if (!isArrayType && (0, common_1.isArray)(value)) {
|
|
670
|
+
value = value[0];
|
|
671
|
+
}
|
|
672
|
+
// Set null values to undefined value for create actions. On update action
|
|
673
|
+
// they will be returned as disconnected relations.
|
|
674
|
+
if (action === 'create' && value === null) {
|
|
675
|
+
relations[key] = undefined;
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
// Normalize plain unique key values or arrays to object values
|
|
679
|
+
if (isArrayType) {
|
|
680
|
+
if ((0, common_1.isArray)(value) && !(0, common_1.isObject)([0])) {
|
|
681
|
+
value = value.map((v) => ({
|
|
682
|
+
[uniqueKey]: v
|
|
683
|
+
}));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
if (!(0, common_1.isObject)(value)) {
|
|
688
|
+
value = { [uniqueKey]: value };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// Map relation connections with an option to create a non-existing entity
|
|
692
|
+
if (config?.createIfNotExists && value) {
|
|
693
|
+
const createdBy = this.createdByConnect(config.model);
|
|
694
|
+
if (isArrayType) {
|
|
695
|
+
relations[key] = {
|
|
696
|
+
connectOrCreate: value.map((v) => ({
|
|
697
|
+
where: v[uniqueKey] ? { [uniqueKey]: v[uniqueKey] } : { id: 0 },
|
|
698
|
+
create: { ...v, createdBy, id: undefined }
|
|
699
|
+
}))
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
relations[key] = {
|
|
704
|
+
connectOrCreate: {
|
|
705
|
+
where: value[uniqueKey]
|
|
706
|
+
? { [uniqueKey]: value[uniqueKey] }
|
|
707
|
+
: { id: 0 },
|
|
708
|
+
create: { ...value, createdBy, id: undefined }
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else if (value) {
|
|
714
|
+
if (isArrayType && (0, common_1.isArray)(value)) {
|
|
715
|
+
relations[key] = {
|
|
716
|
+
connect: value.map((v) => ({ [uniqueKey]: v[uniqueKey] }))
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
relations[key] = { connect: { [uniqueKey]: value[uniqueKey] } };
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// Map relation disconnects if unique keys are no longer present or the
|
|
724
|
+
// new value is null. Delete relations if orphanRemoval is set to true.
|
|
725
|
+
// Also, do not create a disconnect action if the currentValue is already
|
|
726
|
+
// null.
|
|
727
|
+
if (action === 'update') {
|
|
728
|
+
const currentValue = currentData[key];
|
|
729
|
+
const removalMethod = config?.orphanRemoval ? 'delete' : 'disconnect';
|
|
730
|
+
if (currentValue && isArrayType) {
|
|
731
|
+
const newValueKeys = value?.map((v) => v[uniqueKey]) ?? [];
|
|
732
|
+
const currentValueKeys = currentValue
|
|
733
|
+
.filter((v) => newValueKeys.indexOf(v[uniqueKey]) === -1)
|
|
734
|
+
.map((v) => ({
|
|
735
|
+
[uniqueKey]: v[uniqueKey]
|
|
736
|
+
}));
|
|
737
|
+
if (currentValueKeys.length > 0) {
|
|
738
|
+
relations[key] = {
|
|
739
|
+
[removalMethod]: currentValueKeys,
|
|
740
|
+
...(relations[key] ?? {})
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else if (currentValue && value === null) {
|
|
745
|
+
relations[key] = {
|
|
746
|
+
[removalMethod]: { [uniqueKey]: currentValue[uniqueKey] },
|
|
747
|
+
...(relations[key] ?? {})
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
else if (!currentValue) {
|
|
751
|
+
relations[key] = undefined;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return relations;
|
|
756
|
+
}
|
|
757
|
+
createdByConnect(resourceName) {
|
|
758
|
+
const resourceModel = (0, context_1.injectModel)(resourceName ?? this._client.name, false);
|
|
759
|
+
if (resourceModel?.config.audit?.createdById === false) {
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
const currentUser = (0, security_1.currentAuthUser)();
|
|
763
|
+
return currentUser
|
|
764
|
+
? {
|
|
765
|
+
connect: {
|
|
766
|
+
id: currentUser.id
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
: undefined;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
exports.ResourceService = ResourceService;
|