@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.
- package/PRODUCTION_ARCHITECTURE.md +511 -0
- package/README.md +379 -0
- package/SAVE_FUNCTIONALITY_COMPLETE.md +448 -0
- package/dist/cjs/EditableContent.js +150 -0
- package/dist/cjs/HtmlViewer.js +587 -0
- package/dist/cjs/PdfComponents.js +16 -0
- package/dist/cjs/PdfDocumentViewer.js +281 -0
- package/dist/cjs/Structura.js +806 -0
- package/dist/cjs/Table.js +164 -0
- package/dist/cjs/TableCell.js +115 -0
- package/dist/cjs/accuracyMetrics.js +39 -0
- package/dist/cjs/helpers/preprocessData.js +143 -0
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/lib/polyfills.js +15 -0
- package/dist/cjs/lib/utils.js +10 -0
- package/dist/cjs/node_modules/react-icons/fa/index.esm.js +14 -0
- package/dist/cjs/node_modules/react-icons/lib/esm/iconBase.js +69 -0
- package/dist/cjs/node_modules/react-icons/lib/esm/iconContext.js +15 -0
- package/dist/cjs/polyfills.js +19 -0
- package/dist/cjs/route.js +102 -0
- package/dist/cjs/styles.css +7 -0
- package/dist/cjs/styles.css.map +1 -0
- package/dist/cjs/ui/badge.js +34 -0
- package/dist/cjs/ui/button.js +71 -0
- package/dist/cjs/ui/card.js +86 -0
- package/dist/cjs/ui/progress.js +45 -0
- package/dist/cjs/ui/scroll-area.js +62 -0
- package/dist/cjs/ui/tabs.js +60 -0
- package/dist/cjs/worker.js +36 -0
- package/dist/esm/EditableContent.js +161 -0
- package/dist/esm/HtmlViewer.js +640 -0
- package/dist/esm/PdfComponents.js +21 -0
- package/dist/esm/PdfDocumentViewer.js +294 -0
- package/dist/esm/Structura.js +951 -0
- package/dist/esm/Table.js +182 -0
- package/dist/esm/TableCell.js +122 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +305 -0
- package/dist/esm/accuracyMetrics.js +41 -0
- package/dist/esm/helpers/preprocessData.js +152 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/lib/polyfills.js +13 -0
- package/dist/esm/lib/utils.js +8 -0
- package/dist/esm/node_modules/react-icons/fa/index.esm.js +11 -0
- package/dist/esm/node_modules/react-icons/lib/esm/iconBase.js +66 -0
- package/dist/esm/node_modules/react-icons/lib/esm/iconContext.js +12 -0
- package/dist/esm/polyfills.js +17 -0
- package/dist/esm/route.js +154 -0
- package/dist/esm/styles.css +7 -0
- package/dist/esm/styles.css.map +1 -0
- package/dist/esm/types/EditableContent.d.ts +9 -0
- package/dist/esm/types/HtmlViewer.d.ts +10 -0
- package/dist/esm/types/PdfComponents.d.ts +35 -0
- package/dist/esm/types/PdfDocumentViewer.d.ts +22 -0
- package/dist/esm/types/Structura.d.ts +11 -0
- package/dist/esm/types/Table.d.ts +12 -0
- package/dist/esm/types/TableCell.d.ts +13 -0
- package/dist/esm/types/accuracy.d.ts +23 -0
- package/dist/esm/types/accuracyMetrics.d.ts +5 -0
- package/dist/esm/types/helpers/flattenJSON.d.ts +1 -0
- package/dist/esm/types/helpers/hardMerging.d.ts +2 -0
- package/dist/esm/types/helpers/index.d.ts +6 -0
- package/dist/esm/types/helpers/jsonToHtml.d.ts +40 -0
- package/dist/esm/types/helpers/preprocessData.d.ts +3 -0
- package/dist/esm/types/helpers/removeMetadata.d.ts +1 -0
- package/dist/esm/types/helpers/tableProcessor.d.ts +1 -0
- package/dist/esm/types/index.d.ts +3 -0
- package/dist/esm/types/lib/polyfills.d.ts +1 -0
- package/dist/esm/types/lib/utils.d.ts +2 -0
- package/dist/esm/types/polyfills.d.ts +1 -0
- package/dist/esm/types/route.d.ts +45 -0
- package/dist/esm/types/test-app/src/App.d.ts +4 -0
- package/dist/esm/types/test-app/src/main.d.ts +1 -0
- package/dist/esm/types/test-app/vite.config.d.ts +2 -0
- package/dist/esm/types/types.d.ts +23 -0
- package/dist/esm/types/ui/alert.d.ts +8 -0
- package/dist/esm/types/ui/badge.d.ts +9 -0
- package/dist/esm/types/ui/button.d.ts +11 -0
- package/dist/esm/types/ui/card.d.ts +8 -0
- package/dist/esm/types/ui/progress.d.ts +6 -0
- package/dist/esm/types/ui/scroll-area.d.ts +5 -0
- package/dist/esm/types/ui/skeleton.d.ts +2 -0
- package/dist/esm/types/ui/tabs.d.ts +7 -0
- package/dist/esm/types/worker.d.ts +1 -0
- package/dist/esm/ui/badge.js +31 -0
- package/dist/esm/ui/button.js +50 -0
- package/dist/esm/ui/card.js +67 -0
- package/dist/esm/ui/progress.js +26 -0
- package/dist/esm/ui/scroll-area.js +45 -0
- package/dist/esm/ui/tabs.js +39 -0
- package/dist/esm/worker.js +50 -0
- package/dist/index.d.ts +38 -0
- package/package.json +85 -0
- package/server/README.md +203 -0
- package/server/db.js +142 -0
- package/server/server.js +165 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|
package/server/README.md
ADDED
|
@@ -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
|
+
};
|
package/server/server.js
ADDED
|
@@ -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;
|