@nickchristensen/ppls 1.0.1
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/README.md +1190 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/add-command.d.ts +24 -0
- package/dist/add-command.js +47 -0
- package/dist/base-command.d.ts +63 -0
- package/dist/base-command.js +306 -0
- package/dist/commands/config/get.d.ts +11 -0
- package/dist/commands/config/get.js +34 -0
- package/dist/commands/config/init.d.ts +15 -0
- package/dist/commands/config/init.js +43 -0
- package/dist/commands/config/list.d.ts +13 -0
- package/dist/commands/config/list.js +64 -0
- package/dist/commands/config/remove.d.ts +11 -0
- package/dist/commands/config/remove.js +26 -0
- package/dist/commands/config/set.d.ts +12 -0
- package/dist/commands/config/set.js +58 -0
- package/dist/commands/correspondents/add.d.ts +12 -0
- package/dist/commands/correspondents/add.js +20 -0
- package/dist/commands/correspondents/delete.d.ts +16 -0
- package/dist/commands/correspondents/delete.js +18 -0
- package/dist/commands/correspondents/list.d.ts +9 -0
- package/dist/commands/correspondents/list.js +15 -0
- package/dist/commands/correspondents/show.d.ts +14 -0
- package/dist/commands/correspondents/show.js +17 -0
- package/dist/commands/correspondents/update.d.ts +18 -0
- package/dist/commands/correspondents/update.js +24 -0
- package/dist/commands/custom-fields/add.d.ts +16 -0
- package/dist/commands/custom-fields/add.js +91 -0
- package/dist/commands/custom-fields/delete.d.ts +16 -0
- package/dist/commands/custom-fields/delete.js +18 -0
- package/dist/commands/custom-fields/list.d.ts +9 -0
- package/dist/commands/custom-fields/list.js +12 -0
- package/dist/commands/custom-fields/show.d.ts +14 -0
- package/dist/commands/custom-fields/show.js +17 -0
- package/dist/commands/custom-fields/update.d.ts +20 -0
- package/dist/commands/custom-fields/update.js +93 -0
- package/dist/commands/document-types/add.d.ts +12 -0
- package/dist/commands/document-types/add.js +22 -0
- package/dist/commands/document-types/delete.d.ts +16 -0
- package/dist/commands/document-types/delete.js +18 -0
- package/dist/commands/document-types/list.d.ts +9 -0
- package/dist/commands/document-types/list.js +12 -0
- package/dist/commands/document-types/show.d.ts +14 -0
- package/dist/commands/document-types/show.js +17 -0
- package/dist/commands/document-types/update.d.ts +18 -0
- package/dist/commands/document-types/update.js +26 -0
- package/dist/commands/documents/add.d.ts +70 -0
- package/dist/commands/documents/add.js +166 -0
- package/dist/commands/documents/delete.d.ts +16 -0
- package/dist/commands/documents/delete.js +18 -0
- package/dist/commands/documents/download.d.ts +48 -0
- package/dist/commands/documents/download.js +152 -0
- package/dist/commands/documents/list.d.ts +9 -0
- package/dist/commands/documents/list.js +14 -0
- package/dist/commands/documents/show.d.ts +14 -0
- package/dist/commands/documents/show.js +19 -0
- package/dist/commands/documents/update.d.ts +25 -0
- package/dist/commands/documents/update.js +42 -0
- package/dist/commands/profile.d.ts +13 -0
- package/dist/commands/profile.js +19 -0
- package/dist/commands/tags/add.d.ts +17 -0
- package/dist/commands/tags/add.js +29 -0
- package/dist/commands/tags/delete.d.ts +16 -0
- package/dist/commands/tags/delete.js +18 -0
- package/dist/commands/tags/list.d.ts +10 -0
- package/dist/commands/tags/list.js +18 -0
- package/dist/commands/tags/show.d.ts +16 -0
- package/dist/commands/tags/show.js +24 -0
- package/dist/commands/tags/update.d.ts +21 -0
- package/dist/commands/tags/update.js +30 -0
- package/dist/delete-command.d.ts +20 -0
- package/dist/delete-command.js +48 -0
- package/dist/helpers/config-store.d.ts +4 -0
- package/dist/helpers/config-store.js +35 -0
- package/dist/helpers/date-utils.d.ts +3 -0
- package/dist/helpers/date-utils.js +27 -0
- package/dist/helpers/table.d.ts +20 -0
- package/dist/helpers/table.js +42 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/list-command.d.ts +49 -0
- package/dist/list-command.js +98 -0
- package/dist/paginated-command.d.ts +48 -0
- package/dist/paginated-command.js +89 -0
- package/dist/show-command.d.ts +27 -0
- package/dist/show-command.js +50 -0
- package/dist/types/correspondents.d.ts +28 -0
- package/dist/types/correspondents.js +1 -0
- package/dist/types/custom-fields.d.ts +18 -0
- package/dist/types/custom-fields.js +1 -0
- package/dist/types/document-types.d.ts +27 -0
- package/dist/types/document-types.js +1 -0
- package/dist/types/documents.d.ts +70 -0
- package/dist/types/documents.js +1 -0
- package/dist/types/profile.d.ts +15 -0
- package/dist/types/profile.js +1 -0
- package/dist/types/shared.d.ts +4 -0
- package/dist/types/shared.js +1 -0
- package/dist/types/tags.d.ts +31 -0
- package/dist/types/tags.js +1 -0
- package/dist/update-command.d.ts +28 -0
- package/dist/update-command.js +51 -0
- package/oclif.manifest.json +3313 -0
- package/package.json +87 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { DeleteCommand } from '../../delete-command.js';
|
|
3
|
+
export default class DocumentTypesDelete extends DeleteCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({ description: 'Document type id', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Delete a document type';
|
|
8
|
+
static examples = ['<%= config.bin %> <%= command.id %> 123'];
|
|
9
|
+
static flags = {
|
|
10
|
+
yes: Flags.boolean({ char: 'y', description: 'Skip confirmation prompt' }),
|
|
11
|
+
};
|
|
12
|
+
deleteLabel(id) {
|
|
13
|
+
return `document type ${id}`;
|
|
14
|
+
}
|
|
15
|
+
deletePath(id) {
|
|
16
|
+
return `/api/document_types/${id}/`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DocumentType } from '../../types/document-types.js';
|
|
2
|
+
import { ListCommand } from '../../list-command.js';
|
|
3
|
+
export default class DocumentTypesList extends ListCommand<DocumentType> {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
protected listPath: string;
|
|
7
|
+
protected tableAttrs: string[];
|
|
8
|
+
protected plainTemplate(documentType: DocumentType): string | undefined;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ListCommand } from '../../list-command.js';
|
|
2
|
+
export default class DocumentTypesList extends ListCommand {
|
|
3
|
+
static description = 'List document types';
|
|
4
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
5
|
+
listPath = '/api/document_types/';
|
|
6
|
+
tableAttrs = ['id', 'name', 'slug', 'document_count'];
|
|
7
|
+
plainTemplate(documentType) {
|
|
8
|
+
const count = documentType.document_count;
|
|
9
|
+
const suffix = count === undefined ? '' : ` (${count} ${count === 1 ? 'document' : 'documents'})`;
|
|
10
|
+
return `[${documentType.id}] ${documentType.name}${suffix}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DocumentType } from '../../types/document-types.js';
|
|
2
|
+
import { ShowCommand } from '../../show-command.js';
|
|
3
|
+
export default class DocumentTypesShow extends ShowCommand<DocumentType> {
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
6
|
+
max?: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
static description: string;
|
|
11
|
+
static examples: string[];
|
|
12
|
+
protected plainTemplate(documentType: DocumentType): string | undefined;
|
|
13
|
+
protected showPath(id: number | string): string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { ShowCommand } from '../../show-command.js';
|
|
3
|
+
export default class DocumentTypesShow extends ShowCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({ description: 'Document type id', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Show document type details';
|
|
8
|
+
static examples = ['<%= config.bin %> <%= command.id %> 123'];
|
|
9
|
+
plainTemplate(documentType) {
|
|
10
|
+
const count = documentType.document_count;
|
|
11
|
+
const suffix = count === undefined ? '' : ` (${count} ${count === 1 ? 'document' : 'documents'})`;
|
|
12
|
+
return `[${documentType.id}] ${documentType.name}${suffix}`;
|
|
13
|
+
}
|
|
14
|
+
showPath(id) {
|
|
15
|
+
return `/api/document_types/${id}/`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DocumentType, DocumentTypeUpdate } from '../../types/document-types.js';
|
|
2
|
+
import { UpdateCommand } from '../../update-command.js';
|
|
3
|
+
export default class DocumentTypesUpdate extends UpdateCommand<DocumentTypeUpdate, DocumentType> {
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
6
|
+
max?: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
static description: string;
|
|
11
|
+
static examples: string[];
|
|
12
|
+
static flags: {
|
|
13
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
protected buildPayload(_args: unknown, flags: Record<string, unknown>): DocumentTypeUpdate;
|
|
16
|
+
protected plainTemplate(documentType: DocumentType): string | undefined;
|
|
17
|
+
protected updatePath(id: number | string): string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { UpdateCommand } from '../../update-command.js';
|
|
3
|
+
export default class DocumentTypesUpdate extends UpdateCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({ description: 'Document type id', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Update a document type';
|
|
8
|
+
static examples = ['<%= config.bin %> <%= command.id %> 123 --name "Invoice"'];
|
|
9
|
+
static flags = {
|
|
10
|
+
name: Flags.string({ description: 'Document type name' }),
|
|
11
|
+
};
|
|
12
|
+
buildPayload(_args, flags) {
|
|
13
|
+
const typedFlags = flags;
|
|
14
|
+
return {
|
|
15
|
+
name: typedFlags.name,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
plainTemplate(documentType) {
|
|
19
|
+
const count = documentType.document_count;
|
|
20
|
+
const suffix = count === undefined ? '' : ` (${count} ${count === 1 ? 'document' : 'documents'})`;
|
|
21
|
+
return `[${documentType.id}] ${documentType.name}${suffix}`;
|
|
22
|
+
}
|
|
23
|
+
updatePath(id) {
|
|
24
|
+
return `/api/document_types/${id}/`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { DocumentCreate } from '../../types/documents.js';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
type DocumentsAddArgs = {
|
|
4
|
+
path?: string;
|
|
5
|
+
};
|
|
6
|
+
type DocumentsAddFlags = {
|
|
7
|
+
'archive-serial-number'?: number;
|
|
8
|
+
correspondent?: number;
|
|
9
|
+
created?: DocumentCreate['created'];
|
|
10
|
+
'document-type'?: number;
|
|
11
|
+
'storage-path'?: number;
|
|
12
|
+
tag?: number[];
|
|
13
|
+
title?: string;
|
|
14
|
+
};
|
|
15
|
+
type UploadOutputFlags = {
|
|
16
|
+
'date-format': string;
|
|
17
|
+
plain?: boolean;
|
|
18
|
+
table?: boolean;
|
|
19
|
+
};
|
|
20
|
+
type UploadResult = Record<string, unknown>;
|
|
21
|
+
type UploadFileOptions = {
|
|
22
|
+
apiFlags: {
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
hostname: string;
|
|
25
|
+
token: string;
|
|
26
|
+
};
|
|
27
|
+
filePath: string;
|
|
28
|
+
payload: DocumentCreate;
|
|
29
|
+
};
|
|
30
|
+
export default class DocumentsAdd extends BaseCommand {
|
|
31
|
+
static args: {
|
|
32
|
+
path: import("@oclif/core/interfaces").Arg<string | undefined, {
|
|
33
|
+
exists?: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
static description: string;
|
|
37
|
+
static examples: string[];
|
|
38
|
+
static flags: {
|
|
39
|
+
'archive-serial-number': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
40
|
+
correspondent: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
created: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
|
+
'document-type': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
43
|
+
'storage-path': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
44
|
+
tag: import("@oclif/core/interfaces").OptionFlag<number[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
45
|
+
title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
46
|
+
};
|
|
47
|
+
static strict: boolean;
|
|
48
|
+
protected buildPayload(flags: DocumentsAddFlags): DocumentCreate;
|
|
49
|
+
protected buildUploadFormData(payload: DocumentCreate, filename: string, fileContents: Buffer): FormData;
|
|
50
|
+
protected normalizeResult(result: unknown): UploadResult;
|
|
51
|
+
protected plainTemplate(result: UploadResult): string | undefined;
|
|
52
|
+
protected renderUploadOutput(options: {
|
|
53
|
+
flags: UploadOutputFlags;
|
|
54
|
+
result: UploadResult;
|
|
55
|
+
}): void;
|
|
56
|
+
protected resolveUploadPaths(args: DocumentsAddArgs, argv: unknown[]): string[];
|
|
57
|
+
run(): Promise<UploadResult | UploadResult[]>;
|
|
58
|
+
protected toOptionalString(value: null | number | undefined): string | undefined;
|
|
59
|
+
protected uploadFile(options: UploadFileOptions): Promise<UploadResult>;
|
|
60
|
+
protected uploadFiles(options: {
|
|
61
|
+
apiFlags: {
|
|
62
|
+
headers: Record<string, string>;
|
|
63
|
+
hostname: string;
|
|
64
|
+
token: string;
|
|
65
|
+
};
|
|
66
|
+
paths: string[];
|
|
67
|
+
payload: DocumentCreate;
|
|
68
|
+
}): Promise<UploadResult[]>;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { BaseCommand } from '../../base-command.js';
|
|
5
|
+
import { createValueFormatter, formatField } from '../../helpers/table.js';
|
|
6
|
+
export default class DocumentsAdd extends BaseCommand {
|
|
7
|
+
static args = {
|
|
8
|
+
path: Args.file({ description: 'Document file path(s)' }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Upload one or more documents. Supports multiple arguments or a glob.';
|
|
11
|
+
static examples = ['<%= config.bin %> <%= command.id %> ./receipt.pdf --title "Receipt"'];
|
|
12
|
+
static flags = {
|
|
13
|
+
'archive-serial-number': Flags.integer({ description: 'Archive serial number' }),
|
|
14
|
+
correspondent: Flags.integer({ description: 'Correspondent id' }),
|
|
15
|
+
created: Flags.string({ description: 'Document created date-time' }),
|
|
16
|
+
'document-type': Flags.integer({ description: 'Document type id' }),
|
|
17
|
+
'storage-path': Flags.integer({ description: 'Storage path id' }),
|
|
18
|
+
tag: Flags.integer({ description: 'Tag id (repeatable)', multiple: true }),
|
|
19
|
+
title: Flags.string({ description: 'Document title' }),
|
|
20
|
+
};
|
|
21
|
+
static strict = false;
|
|
22
|
+
buildPayload(flags) {
|
|
23
|
+
return {
|
|
24
|
+
'archive_serial_number': flags['archive-serial-number'],
|
|
25
|
+
correspondent: flags.correspondent,
|
|
26
|
+
created: flags.created,
|
|
27
|
+
'document_type': flags['document-type'],
|
|
28
|
+
'storage_path': flags['storage-path'],
|
|
29
|
+
tags: flags.tag,
|
|
30
|
+
title: flags.title,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
buildUploadFormData(payload, filename, fileContents) {
|
|
34
|
+
const formData = new FormData();
|
|
35
|
+
const arrayBuffer = fileContents.buffer.slice(fileContents.byteOffset, fileContents.byteOffset + fileContents.byteLength);
|
|
36
|
+
formData.set('document', new Blob([arrayBuffer]), filename);
|
|
37
|
+
const fields = [
|
|
38
|
+
['title', payload.title],
|
|
39
|
+
['correspondent', this.toOptionalString(payload.correspondent)],
|
|
40
|
+
['document_type', this.toOptionalString(payload.document_type)],
|
|
41
|
+
['storage_path', this.toOptionalString(payload.storage_path)],
|
|
42
|
+
['created', payload.created],
|
|
43
|
+
['archive_serial_number', this.toOptionalString(payload.archive_serial_number)],
|
|
44
|
+
];
|
|
45
|
+
for (const [field, value] of fields) {
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
formData.set(field, value);
|
|
50
|
+
}
|
|
51
|
+
if (payload.tags && payload.tags.length > 0) {
|
|
52
|
+
for (const tag of payload.tags) {
|
|
53
|
+
formData.append('tags', String(tag));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return formData;
|
|
57
|
+
}
|
|
58
|
+
normalizeResult(result) {
|
|
59
|
+
if (result === null || result === undefined) {
|
|
60
|
+
return { result: null };
|
|
61
|
+
}
|
|
62
|
+
if (typeof result === 'object') {
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
return { result };
|
|
66
|
+
}
|
|
67
|
+
plainTemplate(result) {
|
|
68
|
+
if (typeof result.result === 'string' && result.result.trim()) {
|
|
69
|
+
return result.result;
|
|
70
|
+
}
|
|
71
|
+
if (typeof result.id === 'number' || typeof result.id === 'string') {
|
|
72
|
+
return String(result.id);
|
|
73
|
+
}
|
|
74
|
+
return JSON.stringify(result);
|
|
75
|
+
}
|
|
76
|
+
renderUploadOutput(options) {
|
|
77
|
+
if (this.jsonEnabled()) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const { flags, result } = options;
|
|
81
|
+
const dateFormat = flags['date-format'];
|
|
82
|
+
if (flags.plain) {
|
|
83
|
+
const line = this.plainTemplate(result);
|
|
84
|
+
if (line) {
|
|
85
|
+
this.log(line);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const valueFormatter = createValueFormatter(dateFormat);
|
|
90
|
+
const rows = Object.entries(result).map(([field, value]) => ({ field, value }));
|
|
91
|
+
this.logTable([
|
|
92
|
+
{ align: 'right', formatter: formatField, value: 'field' },
|
|
93
|
+
{ formatter: valueFormatter, value: 'value' },
|
|
94
|
+
], rows, { showHeader: false });
|
|
95
|
+
}
|
|
96
|
+
resolveUploadPaths(args, argv) {
|
|
97
|
+
const rawPaths = argv.length > 0 ? argv : args.path ? [args.path] : [];
|
|
98
|
+
if (rawPaths.length === 0) {
|
|
99
|
+
this.error('Provide at least one file path to upload.');
|
|
100
|
+
}
|
|
101
|
+
return rawPaths.map(String);
|
|
102
|
+
}
|
|
103
|
+
async run() {
|
|
104
|
+
const { args, argv, flags, metadata } = await this.parse();
|
|
105
|
+
const { dateFormat, ...apiFlags } = await this.resolveGlobalFlags(flags, metadata);
|
|
106
|
+
const outputFlags = {
|
|
107
|
+
'date-format': dateFormat,
|
|
108
|
+
plain: flags.plain,
|
|
109
|
+
table: flags.table,
|
|
110
|
+
};
|
|
111
|
+
const typedArgs = args;
|
|
112
|
+
const typedFlags = flags;
|
|
113
|
+
const paths = this.resolveUploadPaths(typedArgs, argv);
|
|
114
|
+
const payload = this.buildPayload(typedFlags);
|
|
115
|
+
const results = await this.uploadFiles({ apiFlags, paths, payload });
|
|
116
|
+
if (!this.jsonEnabled()) {
|
|
117
|
+
for (const result of results) {
|
|
118
|
+
this.renderUploadOutput({ flags: outputFlags, result });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return results.length === 1 ? results[0] : results;
|
|
122
|
+
}
|
|
123
|
+
toOptionalString(value) {
|
|
124
|
+
if (value === null || value === undefined) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
return String(value);
|
|
128
|
+
}
|
|
129
|
+
async uploadFile(options) {
|
|
130
|
+
const { apiFlags, filePath, payload } = options;
|
|
131
|
+
let fileContents;
|
|
132
|
+
try {
|
|
133
|
+
fileContents = await readFile(filePath);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
137
|
+
this.error(`Failed to read file at ${filePath}: ${message}`);
|
|
138
|
+
}
|
|
139
|
+
const filename = path.basename(filePath);
|
|
140
|
+
const formData = this.buildUploadFormData(payload, filename, fileContents);
|
|
141
|
+
const rawResult = await this.postApiFormData(apiFlags, '/api/documents/post_document/', formData);
|
|
142
|
+
return this.normalizeResult(rawResult);
|
|
143
|
+
}
|
|
144
|
+
async uploadFiles(options) {
|
|
145
|
+
const { apiFlags, paths, payload } = options;
|
|
146
|
+
const results = [];
|
|
147
|
+
const spinner = this.startSpinner('');
|
|
148
|
+
try {
|
|
149
|
+
for (let index = 0; index < paths.length; index += 1) {
|
|
150
|
+
const filePath = paths[index];
|
|
151
|
+
const filename = path.basename(filePath);
|
|
152
|
+
const prefix = paths.length > 1 ? `[${index + 1}/${paths.length}] ` : '';
|
|
153
|
+
if (spinner) {
|
|
154
|
+
spinner.text = `${prefix}Uploading ${filename}`;
|
|
155
|
+
}
|
|
156
|
+
// eslint-disable-next-line no-await-in-loop -- uploads are sequential to keep spinner output in order.
|
|
157
|
+
const result = await this.uploadFile({ apiFlags, filePath, payload });
|
|
158
|
+
results.push(result);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
spinner?.stop();
|
|
163
|
+
}
|
|
164
|
+
return results;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DeleteCommand } from '../../delete-command.js';
|
|
2
|
+
export default class DocumentsDelete extends DeleteCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
5
|
+
max?: number;
|
|
6
|
+
min?: number;
|
|
7
|
+
}>;
|
|
8
|
+
};
|
|
9
|
+
static description: string;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
protected deleteLabel(id: number | string): string;
|
|
15
|
+
protected deletePath(id: number | string): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { DeleteCommand } from '../../delete-command.js';
|
|
3
|
+
export default class DocumentsDelete extends DeleteCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({ description: 'Document id', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Delete a document';
|
|
8
|
+
static examples = ['<%= config.bin %> <%= command.id %> 123'];
|
|
9
|
+
static flags = {
|
|
10
|
+
yes: Flags.boolean({ char: 'y', description: 'Skip confirmation prompt' }),
|
|
11
|
+
};
|
|
12
|
+
deleteLabel(id) {
|
|
13
|
+
return `document ${id}`;
|
|
14
|
+
}
|
|
15
|
+
deletePath(id) {
|
|
16
|
+
return `/api/documents/${id}/`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
type DownloadResult = {
|
|
3
|
+
bytes: number;
|
|
4
|
+
contentType?: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
id: number;
|
|
7
|
+
original: boolean;
|
|
8
|
+
output: string;
|
|
9
|
+
};
|
|
10
|
+
type DownloadDocumentOptions = {
|
|
11
|
+
apiFlags: {
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
hostname: string;
|
|
14
|
+
token: string;
|
|
15
|
+
};
|
|
16
|
+
id: number;
|
|
17
|
+
original?: boolean;
|
|
18
|
+
output?: string;
|
|
19
|
+
outputDir?: string;
|
|
20
|
+
};
|
|
21
|
+
type DownloadDocumentsOptions = {
|
|
22
|
+
apiFlags: {
|
|
23
|
+
headers: Record<string, string>;
|
|
24
|
+
hostname: string;
|
|
25
|
+
token: string;
|
|
26
|
+
};
|
|
27
|
+
ids: number[];
|
|
28
|
+
original?: boolean;
|
|
29
|
+
output?: string;
|
|
30
|
+
outputDir?: string;
|
|
31
|
+
};
|
|
32
|
+
export default class DocumentsDownload extends BaseCommand {
|
|
33
|
+
static args: {
|
|
34
|
+
id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
35
|
+
};
|
|
36
|
+
static description: string;
|
|
37
|
+
static examples: string[];
|
|
38
|
+
static flags: {
|
|
39
|
+
original: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
40
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
|
+
};
|
|
43
|
+
protected downloadDocument(options: DownloadDocumentOptions): Promise<DownloadResult>;
|
|
44
|
+
protected downloadDocuments(options: DownloadDocumentsOptions): Promise<DownloadResult[]>;
|
|
45
|
+
protected parseIds(raw: string): number[];
|
|
46
|
+
run(): Promise<DownloadResult | DownloadResult[]>;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { BaseCommand } from '../../base-command.js';
|
|
5
|
+
const parseContentDispositionFilename = (header) => {
|
|
6
|
+
if (!header) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const starMatch = /filename\*=([^']*)''([^;]+)/i.exec(header);
|
|
10
|
+
if (starMatch?.[2]) {
|
|
11
|
+
try {
|
|
12
|
+
return decodeURIComponent(starMatch[2]);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return starMatch[2];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const match = /filename="?([^";]+)"?/i.exec(header);
|
|
19
|
+
return match?.[1];
|
|
20
|
+
};
|
|
21
|
+
const resolveFallbackFilename = (id, contentType) => {
|
|
22
|
+
if (contentType?.includes('pdf')) {
|
|
23
|
+
return `document-${id}.pdf`;
|
|
24
|
+
}
|
|
25
|
+
return `document-${id}`;
|
|
26
|
+
};
|
|
27
|
+
const resolveOutputPath = async (output, filename) => {
|
|
28
|
+
if (!output) {
|
|
29
|
+
return path.resolve(process.cwd(), filename);
|
|
30
|
+
}
|
|
31
|
+
const resolved = path.resolve(output);
|
|
32
|
+
try {
|
|
33
|
+
const stats = await stat(resolved);
|
|
34
|
+
if (stats.isDirectory()) {
|
|
35
|
+
return path.join(resolved, filename);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Assume output is a file path if it doesn't exist yet.
|
|
40
|
+
}
|
|
41
|
+
return resolved;
|
|
42
|
+
};
|
|
43
|
+
export default class DocumentsDownload extends BaseCommand {
|
|
44
|
+
static args = {
|
|
45
|
+
id: Args.string({ description: 'Document id or comma-separated list of ids', required: true }),
|
|
46
|
+
};
|
|
47
|
+
static description = 'Download one or more documents';
|
|
48
|
+
static examples = [
|
|
49
|
+
'<%= config.bin %> <%= command.id %> 123 --output document.pdf',
|
|
50
|
+
'<%= config.bin %> <%= command.id %> 123,124 --output-dir ./downloads',
|
|
51
|
+
];
|
|
52
|
+
static flags = {
|
|
53
|
+
original: Flags.boolean({ description: 'Download original file' }),
|
|
54
|
+
output: Flags.string({
|
|
55
|
+
char: 'o',
|
|
56
|
+
description: 'Output file path (single document)',
|
|
57
|
+
exclusive: ['output-dir'],
|
|
58
|
+
}),
|
|
59
|
+
'output-dir': Flags.directory({
|
|
60
|
+
description: 'Output directory (multiple documents)',
|
|
61
|
+
exclusive: ['output'],
|
|
62
|
+
exists: true,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
async downloadDocument(options) {
|
|
66
|
+
const { apiFlags, id, original, output, outputDir } = options;
|
|
67
|
+
const { data, headers: responseHeaders } = await this.fetchApiBinary(apiFlags, `/api/documents/${id}/download/`, {
|
|
68
|
+
original: original ? 'true' : undefined,
|
|
69
|
+
});
|
|
70
|
+
const contentDisposition = responseHeaders.get('content-disposition');
|
|
71
|
+
const contentType = responseHeaders.get('content-type') ?? undefined;
|
|
72
|
+
const headerFilename = parseContentDispositionFilename(contentDisposition);
|
|
73
|
+
const safeFilename = path.basename(headerFilename ?? resolveFallbackFilename(id, contentType));
|
|
74
|
+
const outputPath = outputDir ? path.join(outputDir, safeFilename) : await resolveOutputPath(output, safeFilename);
|
|
75
|
+
await writeFile(outputPath, data);
|
|
76
|
+
return {
|
|
77
|
+
bytes: data.length,
|
|
78
|
+
contentType,
|
|
79
|
+
filename: path.basename(outputPath),
|
|
80
|
+
id,
|
|
81
|
+
original: Boolean(original),
|
|
82
|
+
output: outputPath,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async downloadDocuments(options) {
|
|
86
|
+
const { apiFlags, ids, original, output, outputDir } = options;
|
|
87
|
+
const results = [];
|
|
88
|
+
const spinner = this.startSpinner('');
|
|
89
|
+
try {
|
|
90
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
91
|
+
const id = ids[index];
|
|
92
|
+
const prefix = ids.length > 1 ? `[${index + 1}/${ids.length}] ` : '';
|
|
93
|
+
if (spinner) {
|
|
94
|
+
spinner.text = `${prefix}Downloading document ${id}`;
|
|
95
|
+
}
|
|
96
|
+
// eslint-disable-next-line no-await-in-loop -- downloads are sequential to keep spinner output in order.
|
|
97
|
+
const result = await this.downloadDocument({ apiFlags, id, original, output, outputDir });
|
|
98
|
+
results.push(result);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
spinner?.stop();
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
parseIds(raw) {
|
|
107
|
+
const values = raw
|
|
108
|
+
.split(',')
|
|
109
|
+
.map((value) => value.trim())
|
|
110
|
+
.filter((value) => value.length > 0);
|
|
111
|
+
if (values.length === 0) {
|
|
112
|
+
this.error('Provide at least one document id.');
|
|
113
|
+
}
|
|
114
|
+
return values.map((value) => {
|
|
115
|
+
if (!/^-?\d+$/.test(value)) {
|
|
116
|
+
this.error(`Invalid document id: ${value}`);
|
|
117
|
+
}
|
|
118
|
+
return Number.parseInt(value, 10);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async run() {
|
|
122
|
+
const { args, flags, metadata } = await this.parse();
|
|
123
|
+
const { headers: apiHeaders, hostname, token } = await this.resolveGlobalFlags(flags, metadata);
|
|
124
|
+
const apiFlags = { headers: apiHeaders, hostname, token };
|
|
125
|
+
const typedArgs = args;
|
|
126
|
+
const typedFlags = flags;
|
|
127
|
+
const ids = this.parseIds(typedArgs.id);
|
|
128
|
+
let outputDir = typedFlags['output-dir'];
|
|
129
|
+
if (ids.length > 1) {
|
|
130
|
+
if (typedFlags.output) {
|
|
131
|
+
this.error('Use --output-dir when downloading multiple documents.');
|
|
132
|
+
}
|
|
133
|
+
outputDir = path.resolve(outputDir ?? process.cwd());
|
|
134
|
+
}
|
|
135
|
+
else if (outputDir) {
|
|
136
|
+
this.error('Use --output for a single document download.');
|
|
137
|
+
}
|
|
138
|
+
const results = await this.downloadDocuments({
|
|
139
|
+
apiFlags,
|
|
140
|
+
ids,
|
|
141
|
+
original: typedFlags.original,
|
|
142
|
+
output: typedFlags.output,
|
|
143
|
+
outputDir,
|
|
144
|
+
});
|
|
145
|
+
if (!this.jsonEnabled()) {
|
|
146
|
+
for (const result of results) {
|
|
147
|
+
this.log(`Saved ${result.output}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return results.length === 1 ? results[0] : results;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Document } from '../../types/documents.js';
|
|
2
|
+
import { ListCommand } from '../../list-command.js';
|
|
3
|
+
export default class DocumentsList extends ListCommand<Document> {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
protected listPath: string;
|
|
7
|
+
protected tableAttrs: string[];
|
|
8
|
+
protected plainTemplate(document: Document): string | undefined;
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ListCommand } from '../../list-command.js';
|
|
2
|
+
export default class DocumentsList extends ListCommand {
|
|
3
|
+
static description = 'List documents';
|
|
4
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
5
|
+
listPath = '/api/documents/';
|
|
6
|
+
tableAttrs = ['id', 'title', 'created', 'added', 'correspondent', 'document_type', 'tags'];
|
|
7
|
+
plainTemplate(document) {
|
|
8
|
+
const title = document.title?.trim();
|
|
9
|
+
if (!title) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
return `[${document.id}] ${title}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Document } from '../../types/documents.js';
|
|
2
|
+
import { ShowCommand } from '../../show-command.js';
|
|
3
|
+
export default class DocumentsShow extends ShowCommand<Document> {
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/interfaces").Arg<number, {
|
|
6
|
+
max?: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
static description: string;
|
|
11
|
+
static examples: string[];
|
|
12
|
+
protected plainTemplate(document: Document): string | undefined;
|
|
13
|
+
protected showPath(id: number | string): string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { ShowCommand } from '../../show-command.js';
|
|
3
|
+
export default class DocumentsShow extends ShowCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
id: Args.integer({ description: 'Document id', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Show document details';
|
|
8
|
+
static examples = ['<%= config.bin %> <%= command.id %> 123'];
|
|
9
|
+
plainTemplate(document) {
|
|
10
|
+
const title = document.title?.trim();
|
|
11
|
+
if (!title) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
return `[${document.id}] ${title}`;
|
|
15
|
+
}
|
|
16
|
+
showPath(id) {
|
|
17
|
+
return `/api/documents/${id}/`;
|
|
18
|
+
}
|
|
19
|
+
}
|