@hestia-earth/api 0.26.0 → 0.26.2

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { isNumber } from '@hestia-earth/utils';
2
+ import { BaseModel } from '../../db/model.base';
3
+ import { filenameWithoutExt } from '../../files/model/model';
4
+ export class Aggregation extends BaseModel {
5
+ folder;
6
+ filename;
7
+ filepath;
8
+ validatedAt;
9
+ status;
10
+ pipelineStatus;
11
+ metadata;
12
+ productId;
13
+ countryId;
14
+ startYear;
15
+ endYear;
16
+ }
17
+ export const defaultAggregationPeriod = 20;
18
+ export const aggregationFolderFromFilename = (filename) => {
19
+ const name = filenameWithoutExt(filename);
20
+ const parts = name.split('-');
21
+ const timestamp = parts.pop();
22
+ return timestamp.length === 8 ? parts.join('-') : name;
23
+ };
24
+ export const parseAggregationValues = (value) => {
25
+ const [productId, ...parts] = aggregationFolderFromFilename(value).split('-');
26
+ const countryId = parts.filter(v => !isNumber(v)).join('-');
27
+ const endYear = +parts.pop();
28
+ const lastPart = parts.pop();
29
+ const startYear = isNumber(lastPart) ? +lastPart : endYear - defaultAggregationPeriod + 1;
30
+ return {
31
+ productId,
32
+ countryId,
33
+ startYear,
34
+ endYear
35
+ };
36
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ export var AnalysisType;
3
+ (function (AnalysisType) {
4
+ AnalysisType["gapFilling"] = "gap-filling";
5
+ AnalysisType["quantity"] = "quantity";
6
+ AnalysisType["term"] = "term";
7
+ })(AnalysisType || (AnalysisType = {}));
8
+ export class Analysis extends BaseModel {
9
+ cycleId;
10
+ name;
11
+ indicators;
12
+ cycle;
13
+ impactAssessment;
14
+ quantity;
15
+ term;
16
+ }
@@ -0,0 +1,6 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ export class ApiCall extends BaseModel {
3
+ url;
4
+ user;
5
+ duration;
6
+ }
@@ -0,0 +1 @@
1
+ export const getPrimaryProduct = (products) => (products || []).find(product => product.primary === true);
@@ -0,0 +1,5 @@
1
+ export class BaseModel {
2
+ id;
3
+ createdAt;
4
+ updatedAt;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ export var EarthEngineCallType;
2
+ (function (EarthEngineCallType) {
3
+ EarthEngineCallType["boundary"] = "boundary";
4
+ EarthEngineCallType["coordinates"] = "coordinates";
5
+ EarthEngineCallType["gadm"] = "gadm";
6
+ })(EarthEngineCallType || (EarthEngineCallType = {}));
7
+ export var EarthEngineType;
8
+ (function (EarthEngineType) {
9
+ EarthEngineType["raster"] = "raster";
10
+ EarthEngineType["vector"] = "vector";
11
+ })(EarthEngineType || (EarthEngineType = {}));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,255 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ import { hasPermission, isAdmin, isReviewer, UserPermission, UserRole } from '../../users/model/model';
3
+ export const mb = 1048576;
4
+ export const sizeInMb = (size) => Math.round((size / mb) * 1000) / 1000;
5
+ export const maxFileSizeMb = 100;
6
+ export const maxFileSize = maxFileSizeMb * mb;
7
+ export var SupportedExtensions;
8
+ (function (SupportedExtensions) {
9
+ SupportedExtensions["xlsx"] = "xlsx";
10
+ SupportedExtensions["xls"] = "xls";
11
+ SupportedExtensions["csv"] = "csv";
12
+ SupportedExtensions["json"] = "json";
13
+ SupportedExtensions["draft"] = "txt";
14
+ SupportedExtensions["pdf"] = "pdf";
15
+ })(SupportedExtensions || (SupportedExtensions = {}));
16
+ const maxFileSizeExt = {
17
+ [SupportedExtensions.csv]: maxFileSize,
18
+ [SupportedExtensions.xlsx]: maxFileSize / 2,
19
+ [SupportedExtensions.xls]: maxFileSize / 2,
20
+ [SupportedExtensions.draft]: maxFileSize,
21
+ [SupportedExtensions.json]: maxFileSize,
22
+ [SupportedExtensions.pdf]: maxFileSize / 2
23
+ };
24
+ export const maxFileSizeByFile = (filename) => maxFileSizeExt[fileExt(filename).replace('.', '')];
25
+ export const finalFormatExtensions = [SupportedExtensions.json, SupportedExtensions.csv];
26
+ export var HestiaExtensions;
27
+ (function (HestiaExtensions) {
28
+ HestiaExtensions["nodes"] = "hestia";
29
+ HestiaExtensions["progress"] = "progress";
30
+ HestiaExtensions["log"] = "log";
31
+ HestiaExtensions["jsonLog"] = "jlog";
32
+ HestiaExtensions["error"] = "err";
33
+ HestiaExtensions["json"] = "json";
34
+ HestiaExtensions["jsonld"] = "jsonld";
35
+ HestiaExtensions["contributions"] = "contributions";
36
+ })(HestiaExtensions || (HestiaExtensions = {}));
37
+ export var FileFindFields;
38
+ (function (FileFindFields) {
39
+ FileFindFields["sources"] = "sources";
40
+ FileFindFields["cycles"] = "nbCycles";
41
+ FileFindFields["impactAssessments"] = "nbImpactAssessments";
42
+ FileFindFields["users"] = "user";
43
+ FileFindFields["hestiaData"] = "hestiaDataPath";
44
+ })(FileFindFields || (FileFindFields = {}));
45
+ export var FileProgress;
46
+ (function (FileProgress) {
47
+ FileProgress["convertExcel"] = "convertExcel";
48
+ FileProgress["convertCsv"] = "convertCsv";
49
+ FileProgress["convertJson"] = "convertJson";
50
+ FileProgress["checkExisting"] = "checkExisting";
51
+ FileProgress["extendBibliography"] = "extendBibliography";
52
+ FileProgress["addMetadata"] = "addMetadata";
53
+ FileProgress["validateHestia"] = "validateHestia";
54
+ FileProgress["validateData"] = "validateData";
55
+ FileProgress["copyHestia"] = "copyHestia";
56
+ FileProgress["processJson"] = "processJson";
57
+ FileProgress["indexJson"] = "indexJson";
58
+ })(FileProgress || (FileProgress = {}));
59
+ export var FileStatus;
60
+ (function (FileStatus) {
61
+ FileStatus["convertExcelDone"] = "convertExcelDone";
62
+ FileStatus["convertCsvDone"] = "convertCsvDone";
63
+ FileStatus["convertJsonDone"] = "convertJsonDone";
64
+ FileStatus["checkExistingDone"] = "checkExistingDone";
65
+ FileStatus["extendBibliographyDone"] = "extendBibliographyDone";
66
+ FileStatus["addMetadataDone"] = "addMetadataDone";
67
+ FileStatus["validateHestiaDone"] = "validateHestiaDone";
68
+ FileStatus["validateDataDone"] = "validateDataDone";
69
+ FileStatus["copyHestiaDone"] = "copyHestiaDone";
70
+ FileStatus["processJsonDone"] = "processJsonDone";
71
+ FileStatus["indexJsonDone"] = "indexJsonDone";
72
+ })(FileStatus || (FileStatus = {}));
73
+ export var FileError;
74
+ (function (FileError) {
75
+ FileError["convertExcelError"] = "convertExcelError";
76
+ FileError["convertCsvError"] = "convertCsvError";
77
+ FileError["convertJsonError"] = "convertJsonError";
78
+ FileError["checkExistingError"] = "checkExistingError";
79
+ FileError["extendBibliographyError"] = "extendBibliographyError";
80
+ FileError["addMetadataError"] = "addMetadataError";
81
+ FileError["validateHestiaError"] = "validateHestiaError";
82
+ FileError["validateDataError"] = "validateDataError";
83
+ FileError["copyHestiaError"] = "copyHestiaError";
84
+ FileError["processJsonError"] = "processJsonError";
85
+ FileError["indexJsonError"] = "indexJsonError";
86
+ })(FileError || (FileError = {}));
87
+ export var FilePipelineStatus;
88
+ (function (FilePipelineStatus) {
89
+ FilePipelineStatus["indexEngineDone"] = "indexEngineDone";
90
+ FilePipelineStatus["calculationEngineDone"] = "calculationEngineDone";
91
+ })(FilePipelineStatus || (FilePipelineStatus = {}));
92
+ export var FilePipelineProgress;
93
+ (function (FilePipelineProgress) {
94
+ FilePipelineProgress["indexEngine"] = "indexEngine";
95
+ FilePipelineProgress["calculationEngine"] = "calculationEngine";
96
+ })(FilePipelineProgress || (FilePipelineProgress = {}));
97
+ export var FilePipelineError;
98
+ (function (FilePipelineError) {
99
+ FilePipelineError["indexEngineError"] = "indexEngineError";
100
+ FilePipelineError["calculationEngineError"] = "calculationEngineError";
101
+ })(FilePipelineError || (FilePipelineError = {}));
102
+ export var FileValidationStatus;
103
+ (function (FileValidationStatus) {
104
+ FileValidationStatus["success"] = "success";
105
+ FileValidationStatus["error"] = "error";
106
+ })(FileValidationStatus || (FileValidationStatus = {}));
107
+ export class File extends BaseModel {
108
+ filename;
109
+ filepath;
110
+ folder;
111
+ user;
112
+ assignedUsers;
113
+ authorizedUsers;
114
+ contentType;
115
+ archived;
116
+ status;
117
+ pipelineStatus;
118
+ validationStatus;
119
+ userValidatedAt;
120
+ validatedUser;
121
+ validatedAt;
122
+ schemaVersion;
123
+ schemaOutdated;
124
+ glossaryOutdated;
125
+ comments;
126
+ todos;
127
+ metadata;
128
+ isPrivate;
129
+ skipValidation;
130
+ study;
131
+ fromDraft;
132
+ deleted;
133
+ isOwner;
134
+ isAuthorized;
135
+ isAssignedToMe;
136
+ isValidated;
137
+ isDraft;
138
+ originalPath;
139
+ hestiaPath;
140
+ progressPath;
141
+ studyPath;
142
+ dataPath;
143
+ dataProgressPath;
144
+ dataCsvPath;
145
+ dataRecalculatedCsvPath;
146
+ dataRecalculatedStatusPath;
147
+ logPath;
148
+ errorPath;
149
+ sources;
150
+ nbCycles;
151
+ nbImpactAssessments;
152
+ hestiaDataPath;
153
+ }
154
+ const fileNoExtSearch = (key, ...parts) => {
155
+ const [filepathNoExt] = parts.filter(Boolean).join('/').split('.');
156
+ return {
157
+ [key]: {
158
+ $in: Object.values(SupportedExtensions).map(ext => `${filepathNoExt}.${ext}`)
159
+ }
160
+ };
161
+ };
162
+ export const filenameSearch = (...parts) => (parts?.length ? fileNoExtSearch('filename', ...parts) : {});
163
+ export const filepathSearch = (...parts) => (parts?.length ? fileNoExtSearch('filepath', ...parts) : {});
164
+ export const fileExt = (path) => {
165
+ const paths = path?.split('/')?.pop()?.split('.');
166
+ return paths.length > 1 ? paths.pop() : null;
167
+ };
168
+ export const isSupportedExt = (path) => Object.values(SupportedExtensions).includes(fileExt(path));
169
+ export const fileToExt = (path, extension = '') => [
170
+ path
171
+ .replace(`.${fileExt(path)}`, '')
172
+ .replace(/^\./g, '')
173
+ .replace(/\.$/g, ''),
174
+ (extension || '').replace(/[.]/g, '')
175
+ ]
176
+ .filter(Boolean)
177
+ .join('.');
178
+ export const filenameWithoutExt = (filepath = '') => {
179
+ const filename = filenameFromPath(filepath);
180
+ const file = fileToExt(filename, '');
181
+ return file.endsWith('.') ? file.substring(0, file.length - 1) : file;
182
+ };
183
+ export const validPathChars = 'A-Za-z\\d\\-_';
184
+ const rstripChar = (value) => (value.length > 1 ? value.replace(/(\.|\_)$/g, '') : value);
185
+ export const replaceInvalidChars = (value) => {
186
+ const folder = folderFromPath(value);
187
+ const newFilename = value
188
+ ? rstripChar(rstripChar(filenameWithoutExt(value))
189
+ .replace(new RegExp(`[^${validPathChars}]`, 'g'), '_')
190
+ .replace(new RegExp(Object.values(SupportedExtensions)
191
+ .map(v => `(\_${v})`)
192
+ .join('|'), 'g'), '')
193
+ .replace(/[\_]{2,}/g, '_'))
194
+ : null;
195
+ return newFilename
196
+ ? value?.includes('.')
197
+ ? [normalizeFolder(folder), fileToExt(newFilename, fileExt(value))].filter(Boolean).join('/')
198
+ : newFilename
199
+ : value;
200
+ };
201
+ export const canUseFolder = (value) => !!value && !isFolderUpload(value.toLowerCase());
202
+ export const canUploadFolderFile = (value) => !!value && isSupportedExt(value) && isFolderUpload(value);
203
+ export const isFilenameValid = (filename) => !!filename && filename.split('.').filter(Boolean).length > 1 && isSupportedExt(filename);
204
+ export const isFilepathValid = (filepath) => !!filepath && [isSupportedExt(filepath), canUseFolder(filepath)].every(Boolean);
205
+ export const normalizeFolder = (folder) => folder.replace(new RegExp(`[^${validPathChars}]`, 'g'), '_').replace(/[\_]{2,}/g, '_');
206
+ export const folderFromPath = (path) => (path?.includes('.') ? path.split('/').slice(0, -1).join('/') : path);
207
+ export const rootFolderFromPath = (path) => path.split('/')[0];
208
+ export const filenameFromPath = (path) => path.split('/').pop();
209
+ export const analysesFolder = 'analyses';
210
+ export const glossaryFolder = 'glossary';
211
+ export const termFolder = 'term';
212
+ export const reconciliationFolder = 'reconciliation';
213
+ export const isAnalysis = (path) => rootFolderFromPath(path) === analysesFolder;
214
+ export const isReconciliation = (path) => rootFolderFromPath(path) === reconciliationFolder;
215
+ export const hasReconciliationAccess = (file, user) => 'idRecalculated' in file && hasPermission(UserPermission.reconciliationsRead, user);
216
+ export const isGlossary = (path) => rootFolderFromPath(path) === glossaryFolder;
217
+ export const isTerm = (path) => rootFolderFromPath(path) === termFolder;
218
+ export const aggregationFolder = 'aggregation';
219
+ export const isAggregation = (path) => rootFolderFromPath(path) === aggregationFolder;
220
+ export const isAdminFolder = (path) => !!path && [isTerm, isAnalysis, isAggregation, isReconciliation].some(f => f(path));
221
+ export const isFolderUpload = (path, termsOnly = false) => [isGlossary, isTerm, ...(termsOnly ? [] : [isAggregation, isReconciliation])].some(f => f(path));
222
+ const asString = (v) => (typeof v === 'object' ? v?._id?.toString() || v?.id?.toString() : v?.toString());
223
+ export const userFolder = (user) => asString(user);
224
+ export const isOwner = (file, user) => asString(file?.user) === asString(user);
225
+ export const isAuthorized = ({ authorizedUsers }, user) => (authorizedUsers || []).map(asString).includes(asString(user));
226
+ export const isAssigned = ({ assignedUsers }, user) => (assignedUsers || []).map(asString).includes(asString(user));
227
+ export const isLocked = ({ status, pipelineStatus }) => ![FileStatus.validateDataDone, ...Object.values(FileError)].includes(status) &&
228
+ (status !== FileStatus.indexJsonDone ||
229
+ ![FilePipelineStatus.calculationEngineDone, FilePipelineError.calculationEngineError].includes(pipelineStatus));
230
+ export const canForceRemove = (file, user, forceDelete) => (isAdmin(user) && forceDelete === true) || !isLocked(file);
231
+ export const canRemove = (file, user) => isAdmin(user) || !isValidated(file) || file.isPrivate;
232
+ const validateStatuses = [FileStatus.validateDataDone, FileStatus.indexJsonDone, FileError.indexJsonError];
233
+ export const validatedStatuses = [FileStatus.copyHestiaDone, FileStatus.processJsonDone, FileStatus.indexJsonDone];
234
+ export const canValidate = ({ status, isPrivate, archived }, user) => !archived && validateStatuses.includes(status) && (isPrivate || isReviewer(user));
235
+ export const isValidated = ({ validatedAt, status }) => !!validatedAt || validatedStatuses.includes(status);
236
+ const submitStatuses = [FileStatus.validateDataDone];
237
+ export const canSubmit = (file, user) => !file.archived &&
238
+ submitStatuses.includes(file.status) &&
239
+ !file.userValidatedAt &&
240
+ (isOwner(file, user) || isAuthorized(file, user));
241
+ export const isDraft = ({ filepath, filename }) => (filepath || filename || '').endsWith(SupportedExtensions.draft);
242
+ export const canEditComment = (comment, user) => asString(comment?.user) === asString(user);
243
+ export const canCommitHestiaData = (file, user) => !file.archived && isValidated(file) && (isReviewer(user) || user?.canCommitHestiaData);
244
+ const grantAccessByRole = {
245
+ [UserRole.Admin]: () => true,
246
+ [UserRole.Reviewer]: (file, user) => !file.isPrivate || isAssigned(file, user),
247
+ [UserRole.Developer]: () => false,
248
+ [UserRole.Researcher]: () => false
249
+ };
250
+ export const hasAccess = (file, user, allowedRoles) => [
251
+ isAdmin(user),
252
+ hasReconciliationAccess(file, user),
253
+ (!allowedRoles.length || allowedRoles.includes(user.role)) &&
254
+ (isAuthorized(file, user) || grantAccessByRole[user.role]?.(file, user))
255
+ ].some(Boolean);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ export var MigrationType;
3
+ (function (MigrationType) {
4
+ MigrationType["glossary"] = "glossary";
5
+ MigrationType["schema"] = "schema";
6
+ })(MigrationType || (MigrationType = {}));
7
+ export class Migration extends BaseModel {
8
+ filenames;
9
+ type;
10
+ }
@@ -0,0 +1,15 @@
1
+ export * from './api-calls/model/model';
2
+ export * from './aggregations/model/model';
3
+ export * from './analyses/model/model';
4
+ export * from './cycles/model/model';
5
+ export * from './earth-engine/model/model';
6
+ export * from './files/model/model';
7
+ export * from './glossary/model/model';
8
+ export * from './migrations/model/model';
9
+ export * from './nodes/model/model';
10
+ export * from './reconciliations/model/model';
11
+ export * from './settings/model/model';
12
+ export * from './url-shortener/model/model';
13
+ export * from './users/model/model';
14
+ export * from './webhooks/model/model';
15
+ export * from './ws.model';
@@ -0,0 +1,93 @@
1
+ import { SchemaType, NodeType, TermTermType, jsonldPath, isTypeNode, isExpandable } from '@hestia-earth/schema';
2
+ import { dayMs, reduceUndefinedValues, unique } from '@hestia-earth/utils';
3
+ import { fileToExt, SupportedExtensions } from '../../files/model/model';
4
+ import { hasPermission, UserPermission } from '../../users/model/model';
5
+ export const blankNodeTypes = Object.values(SchemaType).filter(t => !isTypeNode(t));
6
+ export var DataState;
7
+ (function (DataState) {
8
+ DataState["original"] = "original";
9
+ DataState["recalculated"] = "recalculated";
10
+ })(DataState || (DataState = {}));
11
+ export const dataStatesTypeMapping = {
12
+ [DataState.original]: Object.values(NodeType),
13
+ [DataState.recalculated]: [NodeType.Cycle, NodeType.ImpactAssessment, NodeType.Site]
14
+ };
15
+ export const allowedDataStates = (type) => Object.values(DataState).filter(dataState => dataStatesTypeMapping[dataState].includes(type));
16
+ export const pathWithState = (type, id, dataState = DataState.original, strictDataState = false) => {
17
+ const state = dataStatesTypeMapping[dataState].includes(type) ? dataState : null;
18
+ return state !== null || !strictDataState
19
+ ? [state === DataState.original ? null : state, jsonldPath(type, id)].filter(Boolean).join('/')
20
+ : null;
21
+ };
22
+ export const nodeTypeToParam = (type) => `${(type || '').toLowerCase()}s`;
23
+ export const paramToNodeType = (type) => Object.values(NodeType).find(v => nodeTypeToParam(v) === type);
24
+ const csvValue = (value) => (value || '').replace('[', '').replace(']', '');
25
+ const parseMessage = (message) => {
26
+ try {
27
+ const data = JSON.parse(message);
28
+ return data.message.split(',').reduce((prev, parts) => {
29
+ const [key, value] = parts.split('=');
30
+ const val = csvValue(value);
31
+ return {
32
+ ...prev,
33
+ ...(key && val ? { [key.trim()]: val } : {})
34
+ };
35
+ }, { logger: data.logger });
36
+ }
37
+ catch (err) {
38
+ return {};
39
+ }
40
+ };
41
+ const termTypes = Object.values(TermTermType);
42
+ const parseFilename = (filepath) => {
43
+ const [filename] = filepath.split('.');
44
+ const ext = termTypes.includes(filename) ? SupportedExtensions.xlsx : SupportedExtensions.csv;
45
+ return fileToExt(filename, ext);
46
+ };
47
+ const missingLookupPrefix = 'Missing lookup';
48
+ const parseLookup = ({ column, termid, [missingLookupPrefix]: missingLookup }) => ({
49
+ filename: parseFilename(missingLookup),
50
+ termId: termid,
51
+ column
52
+ });
53
+ export const parseLogMissingLookups = (data) => {
54
+ const lines = data.split('\n').filter(log => log.includes(missingLookupPrefix));
55
+ const messages = lines.map(parseMessage).filter(v => Object.keys(v).length > 1);
56
+ return unique(messages.map(parseLookup));
57
+ };
58
+ const mapTypeToId = (nodes) => nodes.reduce((prev, { '@type': type, '@id': id }) => {
59
+ prev[type] = prev[type] || [];
60
+ prev[type].push(id);
61
+ return prev;
62
+ }, {});
63
+ export const formatForUpload = (nodes, typeToId = mapTypeToId(nodes)) => nodes.map(({ '@type': type, '@id': id, ...node }) => type === NodeType.Term
64
+ ? { '@type': type, '@id': id }
65
+ : reduceUndefinedValues({
66
+ ...(!id || (typeToId[type] ?? []).includes(id) ? { type, id } : { '@type': type, '@id': id }),
67
+ ...Object.fromEntries(Object.entries(node).map(([key, value]) => {
68
+ const newValue = isExpandable(value)
69
+ ? Array.isArray(value)
70
+ ? formatForUpload(value, typeToId)
71
+ : formatForUpload([value], typeToId)[0]
72
+ : value;
73
+ return [key, newValue];
74
+ }))
75
+ }, true));
76
+ export const setPrivate = ({ source, defaultSource, ...node }) => ({
77
+ ...node,
78
+ dataPrivate: true
79
+ });
80
+ export const dataVersionRestrictedDays = 182;
81
+ export const isDataVersionRestricted = (dataVersion) => (new Date().getTime() - new Date(dataVersion).getTime()) / dayMs < dataVersionRestrictedDays;
82
+ export const isDataVersionAuthorised = (user, dataVersion) => dataVersion
83
+ ? isDataVersionRestricted(dataVersion)
84
+ ?
85
+ hasPermission(UserPermission.aggregationsLatest, user)
86
+ : true
87
+ :
88
+ hasPermission(UserPermission.aggregationsPreview, user);
89
+ export const isNodeAuthorised = (user, node, dataVersion) => !node.aggregated ||
90
+ [
91
+ hasPermission(UserPermission.aggregationsRead, user),
92
+ node.aggregatedDataValidated && isDataVersionAuthorised(user, dataVersion)
93
+ ].some(Boolean);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ export { getMainErrorMessage as reconciliationsGetMainErrorMessage, validateProductCorrespondence as reconciliationsValidateProductCorrespondence, validateImpactAssessmentLinks as reconciliationsValidateImpactAssessmentLinks, validateExpectedNodes as reconciliationsValidateExpectedNodes, validatePrivate as reconciliationsValidatePrivate } from './validations';
3
+ export var ReconciliationLevel;
4
+ (function (ReconciliationLevel) {
5
+ ReconciliationLevel["success"] = "success";
6
+ ReconciliationLevel["warning"] = "warning";
7
+ ReconciliationLevel["danger"] = "danger";
8
+ })(ReconciliationLevel || (ReconciliationLevel = {}));
9
+ export class Reconciliation extends BaseModel {
10
+ idExpected;
11
+ idRecalculated;
12
+ dataPrivate;
13
+ name;
14
+ level;
15
+ details;
16
+ error;
17
+ uploadStatus;
18
+ uploadPipelineStatus;
19
+ }
@@ -0,0 +1,62 @@
1
+ import { NodeType } from '@hestia-earth/schema';
2
+ export const notPrivateError = 'expected-not-private';
3
+ export const EXTRANEOUS = 'extraneous';
4
+ const isExtraneousError = (error) => (typeof error === 'string' ? error : error?.message)?.includes(EXTRANEOUS);
5
+ const isNotPrivateError = (error) => (typeof error === 'string' ? error : error?.message) === notPrivateError;
6
+ const getType = (node) => node?.['@type'] || node?.type;
7
+ const getId = (node) => node?.['@id'] || node?.id;
8
+ const filterByType = (nodeType) => (node) => getType(node) === nodeType;
9
+ const getErrorsFromValidationsObj = validations => Object.entries(validations)
10
+ .filter(([_errorMsg, validationFailed]) => !!validationFailed)
11
+ .map(([errorMsg]) => errorMsg);
12
+ export const validateExpectedNodes = (nodes) => {
13
+ const cycles = nodes.filter(node => getType(node) === NodeType.Cycle);
14
+ const sites = nodes.filter(node => getType(node) === NodeType.Site);
15
+ const impactAssessments = nodes.filter(node => getType(node) === NodeType.ImpactAssessment);
16
+ const cycleId = getId(cycles?.[0]);
17
+ const cycleSiteId = getId(cycles?.[0]?.site);
18
+ const siteId = getId(sites?.[0]);
19
+ return getErrorsFromValidationsObj({
20
+ ['More than one Cycle found in expected nodes']: cycles.length > 1,
21
+ ['No Cycle found in expected nodes']: cycles.length < 1,
22
+ ['Cycle missing name field in expected nodes']: cycles.length && !cycles[0].name,
23
+ ['More than one Site found in expected nodes']: sites.length > 1,
24
+ [`Cycle ${cycleId} is not linked to Site ${siteId} in expected nodes`]: cycleSiteId !== siteId,
25
+ ...impactAssessments.reduce((acc, ia) => {
26
+ acc[`ImpactAssessment ${getId(ia)} is not linked to Cycle ${cycleId} in expected nodes`] =
27
+ getId(ia?.cycle) !== cycleId;
28
+ return acc;
29
+ }, {})
30
+ });
31
+ };
32
+ const matchedProduct = (impacts) => (product) => impacts.filter(ia => getId(product.term) === getId(ia.product.term)).length > 1;
33
+ export const validateImpactAssessmentLinks = (nodes) => {
34
+ const impactAssessments = nodes.filter(filterByType(NodeType.ImpactAssessment));
35
+ const cycle = nodes.find(filterByType(NodeType.Cycle));
36
+ const cycleId = getId(cycle);
37
+ return getErrorsFromValidationsObj({
38
+ [`Cycle ${cycleId} has no associated impact assessment`]: !impactAssessments.length,
39
+ [`Cycle ${cycleId} has no products`]: !cycle.products?.length,
40
+ [`Cycle ${cycleId} has a product with more than one linked impact assessment`]: cycle.products?.some(matchedProduct(impactAssessments))
41
+ });
42
+ };
43
+ export const validatePrivate = (cycle) => getErrorsFromValidationsObj({ [notPrivateError]: !cycle.dataPrivate });
44
+ export const validateProductCorrespondence = (expectedProducts, recalculatedProducts, idRecalculated) => {
45
+ const missingProducts = expectedProducts.filter(pExpected => !recalculatedProducts.some(pRecalculated => getId(pRecalculated) === getId(pExpected)));
46
+ const extraneousProducts = recalculatedProducts.filter(pRecalculated => !expectedProducts.some(pExpected => getId(pRecalculated) === getId(pExpected)));
47
+ return [
48
+ [missingProducts, 'missing'],
49
+ [extraneousProducts, EXTRANEOUS]
50
+ ]
51
+ .map(([products, errorType]) => products.length
52
+ ? `Cycle ${idRecalculated} has ${errorType} impact assessment for products: ${products
53
+ .map(p => p['@id'])
54
+ .join(', ')}`
55
+ : null)
56
+ .filter(Boolean);
57
+ };
58
+ export const getMainErrorMessage = errors => errors?.length === 1 && isExtraneousError(errors[0])
59
+ ? 'recalculation-error'
60
+ : errors.some(isNotPrivateError)
61
+ ? notPrivateError
62
+ : 'invalid-nodes';
@@ -0,0 +1,18 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ export const cacheKey = 'settings';
3
+ export var SettingKey;
4
+ (function (SettingKey) {
5
+ SettingKey["maintenanceEnabled"] = "maintenanceEnabled";
6
+ SettingKey["readonlyEnabled"] = "readonlyEnabled";
7
+ SettingKey["privateUploadsEnabled"] = "privateUploadsEnabled";
8
+ SettingKey["publicUploadsEnabled"] = "publicUploadsEnabled";
9
+ SettingKey["aggregationEngine"] = "aggregationEngine";
10
+ SettingKey["calculationEngine"] = "calculationEngine";
11
+ SettingKey["users"] = "users";
12
+ SettingKey["dataReleases"] = "dataReleases";
13
+ })(SettingKey || (SettingKey = {}));
14
+ export class Setting extends BaseModel {
15
+ key;
16
+ value;
17
+ metadata;
18
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseModel } from '../../db/model.base';
2
+ export class UrlShortener extends BaseModel {
3
+ shortId;
4
+ path;
5
+ count;
6
+ }
7
+ export const whitelistedQueryParams = {
8
+ '/explorer': ['sortBy', 'sortOrder', 'page', 'dataVersion', 'type', 'filters']
9
+ };
10
+ export const isShortPathAllowed = (path) => path.split('?')[0] in whitelistedQueryParams;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,107 @@
1
+ import { NodeType } from '@hestia-earth/schema';
2
+ import { dayMs } from '@hestia-earth/utils';
3
+ import { BaseModel } from '../../db/model.base';
4
+ export const defaultMaxApiCalls = 100;
5
+ export const userPermissionRequestTimeout = 7 * dayMs;
6
+ export var MaxUploads;
7
+ (function (MaxUploads) {
8
+ MaxUploads[MaxUploads["New"] = 1] = "New";
9
+ MaxUploads[MaxUploads["Confirmed"] = 10] = "Confirmed";
10
+ })(MaxUploads || (MaxUploads = {}));
11
+ export var UserRole;
12
+ (function (UserRole) {
13
+ UserRole["Researcher"] = "default";
14
+ UserRole["Developer"] = "developer";
15
+ UserRole["Reviewer"] = "reviewer";
16
+ UserRole["Admin"] = "admin";
17
+ })(UserRole || (UserRole = {}));
18
+ export var UserPermission;
19
+ (function (UserPermission) {
20
+ UserPermission["aggregationsCreate"] = "aggregations-create";
21
+ UserPermission["aggregationsRead"] = "aggregations-read";
22
+ UserPermission["aggregationsPreview"] = "aggregations-preview";
23
+ UserPermission["aggregationsDelete"] = "aggregations-delete";
24
+ UserPermission["aggregationsVerify"] = "aggregations-verify";
25
+ UserPermission["aggregationsLatest"] = "aggregations-latest";
26
+ UserPermission["reconciliationsCreate"] = "reconciliations-create";
27
+ UserPermission["reconciliationsRead"] = "reconciliations-view";
28
+ UserPermission["reconciliationsUpdate"] = "reconciliations-update";
29
+ UserPermission["reconciliationsDelete"] = "reconciliations-delete";
30
+ })(UserPermission || (UserPermission = {}));
31
+ export var UserPermissionRequestStatus;
32
+ (function (UserPermissionRequestStatus) {
33
+ UserPermissionRequestStatus["Approved"] = "approved";
34
+ UserPermissionRequestStatus["Rejected"] = "rejected";
35
+ UserPermissionRequestStatus["Pending"] = "pending";
36
+ })(UserPermissionRequestStatus || (UserPermissionRequestStatus = {}));
37
+ export class User extends BaseModel {
38
+ email;
39
+ firstName;
40
+ lastName;
41
+ displayName;
42
+ password;
43
+ token;
44
+ role = UserRole.Researcher;
45
+ maxUploads = MaxUploads.New;
46
+ maxSkills = 1;
47
+ maxApiCalls = defaultMaxApiCalls;
48
+ dataPrivate = false;
49
+ validFilesCount = 0;
50
+ canCommitHestiaData = false;
51
+ permissions = [];
52
+ permissionsRequests = [];
53
+ emailNotificationsSuccess = false;
54
+ emailNotificationsFailure = false;
55
+ emailNotificationsFeedback = false;
56
+ emailNotificationsDownload = true;
57
+ autoSubmitPrivateSubmissions = false;
58
+ confirmToken;
59
+ confirmedAt;
60
+ lastActiveAt;
61
+ scopusID;
62
+ googleID;
63
+ linkedInID;
64
+ gitlabID;
65
+ gitlabUsername;
66
+ orcid;
67
+ website;
68
+ city;
69
+ country;
70
+ primaryInstitution;
71
+ metadata = {};
72
+ admin;
73
+ name;
74
+ actorId;
75
+ }
76
+ export const name = ({ firstName, lastName }) => [firstName, lastName].filter(Boolean).join(' ');
77
+ export const isAdmin = (user) => user?.role === UserRole.Admin;
78
+ export const isReviewer = (user) => [UserRole.Reviewer, UserRole.Admin].includes(user?.role);
79
+ export const isDeveloper = (user) => [UserRole.Developer, UserRole.Reviewer, UserRole.Admin].includes(user?.role);
80
+ export const hasPermission = (permission, user) => isAdmin(user) || (user?.permissions ?? []).includes(permission);
81
+ export const isPermissionRequestExpired = (request, timeout = userPermissionRequestTimeout) => new Date().getTime() - new Date(request.createdAt).getTime() >= timeout;
82
+ export const isPermissionExpired = (request, now = new Date()) => request.status === UserPermissionRequestStatus.Approved && request.expiryDate && new Date(request.expiryDate) <= now;
83
+ export const contains = (users, user) => users.some(u => u.email === user.email);
84
+ export const actorId = (user) => `${(user._id || user.id).toString()}`;
85
+ const jsonLDContext = (type, domain) => `${domain}/schema/${type}.jsonld`;
86
+ const addIfDefined = (key, value) => (!!value ? { [key]: value } : {});
87
+ const profileData = (user) => user.dataPrivate
88
+ ? {}
89
+ : {
90
+ email: user.email,
91
+ firstName: user.firstName,
92
+ lastName: user.lastName,
93
+ name: user.displayName || name(user),
94
+ ...addIfDefined('scopusID', user.scopusID),
95
+ ...addIfDefined('orcid', user.orcid),
96
+ ...addIfDefined('primaryInstitution', user.primaryInstitution),
97
+ ...addIfDefined('city', user.city),
98
+ ...addIfDefined('country', user.country ? { type: NodeType.Term, name: user.country } : undefined),
99
+ ...addIfDefined('website', user.website ? { '@id': user.website } : undefined)
100
+ };
101
+ export const userToActor = (user, domain = 'https://hestia.earth') => ({
102
+ '@context': jsonLDContext(NodeType.Actor, domain),
103
+ '@type': NodeType.Actor,
104
+ '@id': actorId(user),
105
+ dataPrivate: false,
106
+ ...profileData(user)
107
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ export var WebhookType;
2
+ (function (WebhookType) {
3
+ WebhookType["Files"] = "files";
4
+ })(WebhookType || (WebhookType = {}));
5
+ export var WebhookEvent;
6
+ (function (WebhookEvent) {
7
+ WebhookEvent["Created"] = "created";
8
+ WebhookEvent["Updated"] = "updated";
9
+ })(WebhookEvent || (WebhookEvent = {}));
10
+ export var WebhookStatus;
11
+ (function (WebhookStatus) {
12
+ WebhookStatus["Active"] = "active";
13
+ WebhookStatus["Inactive"] = "inactive";
14
+ })(WebhookStatus || (WebhookStatus = {}));
15
+ export const webHookTestEvent = 'test';
@@ -0,0 +1,5 @@
1
+ export var SocketNamespace;
2
+ (function (SocketNamespace) {
3
+ SocketNamespace["aggregation"] = "aggregation";
4
+ SocketNamespace["file"] = "file";
5
+ })(SocketNamespace || (SocketNamespace = {}));
@@ -47,6 +47,7 @@ export declare class User extends BaseModel {
47
47
  token?: string;
48
48
  role: UserRole;
49
49
  maxUploads: MaxUploads;
50
+ maxSkills: number;
50
51
  maxApiCalls: number;
51
52
  dataPrivate: boolean;
52
53
  validFilesCount: number;
@@ -46,6 +46,7 @@ class User extends model_base_1.BaseModel {
46
46
  token;
47
47
  role = UserRole.Researcher;
48
48
  maxUploads = MaxUploads.New;
49
+ maxSkills = 1;
49
50
  maxApiCalls = exports.defaultMaxApiCalls;
50
51
  dataPrivate = false;
51
52
  validFilesCount = 0;
@@ -88,9 +89,7 @@ const hasPermission = (permission, user) => (0, exports.isAdmin)(user) || (user?
88
89
  exports.hasPermission = hasPermission;
89
90
  const isPermissionRequestExpired = (request, timeout = exports.userPermissionRequestTimeout) => new Date().getTime() - new Date(request.createdAt).getTime() >= timeout;
90
91
  exports.isPermissionRequestExpired = isPermissionRequestExpired;
91
- const isPermissionExpired = (request, now = new Date()) => request.status === UserPermissionRequestStatus.Approved &&
92
- request.expiryDate &&
93
- new Date(request.expiryDate) <= now;
92
+ const isPermissionExpired = (request, now = new Date()) => request.status === UserPermissionRequestStatus.Approved && request.expiryDate && new Date(request.expiryDate) <= now;
94
93
  exports.isPermissionExpired = isPermissionExpired;
95
94
  const contains = (users, user) => users.some(u => u.email === user.email);
96
95
  exports.contains = contains;
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@hestia-earth/api",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "description": "Hestia API definitions",
5
5
  "main": "dist/models.js",
6
+ "module": "dist/esm/models.js",
6
7
  "typings": "dist/models.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/esm/models.js",
11
+ "require": "./dist/models.js",
12
+ "types": "./dist/models.d.ts"
13
+ }
14
+ },
15
+ "sideEffects": false,
7
16
  "scripts": {
8
17
  "build": "rm -rf build && tsc -p tsconfig.build.json",
9
- "build:module": "rm -rf dist && tsc -p tsconfig.dist.json",
18
+ "build:module": "rm -rf dist && tsc -p tsconfig.dist.json && tsc -p tsconfig.esm.json",
10
19
  "build:lambdas": "rm -rf build && tsc -p tsconfig.lambdas.json",
11
20
  "validate:jsonld": "hestia-validate-jsonld test/fixtures/nodes",
12
21
  "start": "npm run build && node index.js",