@things-factory/pdf 8.0.0-beta.0 → 8.0.0-beta.2
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 -4
- package/client/bootstrap.js +0 -6
- package/client/pages/pdf-release/pdf-release-importer.ts +0 -87
- package/client/pages/pdf-release/pdf-release-list-page.ts +0 -398
- package/client/pages/pdf-template/pdf-template-importer.ts +0 -87
- package/client/pages/pdf-template/pdf-template-list-page.ts +0 -491
- package/client/route.ts +0 -11
- package/client/tsconfig.json +0 -13
- package/server/controller/pdf-service.ts +0 -35
- package/server/index.ts +0 -3
- package/server/routers/pdf-private-router.ts +0 -85
- package/server/routers/pdf-public-router.ts +0 -80
- package/server/routers/proxy-router.ts +0 -9
- package/server/routes.ts +0 -19
- package/server/service/index.ts +0 -38
- package/server/service/pdf-generate/pdf-generate-resolver.ts +0 -81
- package/server/service/pdf-release/index.ts +0 -7
- package/server/service/pdf-release/pdf-release-mutation.ts +0 -138
- package/server/service/pdf-release/pdf-release-query.ts +0 -51
- package/server/service/pdf-release/pdf-release-type.ts +0 -55
- package/server/service/pdf-release/pdf-release.ts +0 -103
- package/server/service/pdf-template/index.ts +0 -7
- package/server/service/pdf-template/pdf-template-mutation.ts +0 -138
- package/server/service/pdf-template/pdf-template-query.ts +0 -51
- package/server/service/pdf-template/pdf-template-type.ts +0 -87
- package/server/service/pdf-template/pdf-template.ts +0 -108
- package/server/tsconfig.json +0 -10
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
import '@material/web/icon/icon.js'
|
|
2
|
-
import '@material/web/button/elevated-button.js'
|
|
3
|
-
import '@operato/data-grist/ox-grist.js'
|
|
4
|
-
import '@operato/data-grist/ox-filters-form.js'
|
|
5
|
-
import '@operato/data-grist/ox-record-creator.js'
|
|
6
|
-
|
|
7
|
-
import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
|
|
8
|
-
import { PageView, store } from '@operato/shell'
|
|
9
|
-
import { css, html } from 'lit'
|
|
10
|
-
import { customElement, property, query } from 'lit/decorators.js'
|
|
11
|
-
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
|
|
12
|
-
import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
|
|
13
|
-
import { client } from '@operato/graphql'
|
|
14
|
-
import { i18next, localize } from '@operato/i18n'
|
|
15
|
-
import { notify, openPopup } from '@operato/layout'
|
|
16
|
-
import { OxPopup, OxPrompt } from '@operato/popup'
|
|
17
|
-
import { isMobileDevice } from '@operato/utils'
|
|
18
|
-
|
|
19
|
-
import { connect } from 'pwa-helpers/connect-mixin'
|
|
20
|
-
import gql from 'graphql-tag'
|
|
21
|
-
|
|
22
|
-
import { PDFTemplateImporter } from './pdf-template-importer'
|
|
23
|
-
|
|
24
|
-
@customElement('pdf-template-list-page')
|
|
25
|
-
export class PDFTemplateListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
|
|
26
|
-
static styles = [
|
|
27
|
-
ScrollbarStyles,
|
|
28
|
-
CommonGristStyles,
|
|
29
|
-
CommonHeaderStyles,
|
|
30
|
-
css`
|
|
31
|
-
:host {
|
|
32
|
-
display: flex;
|
|
33
|
-
|
|
34
|
-
width: 100%;
|
|
35
|
-
|
|
36
|
-
--grid-record-emphasized-background-color: #8b0000;
|
|
37
|
-
--grid-record-emphasized-color: #ff6b6b;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
ox-grist {
|
|
41
|
-
overflow-y: auto;
|
|
42
|
-
flex: 1;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
ox-filters-form {
|
|
46
|
-
flex: 1;
|
|
47
|
-
}
|
|
48
|
-
`
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
static get scopedElements() {
|
|
52
|
-
return {
|
|
53
|
-
'pdf-template-importer': PDFTemplateImporter
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@property({ type: Object }) gristConfig: any
|
|
58
|
-
@property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
|
|
59
|
-
|
|
60
|
-
@query('ox-grist') private grist!: DataGrist
|
|
61
|
-
|
|
62
|
-
get context() {
|
|
63
|
-
return {
|
|
64
|
-
title: i18next.t('title.pdf-template list'),
|
|
65
|
-
search: {
|
|
66
|
-
handler: (search: string) => {
|
|
67
|
-
this.grist.searchText = search
|
|
68
|
-
},
|
|
69
|
-
value: this.grist.searchText
|
|
70
|
-
},
|
|
71
|
-
filter: {
|
|
72
|
-
handler: () => {
|
|
73
|
-
this.grist.toggleHeadroom()
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
help: 'pdf/pdf-template',
|
|
77
|
-
actions: [
|
|
78
|
-
{
|
|
79
|
-
title: i18next.t('button.save'),
|
|
80
|
-
action: this._updatePDFTemplate.bind(this),
|
|
81
|
-
...CommonButtonStyles.save
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
title: i18next.t('button.delete'),
|
|
85
|
-
action: this._deletePDFTemplate.bind(this),
|
|
86
|
-
...CommonButtonStyles.delete
|
|
87
|
-
}
|
|
88
|
-
],
|
|
89
|
-
exportable: {
|
|
90
|
-
name: i18next.t('title.pdf-template list'),
|
|
91
|
-
data: this.exportHandler.bind(this)
|
|
92
|
-
},
|
|
93
|
-
importable: {
|
|
94
|
-
handler: this.importHandler.bind(this)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
render() {
|
|
100
|
-
const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
|
|
101
|
-
|
|
102
|
-
return html`
|
|
103
|
-
<ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
|
|
104
|
-
<div slot="headroom" class="header">
|
|
105
|
-
<div class="filters">
|
|
106
|
-
<ox-filters-form autofocus without-search></ox-filters-form>
|
|
107
|
-
|
|
108
|
-
<div id="modes">
|
|
109
|
-
<md-icon @click=${() => (this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on</md-icon>
|
|
110
|
-
<md-icon @click=${() => (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted</md-icon>
|
|
111
|
-
<md-icon @click=${() => (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps</md-icon>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
<ox-record-creator id="add" .callback=${this.creationCallback.bind(this)}>
|
|
115
|
-
<button>
|
|
116
|
-
<md-icon>add</md-icon>
|
|
117
|
-
</button>
|
|
118
|
-
</ox-record-creator>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</ox-grist>
|
|
122
|
-
`
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async pageInitialized(lifecycle: any) {
|
|
126
|
-
this.gristConfig = {
|
|
127
|
-
list: {
|
|
128
|
-
fields: ['name', 'description'],
|
|
129
|
-
details: ['active', 'updatedAt']
|
|
130
|
-
},
|
|
131
|
-
columns: [
|
|
132
|
-
{ type: 'gutter', gutterName: 'sequence' },
|
|
133
|
-
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
|
|
134
|
-
{
|
|
135
|
-
type: 'string',
|
|
136
|
-
name: 'name',
|
|
137
|
-
header: i18next.t('field.name'),
|
|
138
|
-
record: {
|
|
139
|
-
editable: true
|
|
140
|
-
},
|
|
141
|
-
filter: 'search',
|
|
142
|
-
sortable: true,
|
|
143
|
-
width: 150
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
type: 'string',
|
|
147
|
-
name: 'description',
|
|
148
|
-
header: i18next.t('field.description'),
|
|
149
|
-
record: {
|
|
150
|
-
editable: true
|
|
151
|
-
},
|
|
152
|
-
filter: 'search',
|
|
153
|
-
width: 200
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
type: 'checkbox',
|
|
157
|
-
name: 'active',
|
|
158
|
-
label: true,
|
|
159
|
-
header: i18next.t('field.active'),
|
|
160
|
-
record: {
|
|
161
|
-
editable: true
|
|
162
|
-
},
|
|
163
|
-
filter: true,
|
|
164
|
-
sortable: true,
|
|
165
|
-
width: 60
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
type: 'string',
|
|
169
|
-
name: 'state',
|
|
170
|
-
label: true,
|
|
171
|
-
header: i18next.t('field.state'),
|
|
172
|
-
record: {
|
|
173
|
-
editable: true
|
|
174
|
-
},
|
|
175
|
-
width: 120
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
type: 'template',
|
|
179
|
-
name: 'content_template',
|
|
180
|
-
label: true,
|
|
181
|
-
header: i18next.t('field.content_template'),
|
|
182
|
-
record: {
|
|
183
|
-
editable: true,
|
|
184
|
-
options: {
|
|
185
|
-
language: 'html'
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
width: 120
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
type: 'template',
|
|
192
|
-
name: 'header_template',
|
|
193
|
-
label: true,
|
|
194
|
-
header: i18next.t('field.header_template'),
|
|
195
|
-
record: {
|
|
196
|
-
editable: true,
|
|
197
|
-
options: {
|
|
198
|
-
language: 'html'
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
width: 120
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
type: 'template',
|
|
205
|
-
name: 'footer_template',
|
|
206
|
-
label: true,
|
|
207
|
-
header: i18next.t('field.footer_template'),
|
|
208
|
-
record: {
|
|
209
|
-
editable: true,
|
|
210
|
-
options: {
|
|
211
|
-
language: 'html'
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
width: 120
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
type: 'template',
|
|
218
|
-
name: 'cover_template',
|
|
219
|
-
label: true,
|
|
220
|
-
header: i18next.t('field.cover_template'),
|
|
221
|
-
record: {
|
|
222
|
-
editable: true,
|
|
223
|
-
options: {
|
|
224
|
-
language: 'html'
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
width: 120
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
type: 'template',
|
|
231
|
-
name: 'last_template',
|
|
232
|
-
label: true,
|
|
233
|
-
header: i18next.t('field.last_template'),
|
|
234
|
-
record: {
|
|
235
|
-
editable: true,
|
|
236
|
-
options: {
|
|
237
|
-
language: 'html'
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
width: 120
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
type: 'select',
|
|
244
|
-
name: 'page_size',
|
|
245
|
-
label: true,
|
|
246
|
-
header: i18next.t('field.page_size'),
|
|
247
|
-
record: {
|
|
248
|
-
editable: true,
|
|
249
|
-
options: ['', 'A4', 'B4']
|
|
250
|
-
},
|
|
251
|
-
width: 120
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
type: 'string',
|
|
255
|
-
name: 'watermark',
|
|
256
|
-
label: true,
|
|
257
|
-
header: i18next.t('field.watermark'),
|
|
258
|
-
record: {
|
|
259
|
-
editable: true
|
|
260
|
-
},
|
|
261
|
-
width: 120
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
{
|
|
265
|
-
type: 'resource-object',
|
|
266
|
-
name: 'updater',
|
|
267
|
-
header: i18next.t('field.updater'),
|
|
268
|
-
record: {
|
|
269
|
-
editable: false
|
|
270
|
-
},
|
|
271
|
-
sortable: true,
|
|
272
|
-
width: 120
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
type: 'datetime',
|
|
276
|
-
name: 'updatedAt',
|
|
277
|
-
header: i18next.t('field.updated_at'),
|
|
278
|
-
record: {
|
|
279
|
-
editable: false
|
|
280
|
-
},
|
|
281
|
-
sortable: true,
|
|
282
|
-
width: 180
|
|
283
|
-
}
|
|
284
|
-
],
|
|
285
|
-
rows: {
|
|
286
|
-
appendable: false,
|
|
287
|
-
selectable: {
|
|
288
|
-
multiple: true
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
sorters: [
|
|
292
|
-
{
|
|
293
|
-
name: 'name'
|
|
294
|
-
}
|
|
295
|
-
]
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async pageUpdated(changes: any, lifecycle: any) {
|
|
300
|
-
if (this.active) {
|
|
301
|
-
// do something here when this page just became as active
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
|
|
306
|
-
const response = await client.query({
|
|
307
|
-
query: gql`
|
|
308
|
-
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
309
|
-
responses: PDFTemplates(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
310
|
-
items {
|
|
311
|
-
id
|
|
312
|
-
name
|
|
313
|
-
description
|
|
314
|
-
active
|
|
315
|
-
state
|
|
316
|
-
content_template
|
|
317
|
-
header_template
|
|
318
|
-
footer_template
|
|
319
|
-
cover_template
|
|
320
|
-
last_template
|
|
321
|
-
page_size
|
|
322
|
-
watermark
|
|
323
|
-
updater {
|
|
324
|
-
id
|
|
325
|
-
name
|
|
326
|
-
}
|
|
327
|
-
updatedAt
|
|
328
|
-
}
|
|
329
|
-
total
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
`,
|
|
333
|
-
variables: {
|
|
334
|
-
filters,
|
|
335
|
-
pagination: { page, limit },
|
|
336
|
-
sortings
|
|
337
|
-
}
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
return {
|
|
341
|
-
total: response.data.responses.total || 0,
|
|
342
|
-
records: response.data.responses.items || []
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async _deletePDFTemplate() {
|
|
347
|
-
if (
|
|
348
|
-
await OxPrompt.open({
|
|
349
|
-
title: i18next.t('text.are_you_sure'),
|
|
350
|
-
text: i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }),
|
|
351
|
-
confirmButton: { text: i18next.t('button.confirm') },
|
|
352
|
-
cancelButton: { text: i18next.t('button.cancel') }
|
|
353
|
-
})
|
|
354
|
-
) {
|
|
355
|
-
const ids = this.grist.selected.map(record => record.id)
|
|
356
|
-
if (ids && ids.length > 0) {
|
|
357
|
-
const response = await client.mutate({
|
|
358
|
-
mutation: gql`
|
|
359
|
-
mutation ($ids: [String!]!) {
|
|
360
|
-
deletePDFTemplates(ids: $ids)
|
|
361
|
-
}
|
|
362
|
-
`,
|
|
363
|
-
variables: {
|
|
364
|
-
ids
|
|
365
|
-
}
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
if (!response.errors) {
|
|
369
|
-
this.grist.fetch()
|
|
370
|
-
notify({
|
|
371
|
-
message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
|
|
372
|
-
})
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
async _updatePDFTemplate() {
|
|
379
|
-
let patches = this.grist.dirtyRecords
|
|
380
|
-
if (patches && patches.length) {
|
|
381
|
-
patches = patches.map(patch => {
|
|
382
|
-
let patchField: any = patch.id ? { id: patch.id } : {}
|
|
383
|
-
const dirtyFields = patch.__dirtyfields__
|
|
384
|
-
for (let key in dirtyFields) {
|
|
385
|
-
patchField[key] = dirtyFields[key].after
|
|
386
|
-
}
|
|
387
|
-
patchField.cuFlag = patch.__dirty__
|
|
388
|
-
|
|
389
|
-
return patchField
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
const response = await client.mutate({
|
|
393
|
-
mutation: gql`
|
|
394
|
-
mutation ($patches: [PDFTemplatePatch!]!) {
|
|
395
|
-
updateMultiplePDFTemplate(patches: $patches) {
|
|
396
|
-
name
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
`,
|
|
400
|
-
variables: {
|
|
401
|
-
patches
|
|
402
|
-
}
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
if (!response.errors) {
|
|
406
|
-
this.grist.fetch()
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async creationCallback(pdfTemplate) {
|
|
412
|
-
try {
|
|
413
|
-
const response = await client.query({
|
|
414
|
-
query: gql`
|
|
415
|
-
mutation ($pdfTemplate: NewPDFTemplate!) {
|
|
416
|
-
createPDFTemplate(pdfTemplate: $pdfTemplate) {
|
|
417
|
-
id
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
`,
|
|
421
|
-
variables: {
|
|
422
|
-
pdfTemplate
|
|
423
|
-
},
|
|
424
|
-
context: {
|
|
425
|
-
hasUpload: true
|
|
426
|
-
}
|
|
427
|
-
})
|
|
428
|
-
|
|
429
|
-
if (!response.errors) {
|
|
430
|
-
this.grist.fetch()
|
|
431
|
-
document.dispatchEvent(
|
|
432
|
-
new CustomEvent('notify', {
|
|
433
|
-
detail: {
|
|
434
|
-
message: i18next.t('text.data_created_successfully')
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return true
|
|
441
|
-
} catch (ex) {
|
|
442
|
-
console.error(ex)
|
|
443
|
-
document.dispatchEvent(
|
|
444
|
-
new CustomEvent('notify', {
|
|
445
|
-
detail: {
|
|
446
|
-
type: 'error',
|
|
447
|
-
message: i18next.t('text.error')
|
|
448
|
-
}
|
|
449
|
-
})
|
|
450
|
-
)
|
|
451
|
-
return false
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async exportHandler() {
|
|
456
|
-
const exportTargets = this.grist.selected.length ? this.grist.selected : this.grist.dirtyData.records
|
|
457
|
-
const targetFieldSet = new Set(['id', 'name', 'description', 'active'])
|
|
458
|
-
|
|
459
|
-
return exportTargets.map(pdfTemplate => {
|
|
460
|
-
let tempObj = {}
|
|
461
|
-
for (const field of targetFieldSet) {
|
|
462
|
-
tempObj[field] = pdfTemplate[field]
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return tempObj
|
|
466
|
-
})
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
async importHandler(records) {
|
|
470
|
-
const popup = openPopup(
|
|
471
|
-
html`
|
|
472
|
-
<pdf-template-importer
|
|
473
|
-
.pdfTemplates=${records}
|
|
474
|
-
@imported=${() => {
|
|
475
|
-
history.back()
|
|
476
|
-
this.grist.fetch()
|
|
477
|
-
}}
|
|
478
|
-
></pdf-template-importer>
|
|
479
|
-
`,
|
|
480
|
-
{
|
|
481
|
-
backdrop: true,
|
|
482
|
-
size: 'large',
|
|
483
|
-
title: i18next.t('title.import pdf-template')
|
|
484
|
-
}
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
popup.onclosed = () => {
|
|
488
|
-
this.grist.fetch()
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
package/client/route.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export default function route(page: string) {
|
|
2
|
-
switch (page) {
|
|
3
|
-
case 'pdf-template-list-page':
|
|
4
|
-
import('./pages/pdf-template/pdf-template-list-page')
|
|
5
|
-
return page
|
|
6
|
-
|
|
7
|
-
case 'pdf-release-list':
|
|
8
|
-
import('./pages/pdf-release/pdf-release-list-page')
|
|
9
|
-
return page
|
|
10
|
-
}
|
|
11
|
-
}
|
package/client/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig-base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"experimentalDecorators": true,
|
|
5
|
-
"skipLibCheck": true,
|
|
6
|
-
"strict": true,
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"module": "esnext",
|
|
9
|
-
"outDir": "../dist-client",
|
|
10
|
-
"baseUrl": "./"
|
|
11
|
-
},
|
|
12
|
-
"include": ["./**/*"]
|
|
13
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import * as ejs from 'ejs'
|
|
2
|
-
import { PDFDocument } from 'pdf-lib'
|
|
3
|
-
import { PDFTemplate } from '../service/pdf-template/pdf-template'
|
|
4
|
-
|
|
5
|
-
export class PdfService {
|
|
6
|
-
async generatePdf(template: PDFTemplate, data: any): Promise<Buffer> {
|
|
7
|
-
const pdfDoc = await PDFDocument.create()
|
|
8
|
-
|
|
9
|
-
// 템플릿 렌더링
|
|
10
|
-
const renderedContent = await ejs.render(template.content_template || '', data)
|
|
11
|
-
|
|
12
|
-
// PDF 생성
|
|
13
|
-
const page = pdfDoc.addPage()
|
|
14
|
-
const { width, height } = page.getSize()
|
|
15
|
-
page.drawText(renderedContent, {
|
|
16
|
-
x: 50,
|
|
17
|
-
y: height - 50,
|
|
18
|
-
size: 12
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// 추가적으로 header, footer, cover 등의 렌더링 및 PDF 구성 가능
|
|
22
|
-
if (template.header_template) {
|
|
23
|
-
const renderedHeader = await ejs.render(template.header_template, data)
|
|
24
|
-
// Header 처리 로직
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (template.footer_template) {
|
|
28
|
-
const renderedFooter = await ejs.render(template.footer_template, data)
|
|
29
|
-
// Footer 처리 로직
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const pdfBytes = await pdfDoc.save()
|
|
33
|
-
return Buffer.from(pdfBytes)
|
|
34
|
-
}
|
|
35
|
-
}
|
package/server/index.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import Router from 'koa-router'
|
|
2
|
-
import * as fs from 'fs'
|
|
3
|
-
import * as path from 'path'
|
|
4
|
-
import send from 'koa-send'
|
|
5
|
-
|
|
6
|
-
import { PDFRelease } from '../service/pdf-release/pdf-release'
|
|
7
|
-
import { PDFTemplate } from '../service/pdf-template/pdf-template'
|
|
8
|
-
import { PdfService } from '../controller/pdf-service'
|
|
9
|
-
|
|
10
|
-
const PDF_STORAGE_PATH = '/path/to/pdf/storage' // 실제 파일 저장 경로
|
|
11
|
-
const PDF_BASE_URL = 'https://yourdomain.com/pdf/' // 파일 접근 URL의 기본 경로
|
|
12
|
-
|
|
13
|
-
const pdfService = new PdfService()
|
|
14
|
-
|
|
15
|
-
export const pdfPrivateRouter = new Router()
|
|
16
|
-
|
|
17
|
-
function parseQuery(query) {
|
|
18
|
-
for (const key in query) {
|
|
19
|
-
if (query.hasOwnProperty(key)) {
|
|
20
|
-
try {
|
|
21
|
-
query[key] = JSON.parse(query[key])
|
|
22
|
-
} catch (error) {
|
|
23
|
-
// do nothing
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return query
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// PDF 생성 및 저장 라우터
|
|
32
|
-
pdfPrivateRouter.post('/generate-pdf', async ctx => {
|
|
33
|
-
const { templateId, data } = ctx.request.body
|
|
34
|
-
const { domain, user, tx } = ctx.state
|
|
35
|
-
|
|
36
|
-
const templateRepository = tx.getRepository(PDFTemplate)
|
|
37
|
-
const releaseRepository = tx.getRepository(PDFRelease)
|
|
38
|
-
|
|
39
|
-
const template = await templateRepository.findOne(templateId)
|
|
40
|
-
if (!template) {
|
|
41
|
-
ctx.throw(404, 'PDF Template not found')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// EJS 템플릿을 사용하여 PDF 생성
|
|
45
|
-
const pdfBuffer = await pdfService.generatePdf(template, data)
|
|
46
|
-
|
|
47
|
-
// 파일 저장 경로 및 URL 생성
|
|
48
|
-
const fileName = `${template.name}-${Date.now()}.pdf`
|
|
49
|
-
const filePath = path.join(PDF_STORAGE_PATH, fileName)
|
|
50
|
-
const fileUrl = `${PDF_BASE_URL}${fileName}`
|
|
51
|
-
|
|
52
|
-
// 파일 저장
|
|
53
|
-
fs.writeFileSync(filePath, pdfBuffer)
|
|
54
|
-
|
|
55
|
-
// 발행 이력 기록
|
|
56
|
-
const release = releaseRepository.create({
|
|
57
|
-
template,
|
|
58
|
-
filePath,
|
|
59
|
-
fileUrl,
|
|
60
|
-
state: 'published',
|
|
61
|
-
releasedBy: user,
|
|
62
|
-
domain
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
await releaseRepository.save(release)
|
|
66
|
-
|
|
67
|
-
ctx.body = release
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
// PDF 다운로드 라우터
|
|
71
|
-
pdfPrivateRouter.get('/download-pdf/:id', async ctx => {
|
|
72
|
-
const { id } = ctx.params
|
|
73
|
-
const { tx } = ctx.state
|
|
74
|
-
|
|
75
|
-
const releaseRepository = tx.getRepository(PDFRelease)
|
|
76
|
-
const pdfRelease = await releaseRepository.findOne(id)
|
|
77
|
-
|
|
78
|
-
if (!pdfRelease || !pdfRelease.filePath || !fs.existsSync(pdfRelease.filePath)) {
|
|
79
|
-
ctx.throw(404, 'PDF file not found')
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// PDF 파일을 클라이언트에 전송
|
|
83
|
-
ctx.attachment(path.basename(pdfRelease.filePath))
|
|
84
|
-
await send(ctx, pdfRelease.filePath, { root: '/' })
|
|
85
|
-
})
|