1
import type { Accountability, Query, SchemaOverview } from '@directus/types';
2
import type { Knex } from 'knex';
3
import getDatabase from '../database/index.js';
4
import { ForbiddenError } from '@directus/errors';
5
import type { AbstractServiceOptions } from '../types/index.js';
6
import { applyFilter, applySearch } from '../utils/apply-query.js';
8
export class MetaService {
10
accountability: Accountability | null;
11
schema: SchemaOverview;
13
constructor(options: AbstractServiceOptions) {
14
this.knex = options.knex || getDatabase();
15
this.accountability = options.accountability || null;
16
this.schema = options.schema;
19
async getMetaForQuery(collection: string, query: any): Promise<Record<string, any> | undefined> {
20
if (!query || !query.meta) return;
22
const results = await Promise.all(
23
query.meta.map((metaVal: string) => {
24
if (metaVal === 'total_count') return this.totalCount(collection);
25
if (metaVal === 'filter_count') return this.filterCount(collection, query);
30
return results.reduce((metaObject: Record<string, any>, value, index) => {
33
[query.meta![index]]: value,
38
async totalCount(collection: string): Promise<number> {
39
const dbQuery = this.knex(collection).count('*', { as: 'count' }).first();
41
if (this.accountability?.admin !== true) {
42
const permissionsRecord = this.accountability?.permissions?.find((permission) => {
43
return permission.action === 'read' && permission.collection === collection;
46
if (!permissionsRecord) throw new ForbiddenError();
48
const permissions = permissionsRecord.permissions ?? {};
50
applyFilter(this.knex, this.schema, dbQuery, permissions, collection, {});
53
const result = await dbQuery;
55
return Number(result?.count ?? 0);
58
async filterCount(collection: string, query: Query): Promise<number> {
59
const dbQuery = this.knex(collection);
61
let filter = query.filter || {};
64
if (this.accountability?.admin !== true) {
65
const permissionsRecord = this.accountability?.permissions?.find((permission) => {
66
return permission.action === 'read' && permission.collection === collection;
69
if (!permissionsRecord) throw new ForbiddenError();
71
const permissions = permissionsRecord.permissions ?? {};
73
if (Object.keys(filter).length > 0) {
74
filter = { _and: [permissions, filter] };
80
if (Object.keys(filter).length > 0) {
81
({ hasJoins } = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}));
85
applySearch(this.knex, this.schema, dbQuery, query.search, collection);
89
const primaryKeyName = this.schema.collections[collection]!.primary;
91
dbQuery.countDistinct({ count: [`${collection}.${primaryKeyName}`] });
93
dbQuery.count('*', { as: 'count' });
96
const records = await dbQuery;
98
return Number(records[0]!['count']);