@awesomeness-js/server 1.0.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 (51) hide show
  1. package/.editorconfig +4 -0
  2. package/.gitattributes +2 -0
  3. package/.jshintrc +3 -0
  4. package/.vscode/settings.json +17 -0
  5. package/CLA.md +9 -0
  6. package/CONTRIBUTING.md +18 -0
  7. package/LICENSE +13 -0
  8. package/NOTICE +15 -0
  9. package/README.md +15 -0
  10. package/SECURITY.md +7 -0
  11. package/config.js +29 -0
  12. package/eslint.config.js +101 -0
  13. package/example-.awesomeness/applicationMap.js +4 -0
  14. package/example-.awesomeness/beforeRouteMiddleware.js +15 -0
  15. package/example-.awesomeness/checkSession.js +15 -0
  16. package/example-.awesomeness/config.js +40 -0
  17. package/example-.awesomeness/hostMap.js +44 -0
  18. package/example-.awesomeness/initDB.js +65 -0
  19. package/example-.awesomeness/setupDB/applications.js +47 -0
  20. package/example-.awesomeness/setupDB/users.js +65 -0
  21. package/example-.awesomeness/setupDB/websites.js +49 -0
  22. package/example-.awesomeness/specialRoutes.js +7 -0
  23. package/example-.awesomeness/wsHandler.js +13 -0
  24. package/index.js +22 -0
  25. package/package.json +34 -0
  26. package/server/applicationMap.js +33 -0
  27. package/server/awesomenessNormalizeRequest.js +131 -0
  28. package/server/brotliJsonResponse.js +28 -0
  29. package/server/checkAccess.js +34 -0
  30. package/server/componentDependencies.js +301 -0
  31. package/server/errors.js +11 -0
  32. package/server/fetchPage.js +269 -0
  33. package/server/getMD.js +22 -0
  34. package/server/koa/attachAwesomenessRequest.js +24 -0
  35. package/server/koa/cors.js +22 -0
  36. package/server/koa/errorHandler.js +32 -0
  37. package/server/koa/finalFormat.js +34 -0
  38. package/server/koa/jsonBodyParser.js +172 -0
  39. package/server/koa/routeRequest.js +288 -0
  40. package/server/koa/serverUp.js +7 -0
  41. package/server/koa/staticFiles.js +97 -0
  42. package/server/koa/timeout.js +42 -0
  43. package/server/pageInfo.js +121 -0
  44. package/server/reRoute.js +54 -0
  45. package/server/resolveRealCasePath.js +56 -0
  46. package/server/specialPaths.js +107 -0
  47. package/server/validateRequest.js +127 -0
  48. package/server/ws/handlers.js +67 -0
  49. package/server/ws/index.js +50 -0
  50. package/start.js +122 -0
  51. package/vitest.config.js +15 -0
