@tfw.in/structura-lib 0.2.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 (95) hide show
  1. package/PRODUCTION_ARCHITECTURE.md +511 -0
  2. package/README.md +379 -0
  3. package/SAVE_FUNCTIONALITY_COMPLETE.md +448 -0
  4. package/dist/cjs/EditableContent.js +150 -0
  5. package/dist/cjs/HtmlViewer.js +587 -0
  6. package/dist/cjs/PdfComponents.js +16 -0
  7. package/dist/cjs/PdfDocumentViewer.js +281 -0
  8. package/dist/cjs/Structura.js +806 -0
  9. package/dist/cjs/Table.js +164 -0
  10. package/dist/cjs/TableCell.js +115 -0
  11. package/dist/cjs/accuracyMetrics.js +39 -0
  12. package/dist/cjs/helpers/preprocessData.js +143 -0
  13. package/dist/cjs/index.js +7 -0
  14. package/dist/cjs/lib/polyfills.js +15 -0
  15. package/dist/cjs/lib/utils.js +10 -0
  16. package/dist/cjs/node_modules/react-icons/fa/index.esm.js +14 -0
  17. package/dist/cjs/node_modules/react-icons/lib/esm/iconBase.js +69 -0
  18. package/dist/cjs/node_modules/react-icons/lib/esm/iconContext.js +15 -0
  19. package/dist/cjs/polyfills.js +19 -0
  20. package/dist/cjs/route.js +102 -0
  21. package/dist/cjs/styles.css +7 -0
  22. package/dist/cjs/styles.css.map +1 -0
  23. package/dist/cjs/ui/badge.js +34 -0
  24. package/dist/cjs/ui/button.js +71 -0
  25. package/dist/cjs/ui/card.js +86 -0
  26. package/dist/cjs/ui/progress.js +45 -0
  27. package/dist/cjs/ui/scroll-area.js +62 -0
  28. package/dist/cjs/ui/tabs.js +60 -0
  29. package/dist/cjs/worker.js +36 -0
  30. package/dist/esm/EditableContent.js +161 -0
  31. package/dist/esm/HtmlViewer.js +640 -0
  32. package/dist/esm/PdfComponents.js +21 -0
  33. package/dist/esm/PdfDocumentViewer.js +294 -0
  34. package/dist/esm/Structura.js +951 -0
  35. package/dist/esm/Table.js +182 -0
  36. package/dist/esm/TableCell.js +122 -0
  37. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +305 -0
  38. package/dist/esm/accuracyMetrics.js +41 -0
  39. package/dist/esm/helpers/preprocessData.js +152 -0
  40. package/dist/esm/index.js +1 -0
  41. package/dist/esm/lib/polyfills.js +13 -0
  42. package/dist/esm/lib/utils.js +8 -0
  43. package/dist/esm/node_modules/react-icons/fa/index.esm.js +11 -0
  44. package/dist/esm/node_modules/react-icons/lib/esm/iconBase.js +66 -0
  45. package/dist/esm/node_modules/react-icons/lib/esm/iconContext.js +12 -0
  46. package/dist/esm/polyfills.js +17 -0
  47. package/dist/esm/route.js +154 -0
  48. package/dist/esm/styles.css +7 -0
  49. package/dist/esm/styles.css.map +1 -0
  50. package/dist/esm/types/EditableContent.d.ts +9 -0
  51. package/dist/esm/types/HtmlViewer.d.ts +10 -0
  52. package/dist/esm/types/PdfComponents.d.ts +35 -0
  53. package/dist/esm/types/PdfDocumentViewer.d.ts +22 -0
  54. package/dist/esm/types/Structura.d.ts +11 -0
  55. package/dist/esm/types/Table.d.ts +12 -0
  56. package/dist/esm/types/TableCell.d.ts +13 -0
  57. package/dist/esm/types/accuracy.d.ts +23 -0
  58. package/dist/esm/types/accuracyMetrics.d.ts +5 -0
  59. package/dist/esm/types/helpers/flattenJSON.d.ts +1 -0
  60. package/dist/esm/types/helpers/hardMerging.d.ts +2 -0
  61. package/dist/esm/types/helpers/index.d.ts +6 -0
  62. package/dist/esm/types/helpers/jsonToHtml.d.ts +40 -0
  63. package/dist/esm/types/helpers/preprocessData.d.ts +3 -0
  64. package/dist/esm/types/helpers/removeMetadata.d.ts +1 -0
  65. package/dist/esm/types/helpers/tableProcessor.d.ts +1 -0
  66. package/dist/esm/types/index.d.ts +3 -0
  67. package/dist/esm/types/lib/polyfills.d.ts +1 -0
  68. package/dist/esm/types/lib/utils.d.ts +2 -0
  69. package/dist/esm/types/polyfills.d.ts +1 -0
  70. package/dist/esm/types/route.d.ts +45 -0
  71. package/dist/esm/types/test-app/src/App.d.ts +4 -0
  72. package/dist/esm/types/test-app/src/main.d.ts +1 -0
  73. package/dist/esm/types/test-app/vite.config.d.ts +2 -0
  74. package/dist/esm/types/types.d.ts +23 -0
  75. package/dist/esm/types/ui/alert.d.ts +8 -0
  76. package/dist/esm/types/ui/badge.d.ts +9 -0
  77. package/dist/esm/types/ui/button.d.ts +11 -0
  78. package/dist/esm/types/ui/card.d.ts +8 -0
  79. package/dist/esm/types/ui/progress.d.ts +6 -0
  80. package/dist/esm/types/ui/scroll-area.d.ts +5 -0
  81. package/dist/esm/types/ui/skeleton.d.ts +2 -0
  82. package/dist/esm/types/ui/tabs.d.ts +7 -0
  83. package/dist/esm/types/worker.d.ts +1 -0
  84. package/dist/esm/ui/badge.js +31 -0
  85. package/dist/esm/ui/button.js +50 -0
  86. package/dist/esm/ui/card.js +67 -0
  87. package/dist/esm/ui/progress.js +26 -0
  88. package/dist/esm/ui/scroll-area.js +45 -0
  89. package/dist/esm/ui/tabs.js +39 -0
  90. package/dist/esm/worker.js +50 -0
  91. package/dist/index.d.ts +38 -0
  92. package/package.json +85 -0
  93. package/server/README.md +203 -0
  94. package/server/db.js +142 -0
  95. package/server/server.js +165 -0
