@evenicanpm/portal-dms-adapaters 1.4.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 ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@evenicanpm/portal-dms-adapaters",
3
+ "version": "1.4.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "echo \"Error: no test specified\" && exit 1",
10
+ "format": "npx @biomejs/biome format --write ./src",
11
+ "build": "npx tsc"
12
+ },
13
+ "exports": {
14
+ ".": "./src/index.ts"
15
+ },
16
+ "types": "./dist/index.d.ts",
17
+ "devDependencies": {
18
+ "@types/node": "^25.3.0",
19
+ "typescript": "^5.6.3"
20
+ },
21
+ "dependencies": {
22
+ "@evenicanpm/storefront-portal-types-schemas": "*",
23
+ "zod": "^3.0.0"
24
+ },
25
+ "gitHead": "0614726ec6b7ce9564706514a6b756b390a13e96"
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./paperless/client";
2
+ export * from "./paperless/config";
@@ -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
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "sourceMap": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "preserveSymlinks": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "emitDeclarationOnly": true,
16
+ "jsx": "preserve",
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "allowJs": true,
22
+ "forceConsistentCasingInFileNames": true,
23
+ "incremental": true,
24
+ "rootDir": "./src",
25
+ "outDir": "./dist"
26
+ },
27
+ "include": ["src"]
28
+ }