json-rest-api 0.1.1 → 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/.claude/settings.local.json +16 -0
- package/LICENSE +674 -20
- package/README.md +728 -120
- package/RELATIONSHIPS.md +1013 -0
- package/TODO_rest-api-plugin-extras.js +142 -0
- package/connectors_ideas/express-plugin.js +120 -0
- package/connectors_ideas/fastify.js +219 -0
- package/connectors_ideas/http-plugin.js +138 -0
- package/connectors_ideas/koa-plugin.js +113 -0
- package/docs/fileUploads.md +722 -0
- package/docs/mini-guides/file-uploads-complete.md +159 -0
- package/examples/.gitkeep +0 -0
- package/examples/express-example.js +262 -0
- package/examples/express-plugin.js +366 -0
- package/examples/file-upload-example.js +205 -0
- package/examples/filename-strategies.js +140 -0
- package/examples/knex-basic.js +66 -0
- package/index.js +28 -202
- package/lib/payload-validators.js +685 -0
- package/lib/rest-api-errors.js +58 -0
- package/package.json +37 -20
- package/payloadValdators.js +686 -0
- package/plugins/core/connectors/express-plugin.js +449 -0
- package/plugins/core/connectors/http-plugin.js +450 -0
- package/plugins/core/file-handling-plugin.js +338 -0
- package/plugins/core/lib/busboy-detector.js +167 -0
- package/plugins/core/lib/formidable-detector.js +130 -0
- package/plugins/core/rest-api-knex-plugin.js +200 -0
- package/plugins/core/rest-api-plugin.js +1419 -0
- package/plugins/storage/local-storage.js +218 -0
- package/plugins/storage/s3-storage.js +125 -0
- package/test-examples/package-lock.json +906 -0
- package/test-examples/package.json +15 -0
- package/test-examples/test-basic-api.js +11 -0
- package/test-examples/test-books-resource.js +116 -0
- package/test-examples/test-complete-file-upload.js +140 -0
- package/test-examples/test-debug.js +28 -0
- package/test-examples/test-express-plugin.js +177 -0
- package/test-examples/test-express-simple.js +28 -0
- package/test-examples/test-express-with-import.js +71 -0
- package/test-examples/test-file-upload.js +132 -0
- package/test-examples/test-http-plugin.js +159 -0
- package/test-examples/test-programmatic-api.js +188 -0
- package/test-examples/test-rest-api-plugin.js +13 -0
- package/.jshintrc +0 -48
- package/.npmignore +0 -15
- package/examples/client.js +0 -18
- package/examples/client2.js +0 -27
- package/examples/server.js +0 -23
- package/test.js +0 -101
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-examples",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Testing jsonrestapi examples",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"busboy": "^1.6.0",
|
|
11
|
+
"express": "^5.1.0",
|
|
12
|
+
"hooked-api": "^1.0.2",
|
|
13
|
+
"jsonrestapi": "file:.."
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
|
|
3
|
+
// Create a new API instance
|
|
4
|
+
const api = new Api({
|
|
5
|
+
name: 'my-library-api',
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
console.log('✓ Basic API creation works!');
|
|
10
|
+
console.log(' API name:', api.options.name);
|
|
11
|
+
console.log(' API version:', api.options.version);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin } from 'json-rest-api';
|
|
3
|
+
|
|
4
|
+
const api = new Api({
|
|
5
|
+
name: 'my-library-api',
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Use the REST API plugin
|
|
10
|
+
api.use(RestApiPlugin);
|
|
11
|
+
|
|
12
|
+
// Define a books resource
|
|
13
|
+
api.addResource('books', {
|
|
14
|
+
schema: {
|
|
15
|
+
title: { type: 'string', required: true },
|
|
16
|
+
author: { type: 'string', required: true },
|
|
17
|
+
year: { type: 'number' },
|
|
18
|
+
isbn: { type: 'string' }
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Add simple data helpers (in real apps, these would connect to a database)
|
|
23
|
+
api.customize({
|
|
24
|
+
vars: {
|
|
25
|
+
helpers: {
|
|
26
|
+
dataQuery: async ({ scopeName, queryParams }) => {
|
|
27
|
+
// For now, just return some fake books
|
|
28
|
+
const fakeBooks = [
|
|
29
|
+
{ id: '1', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
|
|
30
|
+
{ id: '2', title: '1984', author: 'George Orwell', year: 1949 },
|
|
31
|
+
{ id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
data: fakeBooks.map(book => ({
|
|
36
|
+
type: 'books',
|
|
37
|
+
id: book.id,
|
|
38
|
+
attributes: {
|
|
39
|
+
title: book.title,
|
|
40
|
+
author: book.author,
|
|
41
|
+
year: book.year
|
|
42
|
+
}
|
|
43
|
+
}))
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
dataGet: async ({ scopeName, id }) => {
|
|
48
|
+
// Return a single fake book
|
|
49
|
+
const book = { id, title: 'Example Book', author: 'Example Author', year: 2024 };
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
data: {
|
|
53
|
+
type: 'books',
|
|
54
|
+
id: book.id,
|
|
55
|
+
attributes: {
|
|
56
|
+
title: book.title,
|
|
57
|
+
author: book.author,
|
|
58
|
+
year: book.year
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
dataPost: async ({ scopeName, inputRecord }) => {
|
|
65
|
+
// Pretend to create a book and return it
|
|
66
|
+
const newBook = {
|
|
67
|
+
id: String(Date.now()), // Simple ID generation
|
|
68
|
+
...inputRecord.data.attributes
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
data: {
|
|
73
|
+
type: 'books',
|
|
74
|
+
id: newBook.id,
|
|
75
|
+
attributes: {
|
|
76
|
+
title: newBook.title,
|
|
77
|
+
author: newBook.author,
|
|
78
|
+
year: newBook.year
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
dataPut: async ({ scopeName, id, inputRecord }) => {
|
|
85
|
+
// Pretend to replace a book
|
|
86
|
+
return {
|
|
87
|
+
data: {
|
|
88
|
+
type: 'books',
|
|
89
|
+
id: id,
|
|
90
|
+
attributes: inputRecord.data.attributes
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
dataPatch: async ({ scopeName, id, inputRecord }) => {
|
|
96
|
+
// Pretend to update a book
|
|
97
|
+
return {
|
|
98
|
+
data: {
|
|
99
|
+
type: 'books',
|
|
100
|
+
id: id,
|
|
101
|
+
attributes: inputRecord.data.attributes
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
dataDelete: async ({ scopeName, id }) => {
|
|
107
|
+
// Just return success
|
|
108
|
+
return { success: true };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log('✓ Books resource created with all data helpers!');
|
|
115
|
+
console.log(' Resource:', api.resources.books ? 'books resource exists' : 'ERROR: books resource missing');
|
|
116
|
+
console.log(' Available methods:', api.resources.books ? Object.keys(api.resources.books).filter(k => typeof api.resources.books[k] === 'function').join(', ') : 'none');
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin, FileHandlingPlugin, ExpressPlugin } from 'json-rest-api';
|
|
3
|
+
import { LocalStorage } from 'json-rest-api';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
|
|
6
|
+
// Create API
|
|
7
|
+
const api = new Api({
|
|
8
|
+
name: 'my-library-api',
|
|
9
|
+
version: '1.0.0'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Create storage for different file types
|
|
13
|
+
const coverStorage = new LocalStorage({
|
|
14
|
+
directory: './uploads/covers',
|
|
15
|
+
baseUrl: 'http://localhost:3000/uploads/covers',
|
|
16
|
+
nameStrategy: 'hash',
|
|
17
|
+
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif']
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const pdfStorage = new LocalStorage({
|
|
21
|
+
directory: './uploads/pdfs',
|
|
22
|
+
baseUrl: 'http://localhost:3000/uploads/pdfs',
|
|
23
|
+
nameStrategy: 'timestamp'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Use plugins
|
|
27
|
+
api.use(RestApiPlugin);
|
|
28
|
+
api.use(FileHandlingPlugin);
|
|
29
|
+
api.use(ExpressPlugin); // File parser configuration shown in connector plugins section
|
|
30
|
+
|
|
31
|
+
// Define schema with multiple file fields
|
|
32
|
+
api.addResource('books', {
|
|
33
|
+
schema: {
|
|
34
|
+
title: { type: 'string', required: true },
|
|
35
|
+
author: { type: 'string', required: true },
|
|
36
|
+
year: { type: 'number' },
|
|
37
|
+
cover: {
|
|
38
|
+
type: 'file',
|
|
39
|
+
storage: coverStorage,
|
|
40
|
+
accepts: ['image/*'],
|
|
41
|
+
maxSize: '5mb'
|
|
42
|
+
},
|
|
43
|
+
sample: {
|
|
44
|
+
type: 'file',
|
|
45
|
+
storage: pdfStorage,
|
|
46
|
+
accepts: ['application/pdf'],
|
|
47
|
+
maxSize: '10mb',
|
|
48
|
+
required: false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Simple data helpers
|
|
54
|
+
api.customize({
|
|
55
|
+
vars: {
|
|
56
|
+
helpers: {
|
|
57
|
+
dataPost: async ({ scopeName, inputRecord }) => {
|
|
58
|
+
const newBook = {
|
|
59
|
+
id: String(Date.now()),
|
|
60
|
+
...inputRecord.data.attributes
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
console.log('Created book:', newBook);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
data: {
|
|
67
|
+
type: 'books',
|
|
68
|
+
id: newBook.id,
|
|
69
|
+
attributes: newBook
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
dataQuery: async () => {
|
|
75
|
+
return { data: [] };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Wait for async plugin initialization
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
83
|
+
|
|
84
|
+
// Express setup
|
|
85
|
+
const app = express();
|
|
86
|
+
app.use('/uploads', express.static('./uploads'));
|
|
87
|
+
|
|
88
|
+
// Test form
|
|
89
|
+
app.get('/', (req, res) => {
|
|
90
|
+
res.send(`
|
|
91
|
+
<form action="/api/books" method="POST" enctype="multipart/form-data">
|
|
92
|
+
<h2>Add a Book</h2>
|
|
93
|
+
<p>Title: <input name="title" required></p>
|
|
94
|
+
<p>Author: <input name="author" required></p>
|
|
95
|
+
<p>Year: <input name="year" type="number"></p>
|
|
96
|
+
<p>Cover: <input name="cover" type="file" accept="image/*"></p>
|
|
97
|
+
<p>Sample PDF: <input name="sample" type="file" accept=".pdf"></p>
|
|
98
|
+
<button type="submit">Add Book</button>
|
|
99
|
+
</form>
|
|
100
|
+
`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
app.use(api.http.express.router);
|
|
104
|
+
|
|
105
|
+
const server = app.listen(3000, () => {
|
|
106
|
+
console.log('Library API running at http://localhost:3000');
|
|
107
|
+
console.log('Test form at http://localhost:3000');
|
|
108
|
+
console.log('API endpoints at http://localhost:3000/api/books');
|
|
109
|
+
|
|
110
|
+
// Test with a simple POST
|
|
111
|
+
console.log('\nTesting JSON POST (without files)...');
|
|
112
|
+
fetch('http://localhost:3000/api/books', {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json'
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
data: {
|
|
119
|
+
type: 'books',
|
|
120
|
+
attributes: {
|
|
121
|
+
title: 'Test Book',
|
|
122
|
+
author: 'Test Author',
|
|
123
|
+
year: 2024
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
.then(res => res.json())
|
|
129
|
+
.then(result => {
|
|
130
|
+
console.log('JSON POST result:', JSON.stringify(result, null, 2));
|
|
131
|
+
|
|
132
|
+
// Keep server running for manual testing
|
|
133
|
+
console.log('\nServer is running. Visit http://localhost:3000 to test file uploads.');
|
|
134
|
+
console.log('Press Ctrl+C to stop.');
|
|
135
|
+
})
|
|
136
|
+
.catch(err => {
|
|
137
|
+
console.error('Test failed:', err);
|
|
138
|
+
server.close();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin } from 'json-rest-api';
|
|
3
|
+
|
|
4
|
+
// Create a minimal test plugin to check what parameters are available
|
|
5
|
+
const TestPlugin = {
|
|
6
|
+
name: 'test-plugin',
|
|
7
|
+
install(context) {
|
|
8
|
+
console.log('Plugin install context keys:', Object.keys(context));
|
|
9
|
+
console.log('api type:', typeof context.api);
|
|
10
|
+
console.log('api value:', context.api);
|
|
11
|
+
|
|
12
|
+
if (context.api) {
|
|
13
|
+
console.log('api properties:', Object.keys(context.api));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const api = new Api({
|
|
19
|
+
name: 'my-library-api',
|
|
20
|
+
version: '1.0.0'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// First test with our debug plugin
|
|
24
|
+
console.log('Testing with debug plugin...');
|
|
25
|
+
api.use(TestPlugin);
|
|
26
|
+
|
|
27
|
+
console.log('\nNow testing REST API plugin...');
|
|
28
|
+
api.use(RestApiPlugin);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin, FileHandlingPlugin, ExpressPlugin } from 'json-rest-api';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
|
|
5
|
+
const api = new Api({
|
|
6
|
+
name: 'my-library-api',
|
|
7
|
+
version: '1.0.0'
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Add plugins (ORDER MATTERS!)
|
|
11
|
+
api.use(RestApiPlugin);
|
|
12
|
+
api.use(FileHandlingPlugin); // Must come after RestApiPlugin
|
|
13
|
+
api.use(ExpressPlugin, { // Must come after FileHandlingPlugin
|
|
14
|
+
basePath: '/api', // Optional, defaults to '/api'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Add books resource
|
|
18
|
+
api.addResource('books', {
|
|
19
|
+
schema: {
|
|
20
|
+
title: { type: 'string', required: true },
|
|
21
|
+
author: { type: 'string', required: true },
|
|
22
|
+
year: { type: 'number' },
|
|
23
|
+
isbn: { type: 'string' }
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Add simple data helpers
|
|
28
|
+
api.customize({
|
|
29
|
+
vars: {
|
|
30
|
+
helpers: {
|
|
31
|
+
dataQuery: async ({ scopeName, queryParams }) => {
|
|
32
|
+
const fakeBooks = [
|
|
33
|
+
{ id: '1', title: 'The Great Gatsby', author: 'F. Scott Fitzgerald', year: 1925 },
|
|
34
|
+
{ id: '2', title: '1984', author: 'George Orwell', year: 1949 },
|
|
35
|
+
{ id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
data: fakeBooks.map(book => ({
|
|
40
|
+
type: 'books',
|
|
41
|
+
id: book.id,
|
|
42
|
+
attributes: {
|
|
43
|
+
title: book.title,
|
|
44
|
+
author: book.author,
|
|
45
|
+
year: book.year
|
|
46
|
+
}
|
|
47
|
+
}))
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
dataGet: async ({ scopeName, id }) => {
|
|
52
|
+
const book = { id, title: 'Example Book', author: 'Example Author', year: 2024 };
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
data: {
|
|
56
|
+
type: 'books',
|
|
57
|
+
id: book.id,
|
|
58
|
+
attributes: {
|
|
59
|
+
title: book.title,
|
|
60
|
+
author: book.author,
|
|
61
|
+
year: book.year
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
dataPost: async ({ scopeName, inputRecord }) => {
|
|
68
|
+
const newBook = {
|
|
69
|
+
id: String(Date.now()),
|
|
70
|
+
...inputRecord.data.attributes
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
data: {
|
|
75
|
+
type: 'books',
|
|
76
|
+
id: newBook.id,
|
|
77
|
+
attributes: {
|
|
78
|
+
title: newBook.title,
|
|
79
|
+
author: newBook.author,
|
|
80
|
+
year: newBook.year
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
dataPut: async ({ scopeName, id, inputRecord }) => {
|
|
87
|
+
return {
|
|
88
|
+
data: {
|
|
89
|
+
type: 'books',
|
|
90
|
+
id: id,
|
|
91
|
+
attributes: inputRecord.data.attributes
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
dataPatch: async ({ scopeName, id, inputRecord }) => {
|
|
97
|
+
return {
|
|
98
|
+
data: {
|
|
99
|
+
type: 'books',
|
|
100
|
+
id: id,
|
|
101
|
+
attributes: inputRecord.data.attributes
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
dataDelete: async ({ scopeName, id }) => {
|
|
107
|
+
return { success: true };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Wait a bit for async plugin installation to complete
|
|
114
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
115
|
+
|
|
116
|
+
// Create Express app
|
|
117
|
+
const app = express();
|
|
118
|
+
|
|
119
|
+
// Debug: Check what's available
|
|
120
|
+
console.log('api.http exists?', !!api.http);
|
|
121
|
+
console.log('api.http.express exists?', !!api.http?.express);
|
|
122
|
+
console.log('api.http.express.router exists?', !!api.http?.express?.router);
|
|
123
|
+
|
|
124
|
+
// Mount the API routes - both approaches work:
|
|
125
|
+
// Approach 1: Direct router usage (standard Express pattern)
|
|
126
|
+
app.use(api.http.express.router);
|
|
127
|
+
|
|
128
|
+
// Start the server
|
|
129
|
+
const server = app.listen(3000, () => {
|
|
130
|
+
console.log('✓ Express server started successfully!');
|
|
131
|
+
console.log(' API running at http://localhost:3000/api');
|
|
132
|
+
console.log('\nTesting endpoints with curl...\n');
|
|
133
|
+
|
|
134
|
+
// Close server after tests
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
console.log('\nClosing server...');
|
|
137
|
+
server.close();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}, 2000);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Test with curl commands
|
|
143
|
+
import { exec } from 'child_process';
|
|
144
|
+
import { promisify } from 'util';
|
|
145
|
+
const execAsync = promisify(exec);
|
|
146
|
+
|
|
147
|
+
setTimeout(async () => {
|
|
148
|
+
try {
|
|
149
|
+
// Test GET all books
|
|
150
|
+
console.log('1. Testing GET /api/books:');
|
|
151
|
+
const { stdout: getAll } = await execAsync('curl -s http://localhost:3000/api/books');
|
|
152
|
+
console.log('✓ Response:', getAll.substring(0, 100) + '...');
|
|
153
|
+
|
|
154
|
+
// Test GET single book
|
|
155
|
+
console.log('\n2. Testing GET /api/books/1:');
|
|
156
|
+
const { stdout: getOne } = await execAsync('curl -s http://localhost:3000/api/books/1');
|
|
157
|
+
console.log('✓ Response:', getOne.substring(0, 100) + '...');
|
|
158
|
+
|
|
159
|
+
// Test POST
|
|
160
|
+
console.log('\n3. Testing POST /api/books:');
|
|
161
|
+
const postData = JSON.stringify({
|
|
162
|
+
data: {
|
|
163
|
+
type: 'books',
|
|
164
|
+
attributes: {
|
|
165
|
+
title: 'New Book',
|
|
166
|
+
author: 'New Author',
|
|
167
|
+
year: 2024
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const { stdout: postRes } = await execAsync(`curl -s -X POST http://localhost:3000/api/books -H "Content-Type: application/json" -d '${postData}'`);
|
|
172
|
+
console.log('✓ Response:', postRes.substring(0, 100) + '...');
|
|
173
|
+
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('Test error:', error.message);
|
|
176
|
+
}
|
|
177
|
+
}, 500);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Api } from 'hooked-api';
|
|
2
|
+
import { RestApiPlugin, FileHandlingPlugin, ExpressPlugin } from 'json-rest-api';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
|
|
5
|
+
const api = new Api({
|
|
6
|
+
name: 'my-library-api',
|
|
7
|
+
version: '1.0.0'
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Add plugins
|
|
12
|
+
api.use(RestApiPlugin);
|
|
13
|
+
api.use(FileHandlingPlugin);
|
|
14
|
+
api.use(ExpressPlugin);
|
|
15
|
+
|
|
16
|
+
console.log('All plugins loaded successfully');
|
|
17
|
+
|
|
18
|
+
// Wait for async installation
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
20
|
+
|
|
21
|
+
console.log('Checking API methods after wait:');
|
|
22
|
+
console.log('- getExpressRouter:', typeof api.getExpressRouter);
|
|
23
|
+
console.log('- mountExpress:', typeof api.mountExpress);
|
|
24
|
+
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Plugin installation error:', error);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Import express first to ensure it's available
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import { Api } from 'hooked-api';
|
|
4
|
+
import { RestApiPlugin, FileHandlingPlugin, ExpressPlugin } from 'json-rest-api';
|
|
5
|
+
|
|
6
|
+
// Make express available globally for the plugin
|
|
7
|
+
global.express = express;
|
|
8
|
+
|
|
9
|
+
const api = new Api({
|
|
10
|
+
name: 'my-library-api',
|
|
11
|
+
version: '1.0.0'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Add plugins
|
|
15
|
+
api.use(RestApiPlugin);
|
|
16
|
+
api.use(FileHandlingPlugin);
|
|
17
|
+
|
|
18
|
+
// Temporarily patch the Express plugin to use our express
|
|
19
|
+
const originalInstall = ExpressPlugin.install;
|
|
20
|
+
ExpressPlugin.install = async function(context) {
|
|
21
|
+
// Override the express import in the plugin
|
|
22
|
+
const patchedContext = {
|
|
23
|
+
...context,
|
|
24
|
+
vars: { ...context.vars, _express: express }
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Call original install
|
|
28
|
+
await originalInstall.call(this, patchedContext);
|
|
29
|
+
|
|
30
|
+
// If express wasn't loaded, use our version
|
|
31
|
+
if (!patchedContext.vars.express.router) {
|
|
32
|
+
const router = express.Router({ mergeParams: true });
|
|
33
|
+
patchedContext.vars.express.router = router;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
api.use(ExpressPlugin);
|
|
38
|
+
|
|
39
|
+
// Add a simple test resource
|
|
40
|
+
api.addResource('test', {
|
|
41
|
+
schema: {
|
|
42
|
+
name: { type: 'string', required: true }
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Add test data helper
|
|
47
|
+
api.customize({
|
|
48
|
+
vars: {
|
|
49
|
+
helpers: {
|
|
50
|
+
dataQuery: async () => ({ data: [] })
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log('✓ All plugins loaded successfully!');
|
|
56
|
+
|
|
57
|
+
// Create Express app and test
|
|
58
|
+
const app = express();
|
|
59
|
+
|
|
60
|
+
// Check if methods are available
|
|
61
|
+
console.log('API methods available:', Object.keys(api).filter(k => typeof api[k] === 'function'));
|
|
62
|
+
|
|
63
|
+
// Try the simpler approach - using the Express router directly
|
|
64
|
+
if (api.getExpressRouter) {
|
|
65
|
+
app.use('/api', api.getExpressRouter());
|
|
66
|
+
console.log('✓ Express router mounted successfully!');
|
|
67
|
+
} else {
|
|
68
|
+
console.log('✗ getExpressRouter method not found');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.exit(0);
|