@@ -0,0 +1,38 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ export { Block, BlockOutput, BlockSchema, DocumentOutput } from '@tfw.in/structura-sdk';
3
+
4
+ interface PdfHighlighterpropss {
5
+ initialPdfPath?: string | null;
6
+ initialJsonData?: any | null;
7
+ props: {
8
+ APIKey: string;
9
+ baseUrl?: string;
10
+ onSave?: (editedData: any) => void | Promise<void>;
11
+ };
12
+ }
13
+ declare function Structura({ initialPdfPath, initialJsonData, props }: PdfHighlighterpropss): react_jsx_runtime.JSX.Element;
14
+
15
+ interface PdfHighlighterProps {
16
+ initialPdfPath?: string | null;
17
+ prop?: {
18
+ apiKey: string;
19
+ };
20
+ }
21
+ interface TableProps {
22
+ data: any;
23
+ onCellClick?: (cellId: string) => void;
24
+ selectedCellId?: string | null;
25
+ }
26
+ interface TableCellProps {
27
+ cell: any;
28
+ onClick?: () => void;
29
+ isSelected?: boolean;
30
+ }
31
+ interface EditableContentProps {
32
+ content: string;
33
+ onChange: (newContent: string) => void;
34
+ isEditing?: boolean;
35
+ }
36
+
37
+ export { Structura };
38
+ export type { EditableContentProps, PdfHighlighterProps, TableCellProps, TableProps };
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@tfw.in/structura-lib",
3
+ "version": "0.2.0",
4
+ "description": "Structura Library Components",
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "dist/pdf.worker.mjs",
11
+ "server",
12
+ "README.md",
13
+ "PRODUCTION_ARCHITECTURE.md",
14
+ "SAVE_FUNCTIONALITY_COMPLETE.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "rollup -c",
18
+ "dev": "rollup -c -w",
19
+ "clean": "rimraf dist",
20
+ "tw:init": "tailwindcss init",
21
+ "build:css": "postcss styles.css -o dist/styles.css --minify",
22
+ "server": "node server/server.js",
23
+ "server:dev": "nodemon server/server.js"
24
+ },
25
+ "peerDependencies": {
26
+ "react": "^18.2.0",
27
+ "react-dom": "^18.2.0",
28
+ "tailwindcss": "^3.3.0"
29
+ },
30
+ "dependencies": {
31
+ "@radix-ui/react-progress": "^1.0.3",
32
+ "@radix-ui/react-scroll-area": "^1.0.5",
33
+ "@radix-ui/react-slot": "^1.2.3",
34
+ "@radix-ui/react-tabs": "^1.0.4",
35
+ "@tailwindcss/postcss": "^4.1.8",
36
+ "@tfw.in/structura-sdk": "^0.1.0",
37
+ "@types/better-sqlite3": "^7.6.13",
38
+ "@types/cors": "^2.8.19",
39
+ "@types/express": "^5.0.5",
40
+ "better-sqlite3": "^12.4.1",
41
+ "class-variance-authority": "^0.7.0",
42
+ "cors": "^2.8.5",
43
+ "express": "^5.1.0",
44
+ "lucide-react": "^0.509.0",
45
+ "pdfjs-dist": "4.8.69",
46
+ "postcss-cli": "^11.0.1",
47
+ "react-icons": "^4.12.0",
48
+ "react-pdf": "^9.2.1",
49
+ "react-resizable-panels": "^1.0.0",
50
+ "rollup-plugin-string": "^3.0.0",
51
+ "tailwind-merge": "^2.2.0"
52
+ },
53
+ "devDependencies": {
54
+ "@babel/core": "^7.23.9",
55
+ "@babel/preset-env": "^7.23.9",
56
+ "@babel/preset-react": "^7.23.9",
57
+ "@babel/preset-typescript": "^7.23.9",
58
+ "@rollup/plugin-babel": "^6.0.4",
59
+ "@rollup/plugin-commonjs": "^25.0.7",
60
+ "@rollup/plugin-node-resolve": "^15.2.3",
61
+ "@rollup/plugin-typescript": "^11.1.6",
62
+ "@types/node": "^20.17.57",
63
+ "@types/react": "^18.2.0",
64
+ "@types/react-dom": "^18.2.0",
65
+ "autoprefixer": "^10.4.21",
66
+ "clsx": "^2.1.1",
67
+ "postcss": "^8.5.4",
68
+ "postcss-import": "^16.1.0",
69
+ "rimraf": "^5.0.5",
70
+ "rollup": "^4.9.1",
71
+ "rollup-plugin-dts": "^6.1.0",
72
+ "rollup-plugin-postcss": "^4.0.2",
73
+ "tailwindcss": "^3.4.17",
74
+ "tailwindcss-animate": "^1.0.7",
75
+ "tslib": "^2.6.2",
76
+ "typescript": "^5.3.3"
77
+ },
78
+ "keywords": [
79
+ "react",
80
+ "components",
81
+ "structura"
82
+ ],
83
+ "author": "TFW",
84
+ "license": "MIT"
85
+ }
@@ -0,0 +1,203 @@
1
+ # Structura Edit Server
2
+
3
+ Express server with SQLite storage for saving and retrieving edited documents.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Start the server
8
+
9
+ ```bash
10
+ cd /Users/__chaks__/learn/saral-next/old-lib
11
+ npm run server
12
+ ```
13
+
14
+ Server will start on `http://localhost:3002`
15
+
16
+ ### 2. Use the test app
17
+
18
+ The test app (running on port 5175) automatically connects to the server when you click the **Save** button.
19
+
20
+ 1. Load a PDF with cached JSON:
21
+ ```
22
+ http://localhost:5175/?pdf=/aex-hb-gt-101.pdf&json=/gemini-distributed-no-dupes.json
23
+ ```
24
+
25
+ 2. Double-click any text to edit it
26
+
27
+ 3. Click the **Save** button that appears in the header
28
+
29
+ 4. The edited document will be saved to SQLite database at `old-lib/server/edits.db`
30
+
31
+ ## API Endpoints
32
+
33
+ ### POST /api/save
34
+
35
+ Save edited JSON for a document.
36
+
37
+ **Request:**
38
+ ```json
39
+ {
40
+ "pdfName": "aex-hb-gt-101.pdf",
41
+ "editedJson": { ... },
42
+ "summary": "Optional edit description"
43
+ }
44
+ ```
45
+
46
+ **Response:**
47
+ ```json
48
+ {
49
+ "success": true,
50
+ "documentId": 1,
51
+ "editId": 1,
52
+ "message": "Document saved successfully"
53
+ }
54
+ ```
55
+
56
+ ### GET /api/load/:pdfName
57
+
58
+ Load the latest edit for a document.
59
+
60
+ **Example:**
61
+ ```bash
62
+ curl http://localhost:3002/api/load/aex-hb-gt-101.pdf
63
+ ```
64
+
65
+ **Response:**
66
+ ```json
67
+ {
68
+ "success": true,
69
+ "document": {
70
+ "id": 1,
71
+ "pdfName": "aex-hb-gt-101.pdf",
72
+ "originalJson": { ... },
73
+ "currentJson": { ... },
74
+ "latestEdit": {
75
+ "id": 1,
76
+ "edit_summary": "Manual edit via UI",
77
+ "created_at": "2025-11-18 10:30:00"
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### GET /api/history/:pdfName
84
+
85
+ Get full edit history for a document.
86
+
87
+ **Example:**
88
+ ```bash
89
+ curl http://localhost:3002/api/load/aex-hb-gt-101.pdf
90
+ ```
91
+
92
+ **Response:**
93
+ ```json
94
+ {
95
+ "success": true,
96
+ "document": {
97
+ "id": 1,
98
+ "pdfName": "aex-hb-gt-101.pdf"
99
+ },
100
+ "history": [
101
+ {
102
+ "id": 2,
103
+ "document_id": 1,
104
+ "edited_json": { ... },
105
+ "edit_summary": "Edit made at 11/18/2025, 10:45:00 AM",
106
+ "created_at": "2025-11-18 10:45:00"
107
+ },
108
+ {
109
+ "id": 1,
110
+ "document_id": 1,
111
+ "edited_json": { ... },
112
+ "edit_summary": "Manual edit via UI",
113
+ "created_at": "2025-11-18 10:30:00"
114
+ }
115
+ ]
116
+ }
117
+ ```
118
+
119
+ ### GET /health
120
+
121
+ Health check endpoint.
122
+
123
+ **Example:**
124
+ ```bash
125
+ curl http://localhost:3002/health
126
+ ```
127
+
128
+ **Response:**
129
+ ```json
130
+ {
131
+ "status": "ok",
132
+ "timestamp": "2025-11-18T10:30:00.000Z"
133
+ }
134
+ ```
135
+
136
+ ## Database Schema
137
+
138
+ ### documents table
139
+ - `id` - Primary key
140
+ - `pdf_name` - Name of the PDF file
141
+ - `original_json` - Original JSON from first save
142
+ - `created_at` - Timestamp
143
+
144
+ ### edits table
145
+ - `id` - Primary key
146
+ - `document_id` - Foreign key to documents
147
+ - `edited_json` - The edited JSON
148
+ - `edit_summary` - Optional description
149
+ - `created_at` - Timestamp
150
+
151
+ ## Save Behavior
152
+
153
+ **Q: Is it save-as-you-type or does a Save button show up?**
154
+
155
+ **A: Save button** - The component shows a green **Save** button in the header when you make edits. Clicking it triggers the `onSave` callback which sends the data to the server. This gives you control over when to persist changes.
156
+
157
+ ## Development
158
+
159
+ For auto-restart on code changes, install nodemon:
160
+
161
+ ```bash
162
+ npm install --save-dev nodemon
163
+ npm run server:dev
164
+ ```
165
+
166
+ ## Testing the Server
167
+
168
+ ### Test save endpoint
169
+ ```bash
170
+ curl -X POST http://localhost:3002/api/save \
171
+ -H "Content-Type: application/json" \
172
+ -d '{
173
+ "pdfName": "test.pdf",
174
+ "editedJson": {"test": "data"},
175
+ "summary": "Test edit"
176
+ }'
177
+ ```
178
+
179
+ ### Test load endpoint
180
+ ```bash
181
+ curl http://localhost:3002/api/load/test.pdf
182
+ ```
183
+
184
+ ### Test health check
185
+ ```bash
186
+ curl http://localhost:3002/health
187
+ ```
188
+
189
+ ## Database Location
190
+
191
+ The SQLite database is stored at:
192
+ ```
193
+ /Users/__chaks__/learn/saral-next/old-lib/server/edits.db
194
+ ```
195
+
196
+ You can inspect it using:
197
+ ```bash
198
+ sqlite3 server/edits.db
199
+ .tables
200
+ .schema documents
201
+ .schema edits
202
+ SELECT * FROM documents;
203
+ ```
package/server/db.js ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * SQLite database setup for storing edited JSON documents
3
+ */
4
+ const Database = require('better-sqlite3');
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+
8
+ // Ensure server directory exists
9
+ const serverDir = path.join(__dirname);
10
+ if (!fs.existsSync(serverDir)) {
11
+ fs.mkdirSync(serverDir, { recursive: true });
12
+ }
13
+
14
+ // Create/open database
15
+ const db = new Database(path.join(serverDir, 'edits.db'));
16
+
17
+ // Initialize tables
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS documents (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ pdf_name TEXT NOT NULL,
22
+ original_json TEXT NOT NULL,
23
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS edits (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ document_id INTEGER NOT NULL,
29
+ edited_json TEXT NOT NULL,
30
+ edit_summary TEXT,
31
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
32
+ FOREIGN KEY (document_id) REFERENCES documents(id)
33
+ );
34
+
35
+ CREATE INDEX IF NOT EXISTS idx_document_pdf ON documents(pdf_name);
36
+ CREATE INDEX IF NOT EXISTS idx_edits_document ON edits(document_id);
37
+ `);
38
+
39
+ /**
40
+ * Save a new document with its original JSON
41
+ */
42
+ function saveDocument(pdfName, originalJson) {
43
+ const stmt = db.prepare(`
44
+ INSERT INTO documents (pdf_name, original_json)
45
+ VALUES (?, ?)
46
+ `);
47
+
48
+ const result = stmt.run(pdfName, JSON.stringify(originalJson));
49
+ return result.lastInsertRowid;
50
+ }
51
+
52
+ /**
53
+ * Get document by PDF name
54
+ */
55
+ function getDocumentByPdfName(pdfName) {
56
+ const stmt = db.prepare(`
57
+ SELECT * FROM documents
58
+ WHERE pdf_name = ?
59
+ ORDER BY created_at DESC
60
+ LIMIT 1
61
+ `);
62
+
63
+ const doc = stmt.get(pdfName);
64
+ if (doc) {
65
+ doc.original_json = JSON.parse(doc.original_json);
66
+ }
67
+ return doc;
68
+ }
69
+
70
+ /**
71
+ * Save an edit for a document
72
+ */
73
+ function saveEdit(documentId, editedJson, summary = null) {
74
+ const stmt = db.prepare(`
75
+ INSERT INTO edits (document_id, edited_json, edit_summary)
76
+ VALUES (?, ?, ?)
77
+ `);
78
+
79
+ const result = stmt.run(documentId, JSON.stringify(editedJson), summary);
80
+ return result.lastInsertRowid;
81
+ }
82
+
83
+ /**
84
+ * Get latest edit for a document
85
+ */
86
+ function getLatestEdit(documentId) {
87
+ const stmt = db.prepare(`
88
+ SELECT * FROM edits
89
+ WHERE document_id = ?
90
+ ORDER BY created_at DESC
91
+ LIMIT 1
92
+ `);
93
+
94
+ const edit = stmt.get(documentId);
95
+ if (edit) {
96
+ edit.edited_json = JSON.parse(edit.edited_json);
97
+ }
98
+ return edit;
99
+ }
100
+
101
+ /**
102
+ * Get all edits for a document
103
+ */
104
+ function getEditHistory(documentId) {
105
+ const stmt = db.prepare(`
106
+ SELECT * FROM edits
107
+ WHERE document_id = ?
108
+ ORDER BY created_at DESC
109
+ `);
110
+
111
+ const edits = stmt.all(documentId);
112
+ return edits.map(edit => ({
113
+ ...edit,
114
+ edited_json: JSON.parse(edit.edited_json)
115
+ }));
116
+ }
117
+
118
+ /**
119
+ * Get document with latest edit
120
+ */
121
+ function getDocumentWithLatestEdit(pdfName) {
122
+ const doc = getDocumentByPdfName(pdfName);
123
+ if (!doc) return null;
124
+
125
+ const latestEdit = getLatestEdit(doc.id);
126
+
127
+ return {
128
+ ...doc,
129
+ latest_edit: latestEdit,
130
+ current_json: latestEdit ? latestEdit.edited_json : doc.original_json
131
+ };
132
+ }
133
+
134
+ module.exports = {
135
+ db,
136
+ saveDocument,
137
+ getDocumentByPdfName,
138
+ saveEdit,
139
+ getLatestEdit,
140
+ getEditHistory,
141
+ getDocumentWithLatestEdit
142
+ };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Express server for handling document edits
3
+ */
4
+ const express = require('express');
5
+ const cors = require('cors');
6
+ const {
7
+ saveDocument,
8
+ getDocumentByPdfName,
9
+ saveEdit,
10
+ getDocumentWithLatestEdit,
11
+ getEditHistory
12
+ } = require('./db');
13
+
14
+ const app = express();
15
+ const PORT = process.env.PORT || 3002;
16
+
17
+ // Middleware
18
+ app.use(cors());
19
+ app.use(express.json({ limit: '50mb' })); // Increase limit for large JSON documents
20
+
21
+ /**
22
+ * POST /api/save
23
+ * Save edited JSON for a document
24
+ *
25
+ * Request body:
26
+ * {
27
+ * pdfName: string,
28
+ * editedJson: object,
29
+ * originalJson?: object, // Optional: if first save, store as original
30
+ * summary?: string
31
+ * }
32
+ */
33
+ app.post('/api/save', (req, res) => {
34
+ try {
35
+ const { pdfName, editedJson, originalJson, summary } = req.body;
36
+
37
+ if (!pdfName || !editedJson) {
38
+ return res.status(400).json({
39
+ error: 'Missing required fields: pdfName and editedJson'
40
+ });
41
+ }
42
+
43
+ // Check if document exists
44
+ let doc = getDocumentByPdfName(pdfName);
45
+
46
+ if (!doc) {
47
+ // Create new document
48
+ // Use originalJson if provided, otherwise use editedJson as the baseline
49
+ const baselineJson = originalJson || editedJson;
50
+ const documentId = saveDocument(pdfName, baselineJson);
51
+ doc = { id: documentId };
52
+ console.log(`[Server] Created new document for ${pdfName} with ID ${documentId}`);
53
+ } else {
54
+ console.log(`[Server] Found existing document for ${pdfName} with ID ${doc.id}`);
55
+ }
56
+
57
+ // Save the edit
58
+ const editId = saveEdit(doc.id, editedJson, summary || 'Edit via UI');
59
+
60
+ console.log(`[Server] Saved edit ${editId} for document ${doc.id}`);
61
+
62
+ res.json({
63
+ success: true,
64
+ documentId: doc.id,
65
+ editId: editId,
66
+ message: 'Document saved successfully'
67
+ });
68
+ } catch (error) {
69
+ console.error('[Server] Error saving document:', error);
70
+ res.status(500).json({
71
+ error: 'Failed to save document',
72
+ details: error.message
73
+ });
74
+ }
75
+ });
76
+
77
+ /**
78
+ * GET /api/load/:pdfName
79
+ * Load the latest edit for a document
80
+ */
81
+ app.get('/api/load/:pdfName', (req, res) => {
82
+ try {
83
+ const { pdfName } = req.params;
84
+
85
+ const docWithEdit = getDocumentWithLatestEdit(pdfName);
86
+
87
+ if (!docWithEdit) {
88
+ return res.status(404).json({
89
+ error: 'Document not found'
90
+ });
91
+ }
92
+
93
+ res.json({
94
+ success: true,
95
+ document: {
96
+ id: docWithEdit.id,
97
+ pdfName: docWithEdit.pdf_name,
98
+ originalJson: docWithEdit.original_json,
99
+ currentJson: docWithEdit.current_json,
100
+ latestEdit: docWithEdit.latest_edit
101
+ }
102
+ });
103
+ } catch (error) {
104
+ console.error('[Server] Error loading document:', error);
105
+ res.status(500).json({
106
+ error: 'Failed to load document',
107
+ details: error.message
108
+ });
109
+ }
110
+ });
111
+
112
+ /**
113
+ * GET /api/history/:pdfName
114
+ * Get edit history for a document
115
+ */
116
+ app.get('/api/history/:pdfName', (req, res) => {
117
+ try {
118
+ const { pdfName } = req.params;
119
+
120
+ const doc = getDocumentByPdfName(pdfName);
121
+
122
+ if (!doc) {
123
+ return res.status(404).json({
124
+ error: 'Document not found'
125
+ });
126
+ }
127
+
128
+ const history = getEditHistory(doc.id);
129
+
130
+ res.json({
131
+ success: true,
132
+ document: {
133
+ id: doc.id,
134
+ pdfName: doc.pdf_name
135
+ },
136
+ history: history
137
+ });
138
+ } catch (error) {
139
+ console.error('[Server] Error loading history:', error);
140
+ res.status(500).json({
141
+ error: 'Failed to load history',
142
+ details: error.message
143
+ });
144
+ }
145
+ });
146
+
147
+ /**
148
+ * GET /health
149
+ * Health check endpoint
150
+ */
151
+ app.get('/health', (req, res) => {
152
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
153
+ });
154
+
155
+ // Start server
156
+ app.listen(PORT, () => {
157
+ console.log(`[Server] Structura edit server running on http://localhost:${PORT}`);
158
+ console.log(`[Server] Endpoints:`);
159
+ console.log(` POST /api/save - Save edited document`);
160
+ console.log(` GET /api/load/:pdfName - Load document with latest edit`);
161
+ console.log(` GET /api/history/:pdfName - Get edit history`);
162
+ console.log(` GET /health - Health check`);
163
+ });
164
+
165
+ module.exports = app;