@@ -0,0 +1,288 @@
1
+ import fetchPage from '../fetchPage.js';
2
+ import pageInfo from '../pageInfo.js';
3
+ import validateRequest from '../validateRequest.js';
4
+ import { staticFiles } from './staticFiles.js';
5
+ import finalFormat from './finalFormat.js';
6
+
7
+ async function routeRequest(ctx, next){
8
+
9
+ const awesomenessRequest = ctx.awesomenessRequest;
10
+
11
+ // make sure there is no dots in the path
12
+ // regex non alphanumeric or forward slash
13
+ if(awesomenessRequest.pageRoute.match(/[^a-zA-Z0-9\/\_\-]/)){
14
+
15
+ ctx.status = 404;
16
+
17
+ ctx.body = {
18
+ success: false,
19
+ message: 'Invalid path',
20
+ };
21
+
22
+ finalFormat(awesomenessRequest, ctx);
23
+
24
+ return;
25
+
26
+ }
27
+
28
+ console.log(awesomenessRequest.awesomenessType, awesomenessRequest.pageRoute);
29
+
30
+
31
+ if(awesomenessRequest.awesomenessType === 'page'){
32
+
33
+ try {
34
+
35
+ let {
36
+ about,
37
+ cssPath,
38
+ jsPath,
39
+ getData
40
+ } = await pageInfo(awesomenessRequest);
41
+
42
+ awesomenessRequest.routeInfo = about;
43
+
44
+ try {
45
+
46
+ await validateRequest(awesomenessRequest);
47
+
48
+ } catch (error) {
49
+
50
+ ctx.status = 422;
51
+
52
+ ctx.body = {
53
+ success: false,
54
+ ... error,
55
+ stack: error.stack,
56
+ };
57
+
58
+ finalFormat(awesomenessRequest, ctx);
59
+
60
+ return;
61
+
62
+ }
63
+
64
+
65
+ const page = awesomenessRequest.mdContent ? '_md' : awesomenessRequest.pageRoute;
66
+
67
+ try {
68
+
69
+ const pageData = await fetchPage(awesomenessRequest, {
70
+ getData,
71
+ about,
72
+ cssPath,
73
+ jsPath,
74
+ page
75
+ });
76
+
77
+ ctx.body = {
78
+ success: true,
79
+ meta: awesomenessRequest.updatedMeta,
80
+ ... pageData
81
+ };
82
+
83
+ finalFormat(awesomenessRequest, ctx);
84
+
85
+ return;
86
+
87
+ } catch (error) {
88
+
89
+ ctx.status = 500;
90
+
91
+ ctx.body = {
92
+ success: false,
93
+ message: 'Page exists but error fetching data.',
94
+ meta: awesomenessRequest.updatedMeta,
95
+ error,
96
+ page,
97
+ errMessage: error?.message || 'Error in getData function',
98
+ stack: error.stack
99
+ };
100
+
101
+ finalFormat(awesomenessRequest, ctx);
102
+
103
+ return;
104
+
105
+ }
106
+
107
+
108
+
109
+ } catch(error) {
110
+
111
+ ctx.status = 404;
112
+
113
+ ctx.body = {
114
+ success: false,
115
+ error,
116
+ message: error?.message || 'Error fetching page',
117
+ stack: error.stack,
118
+ };
119
+
120
+ finalFormat(awesomenessRequest, ctx);
121
+
122
+ return;
123
+
124
+ }
125
+
126
+ } else if(awesomenessRequest.awesomenessType === 'api'){
127
+
128
+ const siteSpecificRoute = new URL(`../../sites/${awesomenessRequest.site}/api/routes/${awesomenessRequest.pageRoute}/index.js`, import.meta.url);
129
+ const genericRoute = new URL(`../../api/routes/${awesomenessRequest.pageRoute}/index.js`, import.meta.url);
130
+
131
+ const siteSpecific_info = new URL(`../../sites/${awesomenessRequest.site}/api/routes/${awesomenessRequest.pageRoute}/_info.js`, import.meta.url);
132
+ const generic_info = new URL(`../../api/routes/${awesomenessRequest.pageRoute}/_info.js`, import.meta.url);
133
+
134
+ let routeIndex;
135
+ let routeInfo;
136
+
137
+ let specific = false;
138
+
139
+ try {
140
+
141
+ routeIndex = await import(siteSpecificRoute);
142
+ routeInfo = await import(siteSpecific_info);
143
+ routeInfo = routeInfo.default;
144
+
145
+ specific = true;
146
+
147
+ } catch (error) {
148
+
149
+ try {
150
+
151
+ routeIndex = await import(genericRoute);
152
+ routeInfo = await import(generic_info);
153
+ routeInfo = routeInfo.default;
154
+
155
+ specific = false;
156
+
157
+ } catch (error) {
158
+
159
+ ctx.status = 404;
160
+
161
+ ctx.body = {
162
+ success: false,
163
+ message: 'route not found',
164
+ error,
165
+ siteSpecificRoute,
166
+ genericRoute,
167
+ };
168
+
169
+ finalFormat(awesomenessRequest, ctx);
170
+
171
+ return;
172
+
173
+ }
174
+
175
+ }
176
+
177
+
178
+
179
+ try {
180
+
181
+ // store routeInfo
182
+ awesomenessRequest.specific = specific;
183
+ awesomenessRequest.routeInfo = routeInfo;
184
+
185
+ // validate data
186
+ try {
187
+
188
+ await validateRequest(awesomenessRequest);
189
+
190
+ } catch (error) {
191
+
192
+ ctx.status = 422;
193
+
194
+ ctx.body = {
195
+ success: false,
196
+ ... error,
197
+ stack: error.stack,
198
+ };
199
+
200
+ finalFormat(awesomenessRequest, ctx);
201
+
202
+ return;
203
+
204
+ }
205
+
206
+ const data = await routeIndex.default(awesomenessRequest);
207
+
208
+ ctx.body = {
209
+ success: true,
210
+ meta: awesomenessRequest.updatedMeta,
211
+ ...data
212
+ };
213
+
214
+ finalFormat(awesomenessRequest, ctx);
215
+
216
+ } catch (error) {
217
+
218
+ ctx.status = 500;
219
+
220
+ if(awesomenessRequest.status){
221
+
222
+ ctx.status = awesomenessRequest.status;
223
+
224
+ }
225
+
226
+ ctx.body = {
227
+ success: false,
228
+ meta: awesomenessRequest.updatedMeta,
229
+ message: error?.message || 'Error in route function',
230
+ error,
231
+ stack: error.stack,
232
+ };
233
+
234
+ finalFormat(awesomenessRequest, ctx);
235
+
236
+ return;
237
+
238
+ }
239
+
240
+
241
+ } else {
242
+
243
+ // if its a get request
244
+ // a static file should have been served already
245
+ // if the application is working correctly then ctx.awesomenessType === 'page' should be true
246
+ // if it is not true, they are hitting a url cold or reloading
247
+ // so load the app
248
+
249
+ if(awesomenessRequest.method != 'GET'){
250
+
251
+ ctx.status = 405;
252
+ ctx.body = {
253
+ success: false,
254
+ message: 'Method Not Allowed',
255
+ };
256
+
257
+ finalFormat(awesomenessRequest, ctx);
258
+
259
+ return;
260
+
261
+ }
262
+
263
+ if(awesomenessRequest.path === '/test'){
264
+
265
+ ctx.body = {
266
+ success: true,
267
+ message: 'Hello, World! (just a test)',
268
+ };
269
+
270
+ finalFormat(awesomenessRequest, ctx);
271
+
272
+ return;
273
+
274
+ }
275
+
276
+ awesomenessRequest.path = '/';
277
+ await staticFiles(ctx, next);
278
+
279
+ return;
280
+
281
+ }
282
+
283
+
284
+ }
285
+
286
+
287
+
288
+ export { routeRequest };
@@ -0,0 +1,7 @@
1
+ const serverUp = async () => {
2
+
3
+ console.log(`Server running on port ${process.env.PORT}`);
4
+
5
+ };
6
+
7
+ export { serverUp };
@@ -0,0 +1,97 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import send from 'koa-send';
4
+ import { fileURLToPath } from "node:url";
5
+ import { getConfig } from '../../config.js';
6
+
7
+ const staticFiles = async (ctx, next) => {
8
+
9
+ const {
10
+ method,
11
+ path: reqPath,
12
+ site
13
+ } = ctx.awesomenessRequest;
14
+
15
+ const {
16
+ siteURL,
17
+ commonPublicDir
18
+ } = getConfig();
19
+
20
+ if (method !== "GET" && method !== "HEAD") {
21
+
22
+ await next();
23
+
24
+ return;
25
+
26
+ }
27
+
28
+ if (!(siteURL instanceof URL) || !(commonPublicDir instanceof URL)) {
29
+
30
+ ctx.throw(500, new Error("Config must provide siteURL and commonPublicDir as file: URLs"));
31
+
32
+ return;
33
+
34
+ }
35
+
36
+ const sitesRoot = fileURLToPath(siteURL);
37
+ const commonRoot = fileURLToPath(commonPublicDir);
38
+
39
+ const domainRoot = path.join(sitesRoot, site, "public");
40
+
41
+ const relativePath =
42
+ reqPath === "/" ? "index.html" : reqPath.replace(/^\//, "");
43
+
44
+ const domainFilePath = path.join(domainRoot, relativePath);
45
+ const commonFilePath = path.join(commonRoot, relativePath);
46
+
47
+ let fileServed = false;
48
+
49
+ try {
50
+
51
+ // Try to serve from the domain-specific directory
52
+ await fs.promises.access(domainFilePath);
53
+ ctx.set('X-Awesomeness-Place', 'Domain');
54
+ await send(ctx, relativePath, { root: domainRoot });
55
+ fileServed = true;
56
+
57
+ } catch (err) {
58
+
59
+ if (err.code !== 'ENOENT') {
60
+
61
+ ctx.throw(500, new Error('Internal Server Error'));
62
+
63
+ }
64
+
65
+ }
66
+
67
+ if (!fileServed) {
68
+
69
+ try {
70
+
71
+ // Try to serve from the common directory
72
+ await fs.promises.access(commonFilePath);
73
+ ctx.set('X-Awesomeness-Place', 'Common');
74
+ await send(ctx, relativePath, { root: commonRoot });
75
+ fileServed = true;
76
+
77
+ } catch (err2) {
78
+
79
+ if (err2.code !== 'ENOENT') {
80
+
81
+ ctx.throw(500, new Error('Internal Server Error'));
82
+
83
+ }
84
+
85
+ }
86
+
87
+ }
88
+
89
+ if (!fileServed) {
90
+
91
+ await next(); // Proceed to next middleware if no static file was served
92
+
93
+ }
94
+
95
+ };
96
+
97
+ export { staticFiles };
@@ -0,0 +1,42 @@
1
+ // Timeout middleware
2
+ const timeout = (ms) => {
3
+
4
+ return async (ctx, next) => {
5
+
6
+ // Create a promise that rejects after `ms` milliseconds
7
+ const timer = new Promise((_, reject) => {
8
+
9
+ const id = setTimeout(() => {
10
+
11
+ reject(new Error('Request timed out'));
12
+
13
+ }, ms);
14
+
15
+ // Clear the timer if the request finishes early
16
+ ctx.res.on('finish', () => clearTimeout(id));
17
+
18
+ });
19
+
20
+ try {
21
+
22
+ // Race the timer and the next middleware
23
+ await Promise.race([ next(), timer ]);
24
+
25
+ } catch (err) {
26
+
27
+ // If an error occurs (such as a timeout), set the response
28
+ ctx.status = 408; // 408 Request Timeout
29
+ ctx.body = {
30
+ success: false,
31
+ message: err.message
32
+ };
33
+
34
+ return;
35
+
36
+ }
37
+
38
+ };
39
+
40
+ };
41
+
42
+ export { timeout };
@@ -0,0 +1,121 @@
1
+ import { getConfig } from "../config.js";
2
+
3
+ import path from "path";
4
+ import { fileURLToPath, pathToFileURL } from "url";
5
+ import { existsSync } from "fs";
6
+ import { promises as fs } from "fs";
7
+ import { resolveRealCasePath } from "./resolveRealCasePath.js";
8
+
9
+ export default async function pageInfo(awesomenessRequest, { page = null } = {}) {
10
+
11
+ const awesomenessConfig = getConfig();
12
+
13
+ if (!page) {
14
+
15
+ page = awesomenessRequest.pageRoute;
16
+
17
+ }
18
+
19
+ const slug = page.split("/").pop().split(".")[0].split("?")[0];
20
+
21
+ // awesomenessConfig.siteURL points at the /sites/ directory (as a URL)
22
+ const sitesRootPath =
23
+ awesomenessConfig.siteURL instanceof URL
24
+ ? fileURLToPath(awesomenessConfig.siteURL)
25
+ : awesomenessConfig.siteURL;
26
+
27
+ const siteRootPath = path.join(sitesRootPath, awesomenessRequest.site);
28
+ const pagesRootPath = path.join(siteRootPath, "pages");
29
+
30
+ const mainPath = resolveRealCasePath(path.join(pagesRootPath, page));
31
+
32
+ let getDataPath = mainPath ? path.join(mainPath, "getData.js") : null;
33
+ let aboutPath = mainPath ? path.join(mainPath, "_info.js") : null;
34
+ let cssPath = mainPath ? path.join(mainPath, "css") : null;
35
+ let jsPath = mainPath ? path.join(mainPath, "js") : null;
36
+
37
+ let aboutExists = aboutPath ? existsSync(aboutPath) : false;
38
+ let getDataExists = getDataPath ? existsSync(getDataPath) : false;
39
+
40
+ if (aboutExists) {
41
+
42
+ const { default: about } = await import(pathToFileURL(aboutPath).href);
43
+
44
+ let getData = null;
45
+
46
+ if (getDataExists) {
47
+
48
+ ({ default: getData } = await import(pathToFileURL(getDataPath).href));
49
+
50
+ }
51
+
52
+ return {
53
+ getData,
54
+ about,
55
+ cssPath,
56
+ jsPath,
57
+ };
58
+
59
+ }
60
+
61
+ // is it a simple blog page?
62
+ getDataPath = path.join(pagesRootPath, "_md", "getData.js");
63
+ aboutPath = path.join(pagesRootPath, "_md", "_info.js");
64
+ cssPath = path.join(pagesRootPath, "_md", "css");
65
+ jsPath = path.join(pagesRootPath, "_md", "js");
66
+
67
+ aboutExists = existsSync(aboutPath);
68
+ getDataExists = existsSync(getDataPath);
69
+
70
+ if (!aboutExists) {
71
+
72
+ throw {
73
+ message: "page not found",
74
+ aboutPath,
75
+ cssPath,
76
+ jsPath,
77
+ awesomenessRequest,
78
+ };
79
+
80
+ }
81
+
82
+ const blogPageLocation = path.join(pagesRootPath, "_md", "pages", `${slug}.md`);
83
+ const relativePath = resolveRealCasePath(blogPageLocation);
84
+
85
+ const { default: about } = await import(pathToFileURL(aboutPath).href);
86
+
87
+ let getData = null;
88
+
89
+ if (getDataExists) {
90
+
91
+ ({ default: getData } = await import(pathToFileURL(getDataPath).href));
92
+
93
+ }
94
+
95
+ const out = {
96
+ getData,
97
+ about,
98
+ cssPath,
99
+ jsPath,
100
+ };
101
+
102
+ if (relativePath) {
103
+
104
+ out.mdContent = await fs.readFile(relativePath, "utf-8");
105
+ awesomenessRequest.mdContent = out.mdContent;
106
+
107
+ } else {
108
+
109
+ console.log("relative path does not exist, no content found");
110
+
111
+ throw {
112
+ code: 404,
113
+ message: "page not found. tried specific, MD, and database",
114
+ url: slug,
115
+ };
116
+
117
+ }
118
+
119
+ return out;
120
+
121
+ }
@@ -0,0 +1,54 @@
1
+ import specialPaths from './specialPaths.js';
2
+ import fetchPage from './fetchPage.js';
3
+ import { getConfig } from "../config.js";
4
+
5
+ export default async function reRoute({
6
+ goToPage,
7
+ awesomenessRequest
8
+ }){
9
+
10
+ const awesomenessConfig = getConfig();
11
+
12
+ if(!goToPage){
13
+
14
+ return {
15
+ pageData: {},
16
+ pageRouteFixed: awesomenessRequest.pageRoute
17
+ };
18
+
19
+ }
20
+
21
+ if(!awesomenessRequest.reRoutes){
22
+
23
+ awesomenessRequest.reRoutes = [];
24
+
25
+ }
26
+
27
+ awesomenessRequest.reRoutes.push(awesomenessRequest.pageRoute);
28
+
29
+ // re-route
30
+ awesomenessRequest.path = goToPage;
31
+
32
+ // page route - replace leading and trailing slash
33
+ goToPage = goToPage.replace(/^\/+|\/+$/g, '');
34
+ awesomenessRequest.pageRoute = goToPage;
35
+
36
+ // slug
37
+ const slug = goToPage.split('/').pop().split('.')[0].split('?')[0];
38
+
39
+ awesomenessRequest.slug = slug;
40
+
41
+ const routes = awesomenessConfig.specialRoutes[awesomenessRequest.site] || [];
42
+
43
+ await specialPaths(awesomenessRequest, routes);
44
+
45
+ const pageData = await fetchPage(awesomenessRequest);
46
+
47
+ const out = {
48
+ pageData,
49
+ pageRouteFixed: awesomenessRequest.pageRoute
50
+ };
51
+
52
+ return out;
53
+
54
+ }
@@ -0,0 +1,56 @@
1
+ import { readdirSync } from "fs";
2
+ import path from "path";
3
+
4
+ /**
5
+ * Case-insensitive real path resolver.
6
+ * Works correctly on Windows, macOS, and Linux/Docker.
7
+ */
8
+ export function resolveRealCasePath(inputPath, returnAbsolute = false) {
9
+
10
+ if (!inputPath) return null;
11
+
12
+ const absPath = path.resolve(inputPath);
13
+ const { root } = path.parse(absPath);
14
+ const parts = absPath.split(path.sep).filter(Boolean);
15
+
16
+ let current = root || path.sep;
17
+
18
+ // ✅ Only skip the first part if it's a Windows drive letter (e.g., "C:\")
19
+ const startIndex = process.platform === "win32" ? 1 : 0;
20
+
21
+ for (const part of parts.slice(startIndex)) {
22
+
23
+ try {
24
+
25
+ const entries = readdirSync(current);
26
+ const match = entries.find((e) => e.toLowerCase() === part.toLowerCase());
27
+
28
+ if (!match) return null;
29
+ current = path.join(current, match);
30
+
31
+ } catch {
32
+
33
+ return null;
34
+
35
+ }
36
+
37
+ }
38
+
39
+ let finalPath;
40
+
41
+
42
+ if (returnAbsolute) {
43
+
44
+ finalPath = current;
45
+
46
+ } else {
47
+
48
+ const rel = path.relative(process.cwd(), current);
49
+
50
+ finalPath = rel.startsWith(".") ? rel : `./${rel}`;
51
+
52
+ }
53
+
54
+ return finalPath.split(path.sep).join("/");
55
+
56
+ }