@evenicanpm/portal-dms-adapaters 1.5.0 → 1.6.0
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.
- package/package.json +4 -3
- package/src/index.ts +2 -0
- package/src/paperless/client.ts +260 -0
- package/src/paperless/config.ts +31 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evenicanpm/portal-dms-adapaters",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
".": "./src/index.ts"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
-
"dist"
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
18
19
|
],
|
|
19
20
|
"types": "./dist/index.d.ts",
|
|
20
21
|
"devDependencies": {
|
|
@@ -25,5 +26,5 @@
|
|
|
25
26
|
"@evenicanpm/storefront-portal-types-schemas": "*",
|
|
26
27
|
"zod": "^3.0.0"
|
|
27
28
|
},
|
|
28
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "9b18fe6294cc0e57332ec8703825a609ac7b73b2"
|
|
29
30
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @module PaperlessProvider
|
|
4
|
+
* @category dms-provider
|
|
5
|
+
* Pure functions for extracting data from paperless-ngx
|
|
6
|
+
*/
|
|
7
|
+
import type { TableViewParams } from "@evenicanpm/storefront-portal-types-schemas";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Paperless-Specific document config parameters.
|
|
11
|
+
*/
|
|
12
|
+
export interface PaperlessDocConfig {
|
|
13
|
+
/** Paperless uses type for broad category of document */
|
|
14
|
+
type: string;
|
|
15
|
+
/** Paperless tags */
|
|
16
|
+
tags: string[];
|
|
17
|
+
/** Property names to map back from papaless to the UI */
|
|
18
|
+
paperlessToPortalMap: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for Paperless Client
|
|
23
|
+
*/
|
|
24
|
+
export interface PaperlessClientConfig {
|
|
25
|
+
/** Credentials username */
|
|
26
|
+
username: string;
|
|
27
|
+
/** Credentials password */
|
|
28
|
+
password: string;
|
|
29
|
+
/** Base of paperless web-server */
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
/** The endpoint prefix to fetch docs from paperless */
|
|
32
|
+
documentEndpoint: string;
|
|
33
|
+
tenantField: string;
|
|
34
|
+
customFieldsEndpoint: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Input for building a REST URL to fetch documents from Paperless
|
|
39
|
+
*/
|
|
40
|
+
interface PaperlessUrlInput {
|
|
41
|
+
config: PaperlessClientConfig;
|
|
42
|
+
params: TableViewParams;
|
|
43
|
+
paperlessParams: PaperlessDocConfig;
|
|
44
|
+
/** Property name to filter by for multi-tenancy so customer only sees their own docs */
|
|
45
|
+
customerId: string;
|
|
46
|
+
canViewAll: boolean;
|
|
47
|
+
docId?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* Builds URL for fetching documents from Paperless.
|
|
53
|
+
* Takes credentials, parameters and document settings and Builds
|
|
54
|
+
* a fetch-ready URL.
|
|
55
|
+
*
|
|
56
|
+
* @param PaperlessAuth Credentials for paperless basic auth.
|
|
57
|
+
*
|
|
58
|
+
*/
|
|
59
|
+
export const buildPaperlessRESTUrl = ({
|
|
60
|
+
config,
|
|
61
|
+
params,
|
|
62
|
+
paperlessParams,
|
|
63
|
+
customerId,
|
|
64
|
+
canViewAll,
|
|
65
|
+
}: PaperlessUrlInput): Request => {
|
|
66
|
+
const { username, password, tenantField } = config;
|
|
67
|
+
|
|
68
|
+
// Prepare data for request
|
|
69
|
+
const encodedCredentials = Buffer.from(`${username}:${password}`).toString(
|
|
70
|
+
"base64",
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const queryParams = mapParamsToQueryString({
|
|
74
|
+
params,
|
|
75
|
+
paperlessParams,
|
|
76
|
+
tenantField,
|
|
77
|
+
customerId,
|
|
78
|
+
canViewAll,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const url = queryParams
|
|
82
|
+
? `${config.baseUrl}${config.documentEndpoint}?${queryParams}`
|
|
83
|
+
: `${config.baseUrl}${config.documentEndpoint}`;
|
|
84
|
+
|
|
85
|
+
// Return ready-to-use request object
|
|
86
|
+
return new Request(url, {
|
|
87
|
+
headers: {
|
|
88
|
+
Authorization: `Basic ${encodedCredentials}`,
|
|
89
|
+
accept: "application/json",
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Builds a fetch-ready request for downloading a single document
|
|
96
|
+
* from Paperless.
|
|
97
|
+
*
|
|
98
|
+
* Uses Paperless basic authentication and the provided document ID
|
|
99
|
+
* to construct the download endpoint URL and required headers.
|
|
100
|
+
*
|
|
101
|
+
* @param input.config Paperless configuration containing base URL and credentials
|
|
102
|
+
* @param input.docId ID of the document to download
|
|
103
|
+
*/
|
|
104
|
+
export const buildPaperlessDownloadRequest = (input: {
|
|
105
|
+
config: PaperlessClientConfig;
|
|
106
|
+
docId: string;
|
|
107
|
+
}): Request => {
|
|
108
|
+
const { config, docId } = input;
|
|
109
|
+
const { username, password } = config;
|
|
110
|
+
|
|
111
|
+
const encodedCredentials = Buffer.from(`${username}:${password}`).toString(
|
|
112
|
+
"base64",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const url = `${config.baseUrl}${config.documentEndpoint}${encodeURIComponent(docId)}/download/`;
|
|
116
|
+
|
|
117
|
+
return new Request(url, {
|
|
118
|
+
headers: {
|
|
119
|
+
Authorization: `Basic ${encodedCredentials}`,
|
|
120
|
+
accept: "*/*",
|
|
121
|
+
"accept-encoding": "identity",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const buildCustomFieldsRequest = (input: {
|
|
127
|
+
config: PaperlessClientConfig;
|
|
128
|
+
}): Request => {
|
|
129
|
+
const { config } = input;
|
|
130
|
+
const { username, password } = config;
|
|
131
|
+
|
|
132
|
+
const encodedCredentials = Buffer.from(`${username}:${password}`).toString(
|
|
133
|
+
"base64",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const url = `${config.baseUrl}${config.customFieldsEndpoint}`;
|
|
137
|
+
|
|
138
|
+
return new Request(url, {
|
|
139
|
+
headers: {
|
|
140
|
+
Authorization: `Basic ${encodedCredentials}`,
|
|
141
|
+
accept: "application/json",
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Input args for the mapper that takes params
|
|
148
|
+
* and builds a query string.
|
|
149
|
+
*/
|
|
150
|
+
interface PaperlessMapperInput {
|
|
151
|
+
/**
|
|
152
|
+
* TableViewParams - The params that the portal-ui/feature/table generates
|
|
153
|
+
* from the view.
|
|
154
|
+
*/
|
|
155
|
+
params: TableViewParams;
|
|
156
|
+
/** General document setup for paperlessDoc */
|
|
157
|
+
paperlessParams: PaperlessDocConfig;
|
|
158
|
+
tenantField: string;
|
|
159
|
+
customerId: string;
|
|
160
|
+
canViewAll: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Paperless Query Params
|
|
164
|
+
const pq = {
|
|
165
|
+
customField: (name: string, value: string) =>
|
|
166
|
+
`custom_field_query=["${name}", "exact", "${value}"]`,
|
|
167
|
+
documentType: (type: string) => `document_type__name__iexact=${type}`,
|
|
168
|
+
param: (key: string, value: string | number) => `${key}=${value}`,
|
|
169
|
+
search: (column: string, term: string) =>
|
|
170
|
+
`${column}__icontains=${encodeURIComponent(term)}`,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
*
|
|
175
|
+
* Maps our unified Portal params and paperless-specific document settings
|
|
176
|
+
* to the specific Paperless API syntax.
|
|
177
|
+
* @param params Query parameters in portal format
|
|
178
|
+
* @internal
|
|
179
|
+
*/
|
|
180
|
+
const mapParamsToQueryString = ({
|
|
181
|
+
params,
|
|
182
|
+
paperlessParams,
|
|
183
|
+
tenantField,
|
|
184
|
+
customerId,
|
|
185
|
+
canViewAll,
|
|
186
|
+
}: PaperlessMapperInput) => {
|
|
187
|
+
const { type, paperlessToPortalMap } = paperlessParams;
|
|
188
|
+
|
|
189
|
+
// Tenant
|
|
190
|
+
const tenancy = canViewAll ? [] : [pq.customField(tenantField, customerId)];
|
|
191
|
+
|
|
192
|
+
// Base
|
|
193
|
+
const coreParts = [
|
|
194
|
+
pq.documentType(type),
|
|
195
|
+
pq.param("page", params.page + 1),
|
|
196
|
+
pq.param("page_size", params.pageSize),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Search
|
|
200
|
+
const searchEnabled = params.searchTerm && params.searchColumn;
|
|
201
|
+
const searchPart = searchEnabled
|
|
202
|
+
? pq.search(
|
|
203
|
+
paperlessToPortalMap?.[params.searchColumn] || params.searchColumn,
|
|
204
|
+
params.searchTerm,
|
|
205
|
+
)
|
|
206
|
+
: null;
|
|
207
|
+
|
|
208
|
+
// Filters
|
|
209
|
+
const filtersPart = Object.entries(params?.filters ?? {})
|
|
210
|
+
.filter(([_, value]) => value !== "all" && typeof value === "string")
|
|
211
|
+
.map(([name, value]) => pq.customField(name, value as string));
|
|
212
|
+
|
|
213
|
+
// Build full URL
|
|
214
|
+
return [...tenancy, ...coreParts, searchPart, ...filtersPart].join("&");
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
interface PaperlessResponse {
|
|
218
|
+
/** an array with all id's of docs */
|
|
219
|
+
all?: number[];
|
|
220
|
+
/** Property name from paperless */
|
|
221
|
+
results: PaperlessDoc[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export type PaperlessCustomFieldEntry = {
|
|
225
|
+
field: number;
|
|
226
|
+
value: unknown;
|
|
227
|
+
name?: string;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export interface PaperlessDoc {
|
|
231
|
+
id: string | number;
|
|
232
|
+
title: string;
|
|
233
|
+
created_date: string;
|
|
234
|
+
document_type: string;
|
|
235
|
+
customer_id: string;
|
|
236
|
+
custom_fields: PaperlessCustomFieldEntry[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export type CustomFieldsMap = Record<number, string>;
|
|
240
|
+
|
|
241
|
+
interface TransformPaperlessResponseInput {
|
|
242
|
+
paperlessResponse: PaperlessResponse;
|
|
243
|
+
customFieldsMap: Record<number, string>;
|
|
244
|
+
transformDoc: (data: PaperlessDoc, cfMap: Record<number, string>) => unknown;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* TODO: CREATE ZOD SCHEMA FOR PAPERLESS RESPONSE
|
|
249
|
+
* TODO:
|
|
250
|
+
* @param
|
|
251
|
+
* @returns
|
|
252
|
+
*/
|
|
253
|
+
export const transformPaperlessResponse = ({
|
|
254
|
+
paperlessResponse: { results, all },
|
|
255
|
+
customFieldsMap,
|
|
256
|
+
transformDoc,
|
|
257
|
+
}: TransformPaperlessResponseInput) => ({
|
|
258
|
+
data: results.map((doc) => transformDoc(doc, customFieldsMap)),
|
|
259
|
+
items: all?.length || results.length,
|
|
260
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PaperlessClientConfig } from "./client";
|
|
2
|
+
|
|
3
|
+
const PAPERLESS_ENDPOINT = "/api/documents/" as const;
|
|
4
|
+
const TENANT_FIELD = "customer_id" as const;
|
|
5
|
+
const CUSTOM_FIELDS_ENDPOINT = "/api/custom_fields/" as const;
|
|
6
|
+
|
|
7
|
+
export const createPaperlessConfig = (input: {
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
documentEndpoint?: string;
|
|
12
|
+
tenantField?: string;
|
|
13
|
+
customFieldsEndpoint?: string;
|
|
14
|
+
}): PaperlessClientConfig => {
|
|
15
|
+
const baseUrl = input.baseUrl.trim();
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
username: input.username,
|
|
19
|
+
password: input.password,
|
|
20
|
+
baseUrl,
|
|
21
|
+
documentEndpoint: input.documentEndpoint ?? PAPERLESS_ENDPOINT,
|
|
22
|
+
tenantField: input.tenantField ?? TENANT_FIELD,
|
|
23
|
+
customFieldsEndpoint: input.customFieldsEndpoint ?? CUSTOM_FIELDS_ENDPOINT,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const createPaperlessAssets = (input?: { logoUrl?: string }) => {
|
|
28
|
+
return {
|
|
29
|
+
logoUrl: input?.logoUrl ?? "paperless-logo.png",
|
|
30
|
+
};
|
|
31
|
+
};
|