aaexjs-test 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.
@@ -0,0 +1,6 @@
1
+ interface ClientWrapperProps extends React.PropsWithChildren {
2
+ fallback?: React.ReactNode | React.ReactNode[];
3
+ }
4
+ export declare function ClientOnly({ children, fallback, }: ClientWrapperProps): import("react").ReactNode;
5
+ export {};
6
+ //# sourceMappingURL=ClientOnly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientOnly.d.ts","sourceRoot":"","sources":["../../src/components/ClientOnly.tsx"],"names":[],"mappings":"AAEA,UAAU,kBAAmB,SAAQ,KAAK,CAAC,iBAAiB;IAC1D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;CAChD;AAED,wBAAiB,UAAU,CAAC,EAC1B,QAAQ,EACR,QAAe,GAChB,EAAE,kBAAkB,6BAWpB"}
@@ -0,0 +1,12 @@
1
+ import { useEffect, useState } from "react";
2
+ export function ClientOnly({ children, fallback = null, }) {
3
+ const [mounted, setMounted] = useState(false);
4
+ useEffect(() => {
5
+ setMounted(true);
6
+ }, []);
7
+ if (mounted) {
8
+ return children;
9
+ }
10
+ return fallback;
11
+ }
12
+ //# sourceMappingURL=ClientOnly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientOnly.js","sourceRoot":"","sources":["../../src/components/ClientOnly.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAM5C,MAAM,UAAW,UAAU,CAAC,EAC1B,QAAQ,EACR,QAAQ,GAAG,IAAI,GACI;IACnB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE9C,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { ClientOnly } from "./components/ClientOnly.js";
2
+ export { pluginSsrDevFoucFix } from "./utils/ServerLoadCssImports.js";
3
+ export * as Cookies from "./utils/cookies.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { ClientOnly } from "./components/ClientOnly.js";
2
+ export { pluginSsrDevFoucFix } from "./utils/ServerLoadCssImports.js";
3
+ export * as Cookies from "./utils/cookies.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vite';
2
+ export declare function pluginSsrDevFoucFix(): Plugin;
3
+ //# sourceMappingURL=ServerLoadCssImports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerLoadCssImports.d.ts","sourceRoot":"","sources":["../../src/utils/ServerLoadCssImports.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,MAAM,EAAgB,MAAM,MAAM,CAAC;AAMhD,wBAAgB,mBAAmB,IAAI,MAAM,CA0C5C"}
@@ -0,0 +1,45 @@
1
+ //credit to https://github.com/vitejs/vite/issues/16515
2
+ const virtualCssPath = '/@virtual:ssr-css.css';
3
+ const collectedStyles = new Map();
4
+ export function pluginSsrDevFoucFix() {
5
+ let server;
6
+ return {
7
+ name: 'ssr-dev-FOUC-fix',
8
+ apply: 'serve',
9
+ transform(code, id) {
10
+ if (id.includes('node_modules'))
11
+ return null;
12
+ if (id.includes('.css')) {
13
+ collectedStyles.set(id, code);
14
+ }
15
+ return null;
16
+ },
17
+ configureServer(server_) {
18
+ server = server_;
19
+ server.middlewares.use((req, _res, next) => {
20
+ if (req.url === virtualCssPath) {
21
+ _res.setHeader('Content-Type', 'text/css');
22
+ _res.write(Array.from(collectedStyles.values()).join('\n'));
23
+ _res.end();
24
+ return;
25
+ }
26
+ next();
27
+ });
28
+ },
29
+ transformIndexHtml: {
30
+ handler: async () => {
31
+ return [
32
+ {
33
+ tag: 'link',
34
+ injectTo: 'head',
35
+ attrs: {
36
+ rel: 'stylesheet',
37
+ href: virtualCssPath,
38
+ },
39
+ },
40
+ ];
41
+ },
42
+ },
43
+ };
44
+ }
45
+ //# sourceMappingURL=ServerLoadCssImports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerLoadCssImports.js","sourceRoot":"","sources":["../../src/utils/ServerLoadCssImports.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAIvD,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAE/C,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;AAElD,MAAM,UAAU,mBAAmB;IACjC,IAAI,MAAqB,CAAC;IAE1B,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,OAAO;QACd,SAAS,CAAC,IAAY,EAAE,EAAU;YAChC,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC7C,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,eAAe,CAAC,OAAO;YACrB,MAAM,GAAG,OAAO,CAAC;YAEjB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;gBACzC,IAAI,GAAG,CAAC,GAAG,KAAK,cAAc,EAAE,CAAC;oBAC/B,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;oBAC3C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;oBAC5D,IAAI,CAAC,GAAG,EAAE,CAAC;oBACX,OAAO;gBACT,CAAC;gBACD,IAAI,EAAE,CAAC;YACT,CAAC,CAAC,CAAC;QACL,CAAC;QAED,kBAAkB,EAAE;YAClB,OAAO,EAAE,KAAK,IAAI,EAAE;gBAClB,OAAO;oBACL;wBACE,GAAG,EAAE,MAAM;wBACX,QAAQ,EAAE,MAAM;wBAChB,KAAK,EAAE;4BACL,GAAG,EAAE,YAAY;4BACjB,IAAI,EAAE,cAAc;yBACrB;qBACF;iBACF,CAAC;YACJ,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ export declare function setCookie(name: string, value: string, days?: number, options?: {
2
+ path?: string;
3
+ secure?: boolean;
4
+ sameSite?: "Strict" | "Lax" | "None";
5
+ domain?: string;
6
+ }): void;
7
+ export declare function getCookie(name: string): string | null;
8
+ export declare function getCookieAsJSON(name: string): Record<string, string> | null | undefined;
9
+ export declare function deleteCookie(name: string, options?: {
10
+ path?: string;
11
+ domain?: string;
12
+ }): void;
13
+ //# sourceMappingURL=cookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/utils/cookies.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,SAAI,EACR,OAAO,GAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACZ,QAcP;AACD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,iBAWrC;AAiBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,6CAI3C;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,QAKjD"}
@@ -0,0 +1,47 @@
1
+ export function setCookie(name, value, days = 7, options = {}) {
2
+ const maxAge = days * 24 * 60 * 60;
3
+ const encoded = encodeURIComponent(value);
4
+ let cookie = `${name}=${encoded}; max-age=${maxAge}; path=${options.path ?? "/"}`;
5
+ if (options.secure)
6
+ cookie += "; secure";
7
+ if (options.sameSite)
8
+ cookie += `; samesite=${options.sameSite}`;
9
+ if (options.domain)
10
+ cookie += `; domain=${options.domain}`;
11
+ document.cookie = cookie;
12
+ }
13
+ export function getCookie(name) {
14
+ const cookies = document.cookie.split("; ");
15
+ for (const c of cookies) {
16
+ const [key, ...rest] = c.split("=");
17
+ if (key === name) {
18
+ return decodeURIComponent(rest.join("="));
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ function cookieToJSON(cookie) {
24
+ const parts = cookie.split("; ");
25
+ const result = {};
26
+ for (const part of parts) {
27
+ const [key, ...rest] = part.split("=");
28
+ if (!key) {
29
+ return;
30
+ }
31
+ result[key] = rest.join("=");
32
+ }
33
+ return result;
34
+ }
35
+ export function getCookieAsJSON(name) {
36
+ const raw = getCookie(name);
37
+ if (!raw)
38
+ return null;
39
+ return cookieToJSON(raw);
40
+ }
41
+ export function deleteCookie(name, options = {}) {
42
+ let cookie = `${name}=; max-age=0; path=${options.path ?? "/"}`;
43
+ if (options.domain)
44
+ cookie += `; domain=${options.domain}`;
45
+ document.cookie = cookie;
46
+ }
47
+ //# sourceMappingURL=cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/utils/cookies.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,KAAa,EACb,IAAI,GAAG,CAAC,EACR,UAKI,EAAE;IAEN,MAAM,MAAM,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE1C,IAAI,MAAM,GAAG,GAAG,IAAI,IAAI,OAAO,aAAa,MAAM,UAChD,OAAO,CAAC,IAAI,IAAI,GAClB,EAAE,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,UAAU,CAAC;IACzC,IAAI,OAAO,CAAC,QAAQ;QAAE,MAAM,IAAI,cAAc,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjE,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC;IAE3D,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;AAC3B,CAAC;AACD,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE5C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO;QACT,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,UAA8C,EAAE;IAEhD,IAAI,MAAM,GAAG,GAAG,IAAI,sBAAsB,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;IAChE,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC;IAC3D,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;AAC3B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "aaexjs-test",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1",
8
+ "dev": "npx tsc --watch",
9
+ "build": "tsc",
10
+ "postinstall": "node scripts/postinstall.js"
11
+ },
12
+ "exports": {
13
+ ".": "./dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "scripts",
18
+ "templates"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "keywords": [],
24
+ "author": "",
25
+ "license": "ISC",
26
+ "type": "module",
27
+ "devDependencies": {
28
+ "@types/node": "^25.0.3",
29
+ "@types/react": "^19.2.7",
30
+ "typescript": "^5.9.3",
31
+ "vite": "^7.3.0"
32
+ },
33
+ "dependencies": {
34
+ "react": "^19.2.3"
35
+ }
36
+ }
@@ -0,0 +1,103 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ /* ------------------------------
6
+ Paths & environment
7
+ -------------------------------- */
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const projectRoot = process.cwd();
13
+ const aaexDir = path.join(projectRoot, ".aaex");
14
+ const templateDir = path.join(__dirname, "../templates/.aaex");
15
+ const versionFile = path.join(aaexDir, "version.json");
16
+
17
+ /* ------------------------------
18
+ Read framework version
19
+ -------------------------------- */
20
+
21
+ function readPackageVersion() {
22
+ try {
23
+ const pkgPath = path.join(__dirname, "../package.json");
24
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
25
+ return pkg.version;
26
+ } catch {
27
+ return "unknown";
28
+ }
29
+ }
30
+
31
+ const frameworkVersion = readPackageVersion();
32
+
33
+ /* ------------------------------
34
+ Helpers
35
+ -------------------------------- */
36
+
37
+ function readInstalledVersion() {
38
+ if (!fs.existsSync(versionFile)) return null;
39
+
40
+ try {
41
+ const json = JSON.parse(fs.readFileSync(versionFile, "utf-8"));
42
+ return json.version ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function writeVersionFile() {
49
+ const payload = {
50
+ framework: "aaexjs",
51
+ version: frameworkVersion,
52
+ };
53
+
54
+ fs.writeFileSync(versionFile, JSON.stringify(payload, null, 2));
55
+ }
56
+
57
+ function copyRecursive(src, dest) {
58
+ if (!fs.existsSync(dest)) {
59
+ fs.mkdirSync(dest, { recursive: true });
60
+ }
61
+
62
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
63
+ const srcPath = path.join(src, entry.name);
64
+ const destPath = path.join(dest, entry.name);
65
+
66
+ if (entry.isDirectory()) {
67
+ copyRecursive(srcPath, destPath);
68
+ } else {
69
+ fs.copyFileSync(srcPath, destPath);
70
+ }
71
+ }
72
+ }
73
+
74
+ /* ------------------------------
75
+ Main logic
76
+ -------------------------------- */
77
+
78
+ try {
79
+ const installedVersion = readInstalledVersion();
80
+
81
+ if (!fs.existsSync(aaexDir)) {
82
+ console.log("[aaexjs] Creating .aaex directory");
83
+ copyRecursive(templateDir, aaexDir);
84
+ writeVersionFile();
85
+ console.log(`[aaexjs] Installed framework files (v${frameworkVersion})`);
86
+ process.exit(1)
87
+ }
88
+
89
+ if (installedVersion !== frameworkVersion) {
90
+ console.log(
91
+ `[aaexjs] Updating .aaex from v${installedVersion ?? "unknown"} → v${frameworkVersion}`
92
+ );
93
+ copyRecursive(templateDir, aaexDir);
94
+ writeVersionFile();
95
+ console.log("[aaexjs] Update complete");
96
+ } else {
97
+ // Optional: keep silent to avoid noisy installs
98
+ // console.log("[aaexjs] .aaex is up to date");
99
+ }
100
+ } catch (err) {
101
+ console.warn("[aaexjs] Failed to setup .aaex directory");
102
+ console.warn(err);
103
+ }
@@ -0,0 +1,67 @@
1
+ import { FileScanner } from "aaex-file-router/core";
2
+ import { match } from "path-to-regexp";
3
+
4
+ // --- scan folders ---
5
+ //wrapper for the FilesScanner and output data
6
+ async function scanApiFolder(folder) {
7
+ const scanner = new FileScanner(folder);
8
+ return await scanner.get_file_data();
9
+ }
10
+
11
+ const userApiFolder = "src/api";
12
+
13
+ const internalApiFolder = ".aaex/api";
14
+
15
+ //collect file data as easily parsed arrays
16
+ const [userFiles, internalFiles] = await Promise.all([
17
+ scanApiFolder(userApiFolder),
18
+ scanApiFolder(internalApiFolder),
19
+ ]);
20
+
21
+ // --- combine files ---
22
+ const fileMap = new Map();
23
+ internalFiles.forEach((f) => fileMap.set(f.relative_path, f));
24
+ userFiles.forEach((f) => fileMap.set(f.relative_path, f)); //overrides internal api with user defined routes
25
+ const combinedFiles = Array.from(fileMap.values());
26
+
27
+ // --- build route list ---
28
+ /**Builds route object from the scanned files */
29
+ function buildApiRoutes(files) {
30
+ const routes = [];
31
+
32
+ function walk(node, currentPath) {
33
+ //recursivly iterate over child routes
34
+ if (node.isDirectory) {
35
+ node.children?.forEach((c) => walk(c, currentPath + "/" + node.name));
36
+ return;
37
+ }
38
+ const filePath = currentPath + "/" + node.name;
39
+ //build route path from filePath
40
+ let route = filePath
41
+ .replace(/^.*(src\/api|api)/, "/api") //removed parent folder like src
42
+ .replace(/\.ts|js$/, "") //removes file extension
43
+ .replace(/\[(.+?)\]/g, ":$1") //converts [slug] to :slug
44
+ .replace("/index", "");
45
+
46
+ routes.push({ route, filePath: node.relative_path });
47
+ }
48
+
49
+ files.forEach((file) => walk(file, ""));
50
+ return routes;
51
+ }
52
+
53
+ const routes = buildApiRoutes(combinedFiles);
54
+
55
+ // --- match path to route ---
56
+ /** Matches the given path to API routes generated from the api folder + the aaex/api folder*/
57
+ function pathToRoute(pathname) {
58
+ for (const r of routes) {
59
+ const matcher = match(r.route, { decode: decodeURIComponent });
60
+ const matched = matcher(pathname);
61
+ if (matched) return { route: r, params: matched.params };
62
+ }
63
+ return null;
64
+ }
65
+
66
+ // --- export ---
67
+ export { routes, pathToRoute };
@@ -0,0 +1,57 @@
1
+ import { connectToDatabase } from "../../framework/database/mongodb";
2
+ import type { Request, Response } from "express";
3
+ import bcrypt from "bcrypt";
4
+ import jwt from "jsonwebtoken";
5
+ import { LoginUser } from "../../../src/models/User";
6
+
7
+ export const POST = async (req: Request, res: Response) => {
8
+ const { email, password }: LoginUser = req.body;
9
+
10
+ if (!email || !password) {
11
+ return res.status(400).json({ error: "Missing fields!" });
12
+ }
13
+
14
+ const { db } = await connectToDatabase();
15
+
16
+ if (!process.env.JWT_SECRET) {
17
+ console.error("Missing: JWT_SECRET from environment variables");
18
+ return res
19
+ .status(500)
20
+ .json({ error: "Internal server error! Try again later" });
21
+ }
22
+
23
+ const normalizedEmail = email.trim().toLowerCase();
24
+ const user = await db.collection("users").findOne({ email: normalizedEmail });
25
+
26
+ if (!user) {
27
+ return res.status(400).json({ error: "Invalid email or password" });
28
+ }
29
+
30
+ const compared = await bcrypt.compare(password, user.password);
31
+
32
+ if (!compared) {
33
+ return res.status(400).json({ error: "Invalid email or password" });
34
+ }
35
+
36
+ const expiration = process.env.JWT_EXP ?? "24h";
37
+
38
+ const token = jwt.sign(
39
+ {
40
+ id: user._id.toString(),
41
+ email: user.email,
42
+ username: user.username,
43
+ },
44
+ process.env.JWT_SECRET as string,
45
+ { expiresIn: expiration as any } //fixes stupid thing where it wont accept the sring variable because its not number | ms.stringvlue | undefined
46
+ );
47
+
48
+ return res.status(200).json({
49
+ ok: true,
50
+ user: {
51
+ id: user._id.toString(),
52
+ name: user.username,
53
+ email: user.email,
54
+ },
55
+ token,
56
+ });
57
+ };
@@ -0,0 +1,34 @@
1
+ import { Request, Response } from "express";
2
+ import bcrypt from "bcrypt";
3
+ import { connectToDatabase } from "../../framework/database/mongodb";
4
+ import { CreateUser } from "../../../src/models/User";
5
+ export const POST = async (req: Request, res: Response) => {
6
+
7
+ const { email, username, password, confirmPass }: CreateUser = req.body;
8
+
9
+ if (!username || !email || !password || !confirmPass)
10
+ return res.status(400).json({ error: "Missing fields" });
11
+
12
+ if (password !== confirmPass)
13
+ return res.status(400).json({ error: "Passwords do not match" });
14
+
15
+ const { db } = await connectToDatabase();
16
+
17
+ const exists = await db.collection("users").findOne({ email });
18
+ if (exists)
19
+ return res
20
+ .status(409)
21
+ .json({ error: "User with that email already exists" });
22
+
23
+ const salt = 10;
24
+ const hashed = await bcrypt.hash(password, salt);
25
+
26
+ await db.collection("users").insertOne({
27
+ username,
28
+ email,
29
+ password: hashed,
30
+ createdAt: new Date(),
31
+ });
32
+
33
+ return res.status(201).json({ ok: true });
34
+ };
@@ -0,0 +1,18 @@
1
+ import jwt from "jsonwebtoken";
2
+ import type { Request, Response } from "express";
3
+
4
+ export async function POST(req: Request, res: Response) {
5
+ const { token } = req.body;
6
+
7
+ if (!process.env.JWT_SECRET) {
8
+ console.error("Missing: JWT_SECRET from environment");
9
+ return res.status(500).json({error: "Internal server error!"});
10
+ }
11
+
12
+ try {
13
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
14
+ return res.status(200).json({ valid: true, user: decoded });
15
+ } catch (err) {
16
+ return res.status(401).json({ valid: false });
17
+ }
18
+ }
@@ -0,0 +1,29 @@
1
+ import { MongoClient, Db } from "mongodb";
2
+
3
+ const MONGO_URI = process.env.MONGO_URI || "mongodb://localhost:27017";
4
+ const DB_NAME = process.env.DB_NAME || "mydatabase";
5
+
6
+ if (!MONGO_URI) {
7
+ throw new Error("Please define the MONGO_URI environment variable inside .env");
8
+ }
9
+
10
+ let cachedClient: MongoClient | null = null;
11
+ let cachedDb: Db | null = null;
12
+
13
+ export async function connectToDatabase(): Promise<{ client: MongoClient; db: Db }> {
14
+ // Return cached connection if it exists
15
+ if (cachedClient && cachedDb) {
16
+ return { client: cachedClient, db: cachedDb };
17
+ }
18
+
19
+ const client = new MongoClient(MONGO_URI);
20
+ await client.connect();
21
+ const db = client.db(DB_NAME);
22
+
23
+ cachedClient = client;
24
+ cachedDb = db;
25
+
26
+ console.log("MongoDB connected:", DB_NAME);
27
+
28
+ return { client, db };
29
+ }
@@ -0,0 +1,16 @@
1
+ import "../../src/index.css";
2
+ import { StrictMode } from "react";
3
+ import { hydrateRoot } from "react-dom/client";
4
+ import App from "../../src/App";
5
+ import { BrowserRouter } from "react-router";
6
+
7
+ const initialData = (window as any).__INITIAL_DATA__;
8
+
9
+ hydrateRoot(
10
+ document.getElementById("root") as HTMLElement,
11
+ <StrictMode>
12
+ <BrowserRouter>
13
+ <App initialData={initialData} />
14
+ </BrowserRouter>
15
+ </StrictMode>
16
+ );
@@ -0,0 +1,18 @@
1
+ import { StrictMode, Suspense } from "react";
2
+ import { renderToString } from "react-dom/server";
3
+ import { StaticRouter } from "react-router";
4
+ import App from "../../src/App";
5
+
6
+ export function render(_url: string, initialData= {}) {
7
+ const url = `${_url}`;
8
+
9
+
10
+ const html = renderToString(
11
+ <StrictMode>
12
+ <StaticRouter location={url}>
13
+ <App initialData={initialData} />
14
+ </StaticRouter>
15
+ </StrictMode>
16
+ );
17
+ return { html };
18
+ }
@@ -0,0 +1,60 @@
1
+ export default function routeMatcher(routes, url) {
2
+ const segments = url.split("/").filter(Boolean); // "test/hej" → ["test", "hej"]
3
+
4
+ return matchLevel(routes, segments);
5
+ }
6
+
7
+ function matchLevel(routes, segments) {
8
+ for (const route of routes) {
9
+ const result = matchRoute(route, segments);
10
+
11
+ if (result) return result;
12
+ }
13
+
14
+ return null;
15
+ }
16
+
17
+ function matchRoute(route, segments) {
18
+ const isParam = /^:[a-zA-Z0-9_]+$/;
19
+
20
+ const [current, ...rest] = segments;
21
+
22
+ // Root index route
23
+ if (route.path === "" && segments.length === 0) {
24
+ return { route, params: {} };
25
+ }
26
+
27
+ // Static match
28
+ if (route.path === current) {
29
+ if (rest.length === 0) {
30
+ return { route, params: {} };
31
+ }
32
+
33
+ // Has children → go deeper
34
+ if (route.children) {
35
+ return matchLevel(route.children, rest);
36
+ }
37
+ }
38
+
39
+ // Dynamic match → :slug , :id osv
40
+ if (isParam.test(route.path)) {
41
+ const paramName = route.path.slice(1);
42
+
43
+ if (rest.length === 0) {
44
+ return { route, params: { [paramName]: current } };
45
+ }
46
+
47
+ // Has children → go deeper
48
+ if (route.children) {
49
+ const matchedChild = matchLevel(route.children, rest);
50
+ if (matchedChild) {
51
+ return {
52
+ route: matchedChild.route,
53
+ params: { [paramName]: current, ...matchedChild.params },
54
+ };
55
+ }
56
+ }
57
+ }
58
+
59
+ return null;
60
+ }
@@ -0,0 +1,179 @@
1
+ import fs from "node:fs/promises";
2
+ import express from "express";
3
+ import { pathToFileURL } from "node:url";
4
+ import path from "node:path";
5
+ import dotenv from "dotenv";
6
+ dotenv.config();
7
+
8
+ // server.js is now in .aaex/server/
9
+ const projectRoot = path.resolve("."); // root of the project
10
+ let serverRoutes;
11
+ // Import BuildApiRoutes
12
+ import * as BuildApiRoutes from "../BuildApiRoutes.js";
13
+ import routeMatcher from "../matchServerRoutes.js";
14
+
15
+ const apiRoutes = BuildApiRoutes.default; // default export
16
+ const PathToRoute = BuildApiRoutes.pathToRoute; // named export
17
+
18
+ // Constants
19
+ const isProduction = process.env.NODE_ENV === "production";
20
+ const port = process.env.PORT || 5173;
21
+ const base = process.env.BASE || "/";
22
+
23
+ // Cached production HTML
24
+ const templateHtml = isProduction
25
+ ? await fs.readFile(path.join(projectRoot, "dist/client/index.html"), "utf-8")
26
+ : "";
27
+
28
+ // Create HTTP server
29
+ const app = express();
30
+
31
+ /** @type {import('vite').ViteDevServer | undefined} */
32
+ let vite;
33
+ if (!isProduction) {
34
+ const { createServer } = await import("vite");
35
+ vite = await createServer({
36
+ server: { middlewareMode: true },
37
+ appType: "custom",
38
+ root: projectRoot,
39
+ base,
40
+ });
41
+ serverRoutes = (await vite.ssrLoadModule("/src/server-routes.ts")).default;
42
+ app.use(vite.middlewares);
43
+ } else {
44
+ const compression = (await import("compression")).default;
45
+ const sirv = (await import("sirv")).default;
46
+ app.use(compression());
47
+ app.use(
48
+ base,
49
+ sirv(path.join(projectRoot, "dist/client"), { extensions: [] })
50
+ );
51
+
52
+ const serverRoutesModule = await import(
53
+ pathToFileURL(path.join(projectRoot, "dist/src/server-routes.js")).href
54
+ );
55
+
56
+ serverRoutes = serverRoutesModule.default;
57
+ }
58
+
59
+ // parse JSON bodies
60
+ app.use(express.json());
61
+
62
+ // parse URL-encoded bodies (optional, for form POSTs)
63
+ app.use(express.urlencoded({ extended: true }));
64
+
65
+ // API routing
66
+ app.use("/api", async (req, res) => {
67
+ const routeMatch = PathToRoute(req.path, apiRoutes);
68
+
69
+ if (!routeMatch)
70
+ return res.status(404).json({ error: "API route not found" });
71
+
72
+ const { route, params } = routeMatch;
73
+ let modulePath;
74
+
75
+ if (!isProduction) {
76
+ // DEV: Vite handles TS/JS loading
77
+ if (route.filePath.split("/")[0] == ".aaex") {
78
+ modulePath = `${route.filePath.replace(/^src\/api/, "")}`;
79
+ } else {
80
+ modulePath = `/src/api${route.filePath.replace(/^src\/api/, "")}`;
81
+ }
82
+ } else {
83
+ // PROD: bundled JS
84
+ modulePath = pathToFileURL(
85
+ `./dist/server/api${route.filePath
86
+ .replace(/^src\/api/, "")
87
+ .replace(/\.ts$/, ".js")}`
88
+ ).href;
89
+ }
90
+
91
+ try {
92
+ const mod = !isProduction
93
+ ? await vite.ssrLoadModule(modulePath)
94
+ : await import(modulePath);
95
+
96
+ const handler = mod[req.method]; // GET, POST, etc.
97
+
98
+ if (typeof handler !== "function") {
99
+ return res.status(405).json({ error: "Method not allowed" });
100
+ }
101
+
102
+ const result = await handler(req, res, params);
103
+
104
+ if (!res.headersSent) res.json(result);
105
+ } catch (err) {
106
+ console.error("API load error:", err);
107
+ res.status(500).json({ error: "Internal Server Error" });
108
+ }
109
+ });
110
+
111
+ // SSR HTML
112
+ app.use(/.*/, async (req, res) => {
113
+ try {
114
+ let url = req.originalUrl;
115
+
116
+ const routeMatch = routeMatcher(serverRoutes, url);
117
+
118
+ if (!routeMatch) {
119
+ return res.status(404).send("Not found");
120
+ }
121
+
122
+ // Dynamicly import the file
123
+ const mod = await vite.ssrLoadModule(routeMatch.route.modulePath);
124
+
125
+ // Call load if it exist
126
+ let initialData = {};
127
+ if (mod.load) {
128
+ initialData = await mod.load(routeMatch.params);
129
+ }
130
+
131
+ let template;
132
+ let render;
133
+ if (!isProduction) {
134
+ template = await fs.readFile(
135
+ path.join(projectRoot, "index.html"),
136
+ "utf-8"
137
+ );
138
+ template = await vite.transformIndexHtml(url, template);
139
+ render = (await vite.ssrLoadModule("/.aaex/framework/entry-server.tsx"))
140
+ .render;
141
+ } else {
142
+ template = templateHtml;
143
+ render = (
144
+ await import(path.join(projectRoot, "dist/server/entry-server.js"))
145
+ ).render;
146
+ }
147
+
148
+ const rendered = await render(url, initialData);
149
+
150
+ function XSSPrevention(unsafeString) {
151
+ return unsafeString.replace(/</g, "//u003c");
152
+ // .replace(/>/g, "&gt")
153
+ // .replace(/'/g, "&#39")
154
+ // .replace(/"/g, "&#34");
155
+ }
156
+
157
+ const serializedData = JSON.stringify(initialData);
158
+ const safeData = XSSPrevention(serializedData);
159
+
160
+ const html = template
161
+ .replace("<!--app-head-->", rendered.head ?? "")
162
+ .replace("<!--app-html-->", rendered.html ?? "")
163
+ .replace(
164
+ "<!--initial-data-->",
165
+ `<script>window.__INITIAL_DATA__ = ${safeData}</script>`
166
+ );
167
+
168
+ res.status(200).set({ "Content-Type": "text/html" }).send(html);
169
+ } catch (e) {
170
+ vite?.ssrFixStacktrace?.(e);
171
+ console.error(e);
172
+ res.status(500).send(e.stack);
173
+ }
174
+ });
175
+
176
+ // Start server
177
+ app.listen(port, () => {
178
+ console.log(`Server started at http://localhost:${port}`);
179
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "framework": "aaexjs",
3
+ "version": "1.0.0"
4
+ }