@iexec/dataprotector
Version:
This product enables users to confidentially store data–such as mail address, documents, personal information ...
192 lines (182 loc) • 5.64 kB
text/typescript
import { gql } from 'graphql-request';
import {
ensureSearchableDataSchemaIsValid,
reverseSafeSchema,
} from '../../utils/data.js';
import { ValidationError, WorkflowError } from '../../utils/errors.js';
import { getMultiaddrAsString } from '../../utils/getMultiaddrAsString.js';
import { resolveENS } from '../../utils/resolveENS.js';
import {
addressOrEnsSchema,
numberBetweenSchema,
positiveNumberSchema,
throwIfMissing,
} from '../../utils/validators.js';
import { ProtectedDatasGraphQLResponse } from '../types/graphQLTypes.js';
import {
GetProtectedDataParams,
ProtectedData,
SearchableDataSchema,
} from '../types/index.js';
import { IExecConsumer, SubgraphConsumer } from '../types/internalTypes.js';
export const getProtectedData = async ({
iexec = throwIfMissing(),
graphQLClient = throwIfMissing(),
protectedDataAddress,
requiredSchema = {},
owner,
createdAfterTimestamp,
page = 0,
pageSize = 1000,
}: GetProtectedDataParams & IExecConsumer & SubgraphConsumer): Promise<
ProtectedData[]
> => {
const vCreatedAfterTimestamp = positiveNumberSchema()
.label('createdAfterTimestamp')
.validateSync(createdAfterTimestamp);
const vProtectedDataAddress = addressOrEnsSchema()
.label('protectedDataAddress')
.validateSync(protectedDataAddress);
let vRequiredSchema: SearchableDataSchema;
try {
ensureSearchableDataSchemaIsValid(requiredSchema);
vRequiredSchema = requiredSchema;
} catch (e: any) {
throw new ValidationError(`requiredSchema is not valid: ${e.message}`);
}
const vPage = positiveNumberSchema().label('page').validateSync(page);
const vPageSize = numberBetweenSchema(10, 1000)
.label('pageSize')
.validateSync(pageSize);
let vOwner = addressOrEnsSchema().label('owner').validateSync(owner);
vOwner = await resolveENS(iexec, vOwner);
try {
const start = vPage * vPageSize;
const range = vPageSize;
const { requiredSchemas, anyOfSchemas } = flattenSchema(vRequiredSchema);
const whereFilters = [];
if (vProtectedDataAddress) {
whereFilters.push({ id: vProtectedDataAddress });
}
if (vOwner) {
whereFilters.push({ owner: vOwner });
}
if (vCreatedAfterTimestamp) {
whereFilters.push({ creationTimestamp_gte: vCreatedAfterTimestamp });
}
if (requiredSchemas.length > 0) {
whereFilters.push({ schema_contains: requiredSchemas });
}
anyOfSchemas.forEach((anyOfSchema) => {
whereFilters.push({
or: anyOfSchema.map((schemaFragment) => ({
schema_contains: [schemaFragment],
})),
});
});
const filteredProtectedDataQuery = gql`
query ($start: Int!, $range: Int!, $where: ProtectedData_filter) {
protectedDatas(
where: $where
skip: $start
first: $range
orderBy: creationTimestamp
orderDirection: desc
) {
id
name
owner {
id
}
schema {
id
}
creationTimestamp
multiaddr
}
}
`;
const variables = {
where:
whereFilters.length > 0
? {
and: whereFilters,
}
: undefined,
start,
range,
};
const protectedDataResultQuery =
await graphQLClient.request<ProtectedDatasGraphQLResponse>(
filteredProtectedDataQuery,
variables
);
const protectedDataArray = transformGraphQLResponse(
protectedDataResultQuery
);
return protectedDataArray;
} catch (e) {
console.error('[getProtectedData] ERROR', e);
throw new WorkflowError({
message: 'Failed to fetch protected data',
errorCause: e,
});
}
};
function flattenSchema(
schema: SearchableDataSchema,
parentKey?: string
): { requiredSchemas: string[]; anyOfSchemas: string[][] } {
return Object.entries(schema).reduce(
(acc, [key, value]) => {
const newKey = parentKey ? `${parentKey}.${key}` : key;
// Array of possible types
if (Array.isArray(value)) {
if (value.length > 1) {
// one of many
acc.anyOfSchemas.push(value.map((entry) => `${newKey}:${entry}`));
} else {
// Array of only one type. Similar to single type.
acc.requiredSchemas.push(`${newKey}:${value[0]}`);
}
}
// nested schema
else if (typeof value === 'object') {
const { requiredSchemas, anyOfSchemas } = flattenSchema(value, newKey);
acc.requiredSchemas.push(...requiredSchemas);
acc.anyOfSchemas.push(...anyOfSchemas);
}
// single type
else {
acc.requiredSchemas.push(`${newKey}:${value}`);
}
return acc;
},
{ requiredSchemas: [], anyOfSchemas: [] }
);
}
function transformGraphQLResponse(
response: ProtectedDatasGraphQLResponse
): ProtectedData[] {
return response.protectedDatas
.map((protectedData) => {
try {
const schema = reverseSafeSchema(protectedData.schema);
const readableMultiAddr = getMultiaddrAsString({
multiaddrAsHexString: protectedData.multiaddr,
});
return {
name: protectedData.name,
address: protectedData.id,
owner: protectedData.owner.id,
schema,
creationTimestamp: Number(protectedData.creationTimestamp),
multiaddr: readableMultiAddr,
};
} catch (error) {
// Silently ignore the error to not return multiple errors in the console of the user
return null;
}
})
.filter((item) => item !== null);
}