@things-factory/pdf 8.0.37 → 9.0.0-9.0.0-beta.59.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.
Files changed (29) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/dist-server/tsconfig.tsbuildinfo +1 -1
  3. package/package.json +4 -4
  4. package/client/bootstrap.js +0 -6
  5. package/client/pages/pdf-release/pdf-release-importer.ts +0 -87
  6. package/client/pages/pdf-release/pdf-release-list-page.ts +0 -398
  7. package/client/pages/pdf-template/pdf-template-importer.ts +0 -87
  8. package/client/pages/pdf-template/pdf-template-list-page.ts +0 -491
  9. package/client/route.ts +0 -11
  10. package/client/tsconfig.json +0 -13
  11. package/server/controller/pdf-service.ts +0 -35
  12. package/server/index.ts +0 -3
  13. package/server/routers/pdf-private-router.ts +0 -85
  14. package/server/routers/pdf-public-router.ts +0 -80
  15. package/server/routers/proxy-router.ts +0 -9
  16. package/server/routes.ts +0 -25
  17. package/server/service/index.ts +0 -38
  18. package/server/service/pdf-generate/pdf-generate-resolver.ts +0 -81
  19. package/server/service/pdf-release/index.ts +0 -7
  20. package/server/service/pdf-release/pdf-release-mutation.ts +0 -138
  21. package/server/service/pdf-release/pdf-release-query.ts +0 -51
  22. package/server/service/pdf-release/pdf-release-type.ts +0 -55
  23. package/server/service/pdf-release/pdf-release.ts +0 -103
  24. package/server/service/pdf-template/index.ts +0 -7
  25. package/server/service/pdf-template/pdf-template-mutation.ts +0 -138
  26. package/server/service/pdf-template/pdf-template-query.ts +0 -51
  27. package/server/service/pdf-template/pdf-template-type.ts +0 -87
  28. package/server/service/pdf-template/pdf-template.ts +0 -108
  29. 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
- }
@@ -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,3 +0,0 @@
1
- export * from './service'
2
-
3
- import './routes'
@@ -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
- })