Last active
October 9, 2024 18:29
-
-
Save u12206050/56eac1d878784a93cd81f2e34d5419a7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { HookConfig } from '@directus/extensions'; | |
import { FieldFilter, FieldFilterOperator, LogicalFilterOR } from '@directus/types'; | |
import { validate as isUuid } from 'uuid'; | |
type COLLECTION = string; | |
type FIELD = string; | |
type ISearchOptions = Record< | |
COLLECTION, | |
{ | |
string?: Record<FIELD, true | 'uppercase' | 'lowercase'>; | |
number?: Record<FIELD, '_contains' | '_eq'>; | |
} | |
>; | |
// CHANGE THESE OPTIONS TO SUITE YOUR NEEDS | |
const SearchOptions: ISearchOptions = { | |
activities: { | |
string: { | |
'translations.title': true, | |
slug: true, | |
'event.reference': true, | |
status: 'lowercase', | |
}, | |
}, | |
activity_registrations: { | |
string: { | |
'person.display': true, | |
}, | |
number: { | |
person: '_eq', | |
organization: '_eq', | |
}, | |
}, | |
events: { | |
string: { | |
organizer: 'lowercase', | |
reference: true, | |
'event_translations.title': true, | |
}, | |
}, | |
registrations: { | |
string: { | |
'person.display': 'lowercase', | |
}, | |
number: { | |
person: '_eq', | |
organization: '_eq', | |
}, | |
}, | |
persons: { | |
number: { | |
id: '_eq', | |
}, | |
}, | |
categories: { | |
string: { | |
'parent.translations.title': true, | |
'translations.title': true, | |
for: 'lowercase', | |
}, | |
}, | |
locations: { | |
string: { | |
'translations.name': true, | |
type: 'lowercase', | |
}, | |
}, | |
translation_devices: { | |
string: { | |
'person.display': true, | |
person: 'uppercase', | |
serial_no: 'lowercase', | |
}, | |
number: { | |
serial_no: '_eq', | |
}, | |
}, | |
}; | |
const relationFields = new Set(['uuid', 'm2o', 'user-created', 'user-updated']); | |
const numberTypes = new Set(['integer', 'float']); | |
const skipTypes = new Set(['alias', 'boolean', 'json']); | |
const registerHook: HookConfig = ({ filter }) => { | |
filter('items.query', async (query: { search?: string }, meta, context) => { | |
const { search } = query; | |
const { schema, accountability } = context; | |
if (!search || !schema || !accountability) return query; | |
const { collection } = meta; | |
const collectionSchema = schema.collections[collection]; | |
if (!collectionSchema) return query; | |
const newQuery = { filter: {}, ...query }; | |
delete newQuery.search; | |
const filter = newQuery.filter as LogicalFilterOR; | |
if (search) { | |
filter._or = filter._or || []; | |
const fields = {}; | |
if (accountability.admin) Object.assign(fields, collectionSchema.fields); | |
else { | |
const permission = accountability.permissions?.find(p => (p.action === 'read' && p.collection) === collection); | |
if (!permission) return query; | |
if (permission.fields?.includes('*')) Object.assign(fields, collectionSchema.fields); | |
else { | |
for (const field of permission.fields || []) { | |
if (collectionSchema.fields[field]) fields[field] = collectionSchema.fields[field]; | |
} | |
} | |
} | |
// If search value is uuid, then only search uuid fields | |
if (isUuid(search)) { | |
for (const field in fields) { | |
if ( | |
fields[field].type === 'uuid' || | |
(fields[field].type === 'string' && fields[field].special.some(s => relationFields.has(s))) | |
) { | |
filter._or.push({ [field]: { _eq: search } }); | |
} | |
} | |
} else if (isNaN(Number(search))) { | |
if (collection in SearchOptions && SearchOptions[collection].string) { | |
for (const field in SearchOptions[collection].string) { | |
const path = field.split('.'); | |
// User might not have access to this field | |
if (!fields[path[0]]) continue; | |
const subFilter: FieldFilter = {}; | |
let lastPath = subFilter as FieldFilterOperator; | |
for (const key of path) { | |
lastPath[key] = {}; | |
lastPath = lastPath[key] as FieldFilter; | |
} | |
if (SearchOptions[collection][field] === 'lowercase') { | |
lastPath._contains = search.toLowerCase(); | |
} else if (SearchOptions[collection][field] === 'uppercase') { | |
lastPath._contains = search.toUpperCase(); | |
} else { | |
// Since we know the search term is string we use _icontains | |
lastPath._contains = search; | |
} | |
filter._or.push(subFilter); | |
} | |
} else { | |
for (const field in fields) { | |
const { type } = fields[field]; | |
if (skipTypes.has(type)) continue; | |
if (type === 'date') { | |
// Use regex to check date format | |
if (!search.match(/^\d{4}-\d{2}-\d{2}$/)) continue; | |
// Since it is a string we expect the full date | |
if (search.length === 10) { | |
filter._or.push({ [field]: { _eq: search } }); | |
} | |
} else if (type === 'time') { | |
// Use regex to check time format | |
if (!search.match(/^\d{2}:\d{2}$/)) continue; | |
if (search.length === 5) { | |
filter._or.push({ [field]: { _eq: search } }); | |
} | |
} else if (type === 'timestamp') { | |
if (!search.match(/^\d{4}-\d{2}-\d{2}.\d{2}:\d{2}.*$/)) continue; | |
filter._or.push({ [field]: { _contains: search } }); | |
} else if (type === 'uuid') { | |
filter._or.push({ [field]: { _eq: search } }); | |
} else if (!numberTypes.has(type)) { | |
filter._or.push({ [field]: { _contains: search } }); | |
} | |
} | |
} | |
} else { | |
const specialNumFields = collection in SearchOptions ? SearchOptions[collection].number : undefined; | |
for (const field in fields) { | |
const { type } = fields[field]; | |
if (skipTypes.has(type)) continue; | |
if (specialNumFields && specialNumFields[field]) { | |
filter._or.push({ [field]: { [specialNumFields[field]]: search } }); | |
} else if (type === 'date') { | |
// Since it is a number we check only for 4 digit year | |
if (search.length === 4) { | |
filter._or.push({ [`year(${field})`]: { _eq: Number(search) } }); | |
} | |
} else if (numberTypes.has(type)) { | |
filter._or.push({ [field]: { _eq: Number(search) } }); | |
} | |
} | |
} | |
} | |
return newQuery; | |
}); | |
}; | |
export default registerHook; |
@u12206050 I found :
import { fetchPolicies } from '@directus/api/permissions/lib/fetch-policies';
import { fetchPermissions } from '@directus/api/permissions/lib/fetch-permissions';
(...)
const policies = await fetchPolicies(accountability, context);
const permissions = (await fetchPermissions({
action: 'read',
collections: [collection],
policies,
accountability: accountability,
}, context));
const permission = permissions?.[0];
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@u12206050 Directus v11 removed
accountability.permissions
, and I don't find others way to access to permissions thanpermissionsService.findByQuery(...)
at every request, because we cannot access stores on service side. I am missing something ?