Skip to content

Commit e6efe38

Browse files
authored
Merge pull request #8 from amielchristian/main
feature/add Netlify example 2d
2 parents d01aaec + ef9c868 commit e6efe38

9 files changed

Lines changed: 356 additions & 0 deletions

File tree

examples/with-netlify/icon.png

3.78 KB
Loading

examples/with-netlify/netlify.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[functions]
2+
external_node_modules = ["@stackpress/ingest"]
3+
4+
[build]
5+
command = "yarn build"
6+
environment = { NODE_VERSION = "20" }
7+
functions = "src"
8+
9+
[[redirects]]
10+
from = "/*"
11+
to = "/.netlify/functions/handler"
12+
status = 200

examples/with-netlify/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "ingest-with-fetch",
3+
"version": "1.0.0",
4+
"description": "A simple boilerplate for using Ingest with fetch API.",
5+
"private": true,
6+
"scripts": {
7+
"build": "tsc",
8+
"dev": "ts-node src/server.ts"
9+
},
10+
"dependencies": {
11+
"@netlify/functions": "^3.0.0",
12+
"@stackpress/ingest": "0.3.27"
13+
},
14+
"devDependencies": {
15+
"@types/node": "^22.13.1",
16+
"ts-node": "10.9.2",
17+
"typescript": "^4.9.5"
18+
}
19+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { server } from '@stackpress/ingest/fetch';
2+
import pages from '../routes/pages';
3+
import user from '../routes/user';
4+
import tests from '../routes/tests';
5+
import hooks from '../routes/hooks';
6+
7+
export async function handler(event: any, context: any) {
8+
const app = server();
9+
await app.bootstrap();
10+
11+
app.use(pages).use(user).use(hooks).use(tests);
12+
13+
const request = new Request(event.rawUrl, {
14+
method: event.httpMethod,
15+
headers: event.headers,
16+
});
17+
18+
const response = await app.handle(request, undefined);
19+
20+
return {
21+
statusCode: response?.status,
22+
headers: response?.headers
23+
? Object.fromEntries(response.headers.entries())
24+
: {},
25+
body: await response?.text(),
26+
isBase64Encoded: false,
27+
};
28+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { ResponseStatus } from '@stackpress/lib/dist/types';
2+
import { getStatus } from '@stackpress/lib/dist/Status';
3+
import { Exception } from '@stackpress/ingest';
4+
import { router } from '@stackpress/ingest/fetch';
5+
6+
const route = router();
7+
8+
/**
9+
* Error handlers
10+
*/
11+
route.get('/catch', function ErrorResponse(req, res) {
12+
try {
13+
throw Exception.for('Not implemented');
14+
} catch (e) {
15+
const error = e as Exception;
16+
const status = getStatus(error.code) as ResponseStatus;
17+
res.setError({
18+
code: status.code,
19+
status: status.status,
20+
error: error.message,
21+
stack: error.trace()
22+
});
23+
}
24+
});
25+
26+
/**
27+
* Error handlers
28+
*/
29+
route.get('/error', function ErrorResponse(req, res) {
30+
throw Exception.for('Not implemented');
31+
});
32+
33+
/**
34+
* 404 handler
35+
*/
36+
route.get('/**', function NotFound(req, res) {
37+
if (!res.code && !res.status && !res.sent) {
38+
//send the response
39+
res.setHTML('Not Found');
40+
}
41+
});
42+
43+
route.on('error', function Error(req, res) {
44+
const html = [ `<h1>${res.error}</h1>` ];
45+
const stack = res.stack?.map((log, i) => {
46+
const { line, char } = log;
47+
const method = log.method.replace(/</g, '&lt;').replace(/>/g, '&gt;');
48+
const file = log.file.replace(/</g, '&lt;').replace(/>/g, '&gt;');
49+
return `#${i + 1} ${method} - ${file}:${line}:${char}`;
50+
}) || [];
51+
html.push(`<pre>${stack.join('<br><br>')}</pre>`);
52+
53+
res.setHTML(html.join('<br>'));
54+
});
55+
56+
export default route;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { router } from '@stackpress/ingest/fetch';
2+
3+
const template = `
4+
<!DOCTYPE html>
5+
<html>
6+
<head>
7+
<title>Login</title>
8+
</head>
9+
<body>
10+
<h1>Login</h1>
11+
<form action="/user/login" method="POST">
12+
<label for="username">Username:</label>
13+
<input type="text" id="username" name="username" required>
14+
<label for="password">Password:</label>
15+
<input type="password" id="password" name="password" required>
16+
<button type="submit">Login</button>
17+
</form>
18+
</body>
19+
</html>
20+
`;
21+
22+
const route = router();
23+
24+
/**
25+
* Home page
26+
*/
27+
route.get('/', function HomePage(req, res) {
28+
res.setHTML('<h1>Hello, World!</h1><p>from Netlify</p>');
29+
});
30+
31+
/**
32+
* Login page
33+
*/
34+
route.get('/login', function Login(req, res) {
35+
//send the response
36+
res.setHTML(template.trim());
37+
});
38+
39+
export default route;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
import { router } from '@stackpress/ingest/fetch';
5+
6+
const template = `
7+
<!DOCTYPE html>
8+
<html>
9+
<head>
10+
<title>SSE</title>
11+
</head>
12+
<body>
13+
<ul></ul>
14+
<script>
15+
const ul = document.querySelector('ul');
16+
const evtSource = new EventSource('/__sse__');
17+
evtSource.onmessage = (event) => {
18+
const li = document.createElement('li');
19+
li.textContent = event.data;
20+
ul.appendChild(li);
21+
};
22+
</script>
23+
</body>
24+
</html>
25+
`;
26+
27+
const route = router();
28+
29+
/**
30+
* Redirect test
31+
*/
32+
route.get('/redirect', function Redirect(req, res) {
33+
res.redirect('/user');
34+
});
35+
36+
/**
37+
* Static file test
38+
*/
39+
route.get('/icon.png', function Icon(req, res) {
40+
if (res.code || res.status || res.body) return;
41+
const file = path.resolve(process.cwd(), 'icon.png');
42+
if (fs.existsSync(file)) {
43+
res.setBody('image/png', fs.createReadStream(file));
44+
}
45+
});
46+
47+
/**
48+
* Stream template for SSE test
49+
*/
50+
route.get('/stream', function Stream(req, res) {
51+
//send the response
52+
res.setHTML(template.trim());
53+
});
54+
55+
/**
56+
* SSE test
57+
*/
58+
route.get('/__sse__', function SSE(req, res) {
59+
res.headers
60+
.set('Cache-Control', 'no-cache')
61+
.set('Content-Encoding', 'none')
62+
.set('Connection', 'keep-alive')
63+
.set('Access-Control-Allow-Origin', '*');
64+
65+
let timerId: any;
66+
const msg = new TextEncoder().encode('data: hello\r\n\r\n');
67+
res.setBody('text/event-stream', new ReadableStream({
68+
start(controller) {
69+
timerId = setInterval(() => {
70+
controller.enqueue(msg);
71+
}, 2500);
72+
},
73+
cancel() {
74+
if (typeof timerId === 'number') {
75+
clearInterval(timerId);
76+
}
77+
},
78+
}));
79+
});
80+
81+
export default route;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { router } from '@stackpress/ingest/fetch';
2+
3+
const route = router();
4+
5+
let id = 0;
6+
7+
/**
8+
* Example user API search
9+
*/
10+
route.get('/user', function UserSearch(req, res) {
11+
//get filters
12+
//const filters = req.query.get<Record<string, unknown>>('filter');
13+
//maybe get from database?
14+
const results = [
15+
{
16+
id: 1,
17+
name: 'John Doe',
18+
age: 21,
19+
created: new Date().toISOString()
20+
},
21+
{
22+
id: 2,
23+
name: 'Jane Doe',
24+
age: 30,
25+
created: new Date().toISOString()
26+
}
27+
];
28+
//send the response
29+
res.setRows(results, 100);
30+
});
31+
32+
/**
33+
* Example user API create (POST)
34+
* Need to use Postman to see this...
35+
*/
36+
route.post('/user', function UserCreate(req, res) {
37+
//get form body
38+
const form = req.data();
39+
//maybe insert into database?
40+
const results = { ...form, id: ++id, created: new Date().toISOString() };
41+
//send the response
42+
res.setResults(results);
43+
});
44+
45+
/**
46+
* Example user API detail
47+
*/
48+
route.get('/user/:id', function UserDetail(req, res) {
49+
//get params
50+
const id = parseInt(req.data('id') || '');
51+
if (!id) {
52+
res.setError('ID is required');
53+
return;
54+
}
55+
//maybe get from database?
56+
const results = {
57+
id: id,
58+
name: 'John Doe',
59+
age: 21,
60+
created: new Date().toISOString()
61+
};
62+
//send the response
63+
res.setResults(results);
64+
});
65+
route.put('/user/:id', function UserUpdate(req, res) {
66+
//get params
67+
const id = parseInt(req.data('id') || '');
68+
if (!id) {
69+
res.setError('ID is required');
70+
return;
71+
}
72+
//get form body
73+
const form = req.post();
74+
//maybe insert into database?
75+
const results = { ...form, id, created: new Date().toISOString() };
76+
//send the response
77+
res.setResults(results);
78+
});
79+
80+
/**
81+
* Example user API delete (DELETE)
82+
* Need to use Postman to see this...
83+
*/
84+
route.delete('/user/:id', function UserRemove(req, res) {
85+
//get params
86+
const id = parseInt(req.data('id') || '');
87+
if (!id) {
88+
res.setError('ID is required');
89+
return;
90+
}
91+
//maybe get from database?
92+
const results = {
93+
id: 1,
94+
name: 'John Doe',
95+
age: 21,
96+
created: new Date().toISOString()
97+
};
98+
//send the response
99+
res.setResults(results);
100+
});
101+
102+
export default route;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"compilerOptions": {
3+
"declaration": true,
4+
"esModuleInterop": true,
5+
"lib": ["es2021", "es7", "es6", "dom"],
6+
"module": "commonjs",
7+
"noUnusedLocals": true,
8+
"outDir": "./dist/",
9+
"preserveConstEnums": true,
10+
"resolveJsonModule": true,
11+
"removeComments": true,
12+
"sourceMap": false,
13+
"strict": true,
14+
"target": "es6",
15+
"skipLibCheck": true
16+
},
17+
"include": ["src/**/*.ts", "src/server.mts"],
18+
"exclude": ["dist", "node_modules"]
19+
}

0 commit comments

Comments
 (0)