mirror of
https://github.com/papra-hq/papra
synced 2026-04-21 13:37:23 +00:00
feat(demo): added custom properties in demo (#967)
This commit is contained in:
parent
d47d6b29a6
commit
b900a1d947
11 changed files with 509 additions and 8 deletions
|
|
@ -1,7 +1,9 @@
|
|||
import type { ApiKey } from '../api-keys/api-keys.types';
|
||||
import type { CustomPropertyDefinition } from '../custom-properties/custom-properties.types';
|
||||
import type { Document } from '../documents/documents.types';
|
||||
import type { Webhook } from '../webhooks/webhooks.types';
|
||||
import type {
|
||||
DocumentCustomPropertyValueStorage,
|
||||
DocumentFile,
|
||||
} from './demo.storage';
|
||||
import { FetchError } from 'ofetch';
|
||||
|
|
@ -11,6 +13,8 @@ import { defineHandler } from './demo-api-mock.models';
|
|||
import { createId, randomString } from './demo.models';
|
||||
import {
|
||||
apiKeyStorage,
|
||||
customPropertyDefinitionStorage,
|
||||
documentCustomPropertyValueStorage,
|
||||
documentFileStorage,
|
||||
documentStorage,
|
||||
organizationStorage,
|
||||
|
|
@ -65,6 +69,61 @@ async function deserializeFile(storageInfo: DocumentFile): Promise<File> {
|
|||
return new File([await fromBase64(base64Content)], name, { type });
|
||||
}
|
||||
|
||||
function hydratePropertyValue({ value, definition }: { value: unknown; definition: CustomPropertyDefinition }): unknown {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (definition.type === 'select') {
|
||||
const optionId = String(value);
|
||||
const option = definition.options.find(o => o.id === optionId || o.key === optionId);
|
||||
|
||||
if (!option) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { optionId: option.id, name: option.name };
|
||||
}
|
||||
|
||||
if (definition.type === 'multi_select') {
|
||||
const ids = Array.isArray(value) ? value.map(String) : [String(value)];
|
||||
|
||||
return ids
|
||||
.map((id) => {
|
||||
const option = definition.options.find(o => o.id === id || o.key === id);
|
||||
|
||||
return option ? { optionId: option.id, name: option.name } : null;
|
||||
})
|
||||
.filter(v => v !== null);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function buildCustomPropertiesResponse({
|
||||
definitions,
|
||||
storedValues,
|
||||
documentId,
|
||||
}: {
|
||||
definitions: CustomPropertyDefinition[];
|
||||
storedValues: DocumentCustomPropertyValueStorage[];
|
||||
documentId: string;
|
||||
}) {
|
||||
const documentValues = storedValues.filter(v => v.documentId === documentId);
|
||||
const valuesByDefinitionId = Object.fromEntries(documentValues.map(v => [v.propertyDefinitionId, v.value]));
|
||||
|
||||
return definitions
|
||||
.toSorted((a, b) => a.displayOrder - b.displayOrder)
|
||||
.map(def => ({
|
||||
propertyDefinitionId: def.id,
|
||||
key: def.key,
|
||||
name: def.name,
|
||||
type: def.type,
|
||||
displayOrder: def.displayOrder,
|
||||
value: hydratePropertyValue({ value: valuesByDefinitionId[def.id] ?? null, definition: def }),
|
||||
}));
|
||||
}
|
||||
|
||||
const inMemoryApiMock: Record<string, { handler: any }> = {
|
||||
...defineHandler({
|
||||
path: '/api/config',
|
||||
|
|
@ -166,23 +225,32 @@ const inMemoryApiMock: Record<string, { handler: any }> = {
|
|||
assert(organization, { status: 403 });
|
||||
|
||||
const searchQuery = rawSearchQuery.trim();
|
||||
const [organizationDocuments, allTags, tagDocuments] = await Promise.all([
|
||||
const [organizationDocuments, allTags, tagDocuments, allDefinitions, allPropertyValues] = await Promise.all([
|
||||
findMany(documentStorage, document => document?.organizationId === organizationId && !document?.deletedAt),
|
||||
getValues(tagStorage),
|
||||
getValues(tagDocumentStorage),
|
||||
findMany(customPropertyDefinitionStorage, def => def.organizationId === organizationId),
|
||||
getValues(documentCustomPropertyValueStorage),
|
||||
]);
|
||||
|
||||
const documentsWithTags = organizationDocuments.map((document) => {
|
||||
const documentsWithTagsAndProperties = organizationDocuments.map((document) => {
|
||||
const documentTagDocuments = tagDocuments.filter(tagDocument => tagDocument?.documentId === document?.id);
|
||||
const tags = allTags.filter(tag => documentTagDocuments.some(tagDocument => tagDocument?.tagId === tag?.id));
|
||||
|
||||
const customProperties = buildCustomPropertiesResponse({
|
||||
definitions: allDefinitions,
|
||||
storedValues: allPropertyValues,
|
||||
documentId: document.id,
|
||||
});
|
||||
|
||||
return {
|
||||
...document,
|
||||
tags,
|
||||
customProperties,
|
||||
};
|
||||
});
|
||||
|
||||
const filteredDocuments = searchDemoDocuments({ query: searchQuery, documents: documentsWithTags as Document[] });
|
||||
const filteredDocuments = searchDemoDocuments({ query: searchQuery, documents: documentsWithTagsAndProperties as unknown as Document[] });
|
||||
|
||||
const paginatedDocuments = filteredDocuments
|
||||
.toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
||||
|
|
@ -223,13 +291,25 @@ const inMemoryApiMock: Record<string, { handler: any }> = {
|
|||
|
||||
assert(document, { status: 404 });
|
||||
|
||||
const tagDocuments = await findMany(tagDocumentStorage, tagDocument => tagDocument.documentId === documentId);
|
||||
const [tagDocuments, allDefinitions, allPropertyValues] = await Promise.all([
|
||||
findMany(tagDocumentStorage, tagDocument => tagDocument.documentId === documentId),
|
||||
findMany(customPropertyDefinitionStorage, def => def.organizationId === organizationId),
|
||||
findMany(documentCustomPropertyValueStorage, v => v.documentId === documentId),
|
||||
]);
|
||||
|
||||
const tags = await findMany(tagStorage, tag => tagDocuments.some(tagDocument => tagDocument.tagId === tag.id));
|
||||
|
||||
const customProperties = buildCustomPropertiesResponse({
|
||||
definitions: allDefinitions,
|
||||
storedValues: allPropertyValues,
|
||||
documentId,
|
||||
});
|
||||
|
||||
return {
|
||||
document: {
|
||||
...document,
|
||||
tags,
|
||||
customProperties,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
@ -862,6 +942,173 @@ const inMemoryApiMock: Record<string, { handler: any }> = {
|
|||
};
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/custom-properties',
|
||||
method: 'GET',
|
||||
handler: async ({ params: { organizationId } }) => {
|
||||
const organization = await organizationStorage.getItem(organizationId);
|
||||
|
||||
assert(organization, { status: 403 });
|
||||
|
||||
const propertyDefinitions = await findMany(customPropertyDefinitionStorage, def => def.organizationId === organizationId);
|
||||
|
||||
return { propertyDefinitions };
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/custom-properties',
|
||||
method: 'POST',
|
||||
handler: async ({ params: { organizationId }, body }) => {
|
||||
const organization = await organizationStorage.getItem(organizationId);
|
||||
|
||||
assert(organization, { status: 403 });
|
||||
|
||||
const existingDefinitions = await findMany(customPropertyDefinitionStorage, def => def.organizationId === organizationId);
|
||||
|
||||
const propertyDefinition = {
|
||||
id: createId({ prefix: 'cpd' }),
|
||||
organizationId,
|
||||
name: get(body, ['name']) as string,
|
||||
key: (get(body, ['name']) as string).toLowerCase().replace(/\s+/g, '_'),
|
||||
description: (get(body, ['description']) ?? null) as string | null,
|
||||
type: get(body, ['type']) as string,
|
||||
displayOrder: existingDefinitions.length,
|
||||
options: (get(body, ['options']) as { name: string }[] ?? []).map((option, index) => ({
|
||||
id: createId({ prefix: 'opt' }),
|
||||
name: option.name,
|
||||
key: option.name.toLowerCase().replace(/\s+/g, '_'),
|
||||
displayOrder: index,
|
||||
})),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
await customPropertyDefinitionStorage.setItem(propertyDefinition.id, propertyDefinition as any);
|
||||
|
||||
return { propertyDefinition };
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/custom-properties/:propertyDefinitionId',
|
||||
method: 'GET',
|
||||
handler: async ({ params: { organizationId, propertyDefinitionId } }) => {
|
||||
const organization = await organizationStorage.getItem(organizationId);
|
||||
|
||||
assert(organization, { status: 403 });
|
||||
|
||||
const definition = await customPropertyDefinitionStorage.getItem(propertyDefinitionId);
|
||||
|
||||
assert(definition, { status: 404 });
|
||||
|
||||
return { definition };
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/custom-properties/:propertyDefinitionId',
|
||||
method: 'PUT',
|
||||
handler: async ({ params: { organizationId, propertyDefinitionId }, body }) => {
|
||||
const organization = await organizationStorage.getItem(organizationId);
|
||||
|
||||
assert(organization, { status: 403 });
|
||||
|
||||
const propertyDefinition = await customPropertyDefinitionStorage.getItem(propertyDefinitionId);
|
||||
|
||||
assert(propertyDefinition, { status: 404 });
|
||||
|
||||
const updatedDefinition = Object.assign(propertyDefinition, body, { updatedAt: new Date() });
|
||||
|
||||
await customPropertyDefinitionStorage.setItem(propertyDefinitionId, updatedDefinition);
|
||||
|
||||
return { propertyDefinition: updatedDefinition };
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/custom-properties/:propertyDefinitionId',
|
||||
method: 'DELETE',
|
||||
handler: async ({ params: { organizationId, propertyDefinitionId } }) => {
|
||||
const organization = await organizationStorage.getItem(organizationId);
|
||||
|
||||
assert(organization, { status: 403 });
|
||||
|
||||
await customPropertyDefinitionStorage.removeItem(propertyDefinitionId);
|
||||
|
||||
// Remove all values associated with this definition
|
||||
const values = await findMany(documentCustomPropertyValueStorage, v => v.propertyDefinitionId === propertyDefinitionId);
|
||||
|
||||
await Promise.all(values.map(v => documentCustomPropertyValueStorage.removeItem(`${v.documentId}:${propertyDefinitionId}`)));
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/documents/:documentId/custom-properties',
|
||||
method: 'GET',
|
||||
handler: async ({ params: { organizationId, documentId } }) => {
|
||||
const key = `${organizationId}:${documentId}`;
|
||||
const document = await documentStorage.getItem(key);
|
||||
|
||||
assert(document, { status: 404 });
|
||||
|
||||
const [allDefinitions, allValues] = await Promise.all([
|
||||
findMany(customPropertyDefinitionStorage, def => def.organizationId === organizationId),
|
||||
findMany(documentCustomPropertyValueStorage, v => v.documentId === documentId),
|
||||
]);
|
||||
|
||||
const customProperties = buildCustomPropertiesResponse({
|
||||
definitions: allDefinitions,
|
||||
storedValues: allValues,
|
||||
documentId,
|
||||
});
|
||||
|
||||
return { customProperties };
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/documents/:documentId/custom-properties/:propertyDefinitionId',
|
||||
method: 'PUT',
|
||||
handler: async ({ params: { organizationId, documentId, propertyDefinitionId }, body }) => {
|
||||
const docKey = `${organizationId}:${documentId}`;
|
||||
const document = await documentStorage.getItem(docKey);
|
||||
|
||||
assert(document, { status: 404 });
|
||||
|
||||
const definition = await customPropertyDefinitionStorage.getItem(propertyDefinitionId);
|
||||
|
||||
assert(definition, { status: 404 });
|
||||
|
||||
const valueKey = `${documentId}:${propertyDefinitionId}`;
|
||||
const existing = await documentCustomPropertyValueStorage.getItem(valueKey);
|
||||
|
||||
const value = get(body, ['value']);
|
||||
|
||||
await documentCustomPropertyValueStorage.setItem(valueKey, {
|
||||
id: existing?.id ?? createId({ prefix: 'dcpv' }),
|
||||
documentId,
|
||||
propertyDefinitionId,
|
||||
value,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
...defineHandler({
|
||||
path: '/api/organizations/:organizationId/documents/:documentId/custom-properties/:propertyDefinitionId',
|
||||
method: 'DELETE',
|
||||
handler: async ({ params: { organizationId, documentId, propertyDefinitionId } }) => {
|
||||
const docKey = `${organizationId}:${documentId}`;
|
||||
const document = await documentStorage.getItem(docKey);
|
||||
|
||||
assert(document, { status: 404 });
|
||||
|
||||
const valueKey = `${documentId}:${propertyDefinitionId}`;
|
||||
|
||||
await documentCustomPropertyValueStorage.removeItem(valueKey);
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export const router = createRouter({ routes: inMemoryApiMock, strictTrailingSlash: false });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ApiKey } from '../api-keys/api-keys.types';
|
||||
import type { CustomPropertyDefinition } from '../custom-properties/custom-properties.types';
|
||||
import type { Document } from '../documents/documents.types';
|
||||
import type { Organization } from '../organizations/organizations.types';
|
||||
import type { TaggingRule } from '../tagging-rules/tagging-rules.types';
|
||||
|
|
@ -9,6 +10,7 @@ import localStorageDriver from 'unstorage/drivers/localstorage';
|
|||
import { trackingServices } from '../tracking/tracking.services';
|
||||
import { DEMO_IS_SEEDED_KEY } from './demo.constants';
|
||||
import { createId } from './demo.models';
|
||||
import { customPropertyDefinitionsFixtures } from './seed/custom-property-definitions.fixtures';
|
||||
import { documentFixtures } from './seed/documents.fixtures';
|
||||
import { tagsFixtures } from './seed/tags.fixtures';
|
||||
|
||||
|
|
@ -20,6 +22,13 @@ export type DocumentFileStoredFile = { name: string; size: number; type: string;
|
|||
export type DocumentFileRemoteFile = { name: string; path: string };
|
||||
export type DocumentFile = DocumentFileStoredFile | DocumentFileRemoteFile;
|
||||
|
||||
export type DocumentCustomPropertyValueStorage = {
|
||||
id: string;
|
||||
documentId: string;
|
||||
propertyDefinitionId: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
export const organizationStorage = prefixStorage<Organization>(storage, 'organizations');
|
||||
export const documentStorage = prefixStorage<Document>(storage, 'documents');
|
||||
export const documentFileStorage = prefixStorage<DocumentFile>(storage, 'documentFiles');
|
||||
|
|
@ -28,6 +37,8 @@ export const tagDocumentStorage = prefixStorage<{ documentId: string; tagId: str
|
|||
export const taggingRuleStorage = prefixStorage<TaggingRule>(storage, 'taggingRules');
|
||||
export const apiKeyStorage = prefixStorage<ApiKey>(storage, 'apiKeys');
|
||||
export const webhooksStorage = prefixStorage<Webhook>(storage, 'webhooks');
|
||||
export const customPropertyDefinitionStorage = prefixStorage<CustomPropertyDefinition>(storage, 'customPropertyDefinitions');
|
||||
export const documentCustomPropertyValueStorage = prefixStorage<DocumentCustomPropertyValueStorage>(storage, 'documentCustomPropertyValues');
|
||||
|
||||
export async function clearDemoStorage() {
|
||||
await storage.clear();
|
||||
|
|
@ -90,6 +101,24 @@ export async function seedDemoStorage() {
|
|||
|
||||
const tagsPromises = tagStorage.setItems(tags.map(tag => ({ key: tag.id, value: tag })));
|
||||
|
||||
// Create custom property definitions
|
||||
const customPropertyDefinitions = customPropertyDefinitionsFixtures.map((fixture, index) => ({
|
||||
id: createId({ prefix: 'cpd' }),
|
||||
organizationId,
|
||||
name: fixture.name,
|
||||
key: fixture.key,
|
||||
description: fixture.description ?? null,
|
||||
type: fixture.type,
|
||||
displayOrder: index,
|
||||
options: fixture.options ?? [],
|
||||
createdAt: lastMonth,
|
||||
updatedAt: lastMonth,
|
||||
}));
|
||||
|
||||
const customPropertyDefinitionsPromises = customPropertyDefinitionStorage.setItems(
|
||||
customPropertyDefinitions.map(def => ({ key: def.id, value: def })),
|
||||
);
|
||||
|
||||
const documentsPromises = documentFixtures.flatMap((fixture) => {
|
||||
const documentId = createId({ prefix: 'doc' });
|
||||
|
||||
|
|
@ -126,11 +155,30 @@ export async function seedDemoStorage() {
|
|||
};
|
||||
}));
|
||||
|
||||
return [documentPromise, documentFilePromise, tagDocumentPromise];
|
||||
const customPropertyValuePromises = (fixture.customProperties ?? []).map((prop) => {
|
||||
const definition = customPropertyDefinitions.find(def => def.key === prop.key);
|
||||
|
||||
if (!definition) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const id = createId({ prefix: 'dcpv' });
|
||||
const valueKey = `${documentId}:${definition.id}`;
|
||||
|
||||
return documentCustomPropertyValueStorage.setItem(valueKey, {
|
||||
id,
|
||||
documentId,
|
||||
propertyDefinitionId: definition.id,
|
||||
value: prop.value,
|
||||
});
|
||||
});
|
||||
|
||||
return [documentPromise, documentFilePromise, tagDocumentPromise, ...customPropertyValuePromises];
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
tagsPromises,
|
||||
customPropertyDefinitionsPromises,
|
||||
...documentsPromises,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { parseSearchQuery } from '@papra/search-parser';
|
|||
type DocumentCondition = (params: { document: Document }) => boolean;
|
||||
|
||||
const falseCondition: DocumentCondition = () => false;
|
||||
const trueCondition: DocumentCondition = () => true;
|
||||
|
||||
export function someCorpusTokenStartsWith({ corpus, prefix }: { corpus: string | string []; prefix: string }): boolean {
|
||||
const lowerPrefix = prefix.toLowerCase();
|
||||
|
|
@ -149,6 +150,113 @@ function buildHasDateFilter({ expression }: { expression: FilterExpression }): D
|
|||
return ({ document }) => document.documentDate != null;
|
||||
}
|
||||
|
||||
function buildHasCustomPropertyFilter({ propertyKey }: { propertyKey: string }): DocumentCondition {
|
||||
return ({ document }) => {
|
||||
const prop = document.customProperties?.find(p => p.key === propertyKey);
|
||||
|
||||
return prop !== undefined && prop.value != null;
|
||||
};
|
||||
}
|
||||
|
||||
function buildCustomPropertyFilterCondition({ field, expression }: { field: string; expression: FilterExpression }): DocumentCondition {
|
||||
const { value, operator } = expression;
|
||||
|
||||
return ({ document }) => {
|
||||
const prop = document.customProperties?.find(p => p.key === field);
|
||||
|
||||
if (!prop || prop.value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (prop.type) {
|
||||
case 'boolean': {
|
||||
if (operator !== '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const boolValue = ['true', 'yes', '1', 'on', 'enabled'].includes(value.toLowerCase());
|
||||
|
||||
return prop.value === boolValue;
|
||||
}
|
||||
case 'number': {
|
||||
const numValue = Number(value);
|
||||
|
||||
if (Number.isNaN(numValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const propNum = Number(prop.value);
|
||||
|
||||
switch (operator) {
|
||||
case '=': return propNum === numValue;
|
||||
case '<': return propNum < numValue;
|
||||
case '<=': return propNum <= numValue;
|
||||
case '>': return propNum > numValue;
|
||||
case '>=': return propNum >= numValue;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
case 'date': {
|
||||
const dateValue = getDateValue({ value });
|
||||
|
||||
if (Number.isNaN(dateValue.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const propDate = new Date(prop.value as string);
|
||||
|
||||
switch (operator) {
|
||||
case '=': return propDate.toDateString() === dateValue.toDateString();
|
||||
case '<': return propDate < dateValue;
|
||||
case '<=': return propDate <= dateValue;
|
||||
case '>': return propDate > dateValue;
|
||||
case '>=': return propDate >= dateValue;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
case 'text': {
|
||||
if (operator !== '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return someCorpusTokenStartsWith({ corpus: String(prop.value), prefix: value });
|
||||
}
|
||||
case 'select': {
|
||||
if (operator !== '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const propValue = prop.value as { key: string; name: string } | string;
|
||||
const propKey = typeof propValue === 'object' ? propValue.key : propValue;
|
||||
|
||||
return propKey.toLowerCase() === value.toLowerCase();
|
||||
}
|
||||
case 'multi_select': {
|
||||
if (operator !== '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const propValues = prop.value as Array<{ key: string; name: string } | string>;
|
||||
|
||||
return propValues.some((v) => {
|
||||
const optKey = typeof v === 'object' ? v.key : v;
|
||||
|
||||
return optKey.toLowerCase() === value.toLowerCase();
|
||||
});
|
||||
}
|
||||
default: {
|
||||
if (operator !== '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return String(prop.value).toLowerCase() === value.toLowerCase();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const KNOWN_FILTER_FIELDS = new Set(['tag', 'name', 'content', 'created', 'date', 'has']);
|
||||
|
||||
function buildExpressionCondition({ expression }: { expression: Expression }): DocumentCondition {
|
||||
switch (expression.type) {
|
||||
case 'text': return buildTextCondition({ expression });
|
||||
|
|
@ -166,11 +274,19 @@ function buildExpressionCondition({ expression }: { expression: Expression }): D
|
|||
switch (expression.value) {
|
||||
case 'tags': return buildHasTagsFilter({ expression });
|
||||
case 'date': return buildHasDateFilter({ expression });
|
||||
default: return falseCondition;
|
||||
default:
|
||||
// has:<customPropertyKey> — check if document has a non-null value for this property
|
||||
return buildHasCustomPropertyFilter({ propertyKey: expression.value });
|
||||
}
|
||||
default: return falseCondition;
|
||||
default:
|
||||
// Unknown field — treat as a custom property key filter
|
||||
if (!KNOWN_FILTER_FIELDS.has(expression.field)) {
|
||||
return buildCustomPropertyFilterCondition({ field: expression.field, expression });
|
||||
}
|
||||
|
||||
return falseCondition;
|
||||
}
|
||||
case 'empty': return falseCondition;
|
||||
case 'empty': return trueCondition;
|
||||
default: return falseCondition;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import type { CustomPropertySelectOption, CustomPropertyType } from '../../custom-properties/custom-properties.types';
|
||||
|
||||
export type DemoCustomPropertyDefinitionFixture = {
|
||||
name: string;
|
||||
key: string;
|
||||
description?: string;
|
||||
type: CustomPropertyType;
|
||||
options?: CustomPropertySelectOption[];
|
||||
};
|
||||
|
||||
export const customPropertyDefinitionsFixtures: DemoCustomPropertyDefinitionFixture[] = [
|
||||
{
|
||||
name: 'Status',
|
||||
key: 'status',
|
||||
description: 'Current processing status of the document',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ id: 'opt_status_pending', key: 'pending', name: 'Pending', displayOrder: 0 },
|
||||
{ id: 'opt_status_reviewed', key: 'reviewed', name: 'Reviewed', displayOrder: 1 },
|
||||
{ id: 'opt_status_archived', key: 'archived', name: 'Archived', displayOrder: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Priority',
|
||||
key: 'priority',
|
||||
description: 'Investigation priority level',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ id: 'opt_priority_low', key: 'low', name: 'Low', displayOrder: 0 },
|
||||
{ id: 'opt_priority_medium', key: 'medium', name: 'Medium', displayOrder: 1 },
|
||||
{ id: 'opt_priority_high', key: 'high', name: 'High', displayOrder: 2 },
|
||||
{ id: 'opt_priority_critical', key: 'critical', name: 'Critical', displayOrder: 3 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Amount',
|
||||
key: 'amount',
|
||||
description: 'Monetary amount in GBP',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
name: 'Confidential',
|
||||
key: 'confidential',
|
||||
description: 'Whether this document is confidential',
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'Reference',
|
||||
key: 'reference',
|
||||
description: 'External reference number or identifier',
|
||||
type: 'text',
|
||||
},
|
||||
] as const satisfies DemoCustomPropertyDefinitionFixture[];
|
||||
|
|
@ -8,6 +8,12 @@ const demoDocumentFixture: DemoDocumentFixture = {
|
|||
mimeType: 'application/pdf',
|
||||
size: 31564,
|
||||
tags: ['Cases'],
|
||||
customProperties: [
|
||||
{ key: 'status', value: 'reviewed' },
|
||||
{ key: 'priority', value: 'critical' },
|
||||
{ key: 'confidential', value: true },
|
||||
{ key: 'reference', value: 'CASE-2026-001' },
|
||||
],
|
||||
content: `
|
||||
blackmail letter from reginald thornton fiennes date january 20 2026
|
||||
reggie com to sherlock holmes sleuth subject the secrets you
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ const demoDocumentFixture: DemoDocumentFixture = {
|
|||
mimeType: 'application/pdf',
|
||||
size: 54921,
|
||||
tags: ['Legal', 'Cases'],
|
||||
customProperties: [
|
||||
{ key: 'status', value: 'reviewed' },
|
||||
{ key: 'priority', value: 'high' },
|
||||
{ key: 'amount', value: 5000 },
|
||||
{ key: 'confidential', value: true },
|
||||
{ key: 'reference', value: 'SH-2024-087' },
|
||||
],
|
||||
content: `
|
||||
contract private investigation pemberton number sh 2024 087 date 22nd
|
||||
september parties service provider sherlock holmes consulting detective 221b baker
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ const demoDocumentFixture: DemoDocumentFixture = {
|
|||
mimeType: 'application/pdf',
|
||||
size: 50374,
|
||||
tags: ['Receipts'],
|
||||
customProperties: [
|
||||
{ key: 'status', value: 'archived' },
|
||||
{ key: 'amount', value: 19.56 },
|
||||
],
|
||||
content: `
|
||||
receipt for groceries date january 20 2026 store tesco superstore
|
||||
123 regent st london sw1e 7na order number tcgro000023 item
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ const demoDocumentFixture: DemoDocumentFixture = {
|
|||
mimeType: 'application/pdf',
|
||||
size: 38421,
|
||||
tags: ['Receipts', 'Property'],
|
||||
customProperties: [
|
||||
{ key: 'status', value: 'archived' },
|
||||
{ key: 'amount', value: 2000 },
|
||||
],
|
||||
content: `
|
||||
rent receipt april 2025 221b baker street marylebone london nw1
|
||||
6xe date 1st received from mr sherlock holmes for first
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ const demoDocumentFixture: DemoDocumentFixture = {
|
|||
mimeType: 'application/pdf',
|
||||
size: 37805,
|
||||
tags: ['Receipts', 'Property'],
|
||||
customProperties: [
|
||||
{ key: 'status', value: 'archived' },
|
||||
{ key: 'amount', value: 2000 },
|
||||
],
|
||||
content: `
|
||||
rent receipt july 2025 221b baker street marylebone london nw1
|
||||
6xe date 1st received from mr sherlock holmes for first
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ const demoDocumentFixture: DemoDocumentFixture = {
|
|||
mimeType: 'application/pdf',
|
||||
size: 51229,
|
||||
tags: ['Receipts'],
|
||||
customProperties: [
|
||||
{ key: 'status', value: 'archived' },
|
||||
{ key: 'amount', value: 3260 },
|
||||
{ key: 'reference', value: 'SL-2025-1108' },
|
||||
],
|
||||
content: `
|
||||
violin receipt vendor information transaction details item description price quantity
|
||||
customized the baker 2 500 00 1 hard case with
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import type { DemoCustomPropertyDefinitionFixture } from './custom-property-definitions.fixtures';
|
||||
import type { DemoTagFixtureNames } from './tags.fixtures';
|
||||
|
||||
export type DemoDocumentCustomPropertyValue = {
|
||||
key: DemoCustomPropertyDefinitionFixture['key'];
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
export type DemoDocumentFixture = {
|
||||
name: string;
|
||||
date: Date;
|
||||
|
|
@ -8,4 +14,5 @@ export type DemoDocumentFixture = {
|
|||
tags: DemoTagFixtureNames[];
|
||||
mimeType: string;
|
||||
size: number;
|
||||
customProperties?: DemoDocumentCustomPropertyValue[];
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue