qumra-engine 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.
- package/lib/index.js +171 -0
- package/lib/utils/CookieParser.js +37 -0
- package/package.json +16 -0
- package/views/error.njk +48 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
const nunjucks = require("nunjucks");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const Fingerprint = require("express-fingerprint");
|
|
4
|
+
const CookieParser = require("./utils/CookieParser");
|
|
5
|
+
const fs = require("fs")
|
|
6
|
+
function extractLanguageAndPath(path) {
|
|
7
|
+
// Regular expression to match the /[lang]/[path] pattern
|
|
8
|
+
const langPattern = /^\/([a-z]{2})\/(.*)$/;
|
|
9
|
+
const rootPattern = /^\/([a-z]{2})$/;
|
|
10
|
+
|
|
11
|
+
let result = {
|
|
12
|
+
language: 'ar', // Default language is Arabic
|
|
13
|
+
path: null
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Check if the path matches the /[lang]/[path] pattern
|
|
17
|
+
let match = path.match(langPattern);
|
|
18
|
+
if (match) {
|
|
19
|
+
result.language = match[1]; // Extract language
|
|
20
|
+
result.path = `/${match[2]}`; // Extract path
|
|
21
|
+
} else {
|
|
22
|
+
// Check if the path matches the /[lang] pattern
|
|
23
|
+
match = path.match(rootPattern);
|
|
24
|
+
if (match) {
|
|
25
|
+
result.language = match[1]; // Extract language
|
|
26
|
+
result.path = '/'; // Root path
|
|
27
|
+
} else {
|
|
28
|
+
// If the path does not match any pattern, consider the entire path as path and language as default
|
|
29
|
+
result.path = path;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
function createNunjucksMiddleware(app, options = {}) {
|
|
36
|
+
const viewsDirectory = options.viewsDirectory || path.join(__dirname, "../views");
|
|
37
|
+
const localesDirectory = path.join(path.dirname(viewsDirectory), 'locales'); // الرجوع خطوة واحدة إلى مستوى أعلى ثم الوصول إلى مجلد locales
|
|
38
|
+
|
|
39
|
+
// إعداد Nunjucks
|
|
40
|
+
const env = nunjucks.configure(viewsDirectory, {
|
|
41
|
+
autoescape: true,
|
|
42
|
+
express: app,
|
|
43
|
+
watch: options.watch || false,
|
|
44
|
+
noCache: options.noCache || false,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const defaultFilters = {
|
|
48
|
+
upperCase: (str) => str.toUpperCase(),
|
|
49
|
+
reverse: (str) => str.split("").reverse().join(""),
|
|
50
|
+
capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(),
|
|
51
|
+
formatCurrency: (amount, symbol = "$") => symbol + parseFloat(amount).toFixed(2),
|
|
52
|
+
money: () => "ToDo",
|
|
53
|
+
date: (date, format = "YYYY-MM-DD") => new Date(date).toISOString().split("T")[0],
|
|
54
|
+
applyDiscount: (price, discountPercent) => price - price * (discountPercent / 100),
|
|
55
|
+
addVAT: (price, vatPercent = 15) => price + price * (vatPercent / 100),
|
|
56
|
+
pluralize: (quantity, singular, plural) => quantity === 1 ? singular : plural,
|
|
57
|
+
truncate: (str, length = 100) => str.length > length ? str.slice(0, length) + "..." : str,
|
|
58
|
+
trimSpaces: (str) => str.trim(),
|
|
59
|
+
isEmpty: (str) => !str || str.trim().length === 0,
|
|
60
|
+
slugify: (str) => str.toLowerCase().replace(/\s+/g, "-").replace(/[^\w-]+/g, ""),
|
|
61
|
+
formatNumber: (num) => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","),
|
|
62
|
+
assets: (path) => env.getGlobal("assets"),
|
|
63
|
+
cdn: (path) => env.getGlobal("cdn"),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// إضافة الفلترات المدمجة إلى بيئة Nunjucks
|
|
67
|
+
for (const filterName in defaultFilters) {
|
|
68
|
+
env.addFilter(filterName, defaultFilters[filterName]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const locales = {};
|
|
72
|
+
fs.readdirSync(localesDirectory).forEach(file => {
|
|
73
|
+
if (file.endsWith('.json')) {
|
|
74
|
+
const localeName = path.basename(file, '.json');
|
|
75
|
+
locales[localeName] = require(path.join(localesDirectory, file));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const globalVars = {};
|
|
82
|
+
|
|
83
|
+
app.use(async (req, res, next) => {
|
|
84
|
+
const { originalUrl } = req;
|
|
85
|
+
const newPath = extractLanguageAndPath(originalUrl);
|
|
86
|
+
globalVars['lang'] = newPath.language;
|
|
87
|
+
next();
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
env.addGlobal("set", function (key, value) {
|
|
91
|
+
globalVars[key] = value;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
env.addGlobal("get", function (key, defaultValue = null) {
|
|
95
|
+
return globalVars[key] || defaultValue;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
function getNestedValue(obj, key) {
|
|
99
|
+
return key.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
env.addGlobal("t", function (key, defaultValue = '') {
|
|
103
|
+
console.log("🚀 ~ globalVars:", globalVars)
|
|
104
|
+
const lang = globalVars['lang'] || 'ar';
|
|
105
|
+
const locale = locales[lang];
|
|
106
|
+
return locale ? getNestedValue(locale, key) || defaultValue : defaultValue;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
app.use(
|
|
110
|
+
Fingerprint({
|
|
111
|
+
parameters: [
|
|
112
|
+
Fingerprint.useragent,
|
|
113
|
+
Fingerprint.acceptHeaders,
|
|
114
|
+
Fingerprint.geoip,
|
|
115
|
+
function (next) {
|
|
116
|
+
next(null, { param1: "value1" });
|
|
117
|
+
},
|
|
118
|
+
function (next) {
|
|
119
|
+
next(null, { param2: "value2" });
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
app.use(async (req, res, next) => {
|
|
126
|
+
const { originalUrl, protocol } = req;
|
|
127
|
+
const fullUrl = `${protocol}://${req.get("host")}${originalUrl}`;
|
|
128
|
+
const url = `https://api.qumra.cloud/templateEngine/v1/render?path=${encodeURIComponent(fullUrl)}`;
|
|
129
|
+
const headers = {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
secretKey: options.secretKey,
|
|
132
|
+
fingerprint: req.fingerprint.hash,
|
|
133
|
+
"User-Agent": req.get("User-Agent") || "",
|
|
134
|
+
Referer: req.get("Referer") || "",
|
|
135
|
+
};
|
|
136
|
+
const config = { headers };
|
|
137
|
+
|
|
138
|
+
const response = await fetch(url, config);
|
|
139
|
+
const resHeaders = response.headers.getSetCookie();
|
|
140
|
+
if (resHeaders) {
|
|
141
|
+
resHeaders.forEach((header) => {
|
|
142
|
+
const parsedCookies = CookieParser(header);
|
|
143
|
+
Object.keys(parsedCookies).forEach((name) => {
|
|
144
|
+
const { value, attributes } = parsedCookies[name];
|
|
145
|
+
res.cookie(name, value, attributes);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
const { global } = data;
|
|
152
|
+
for (const key in global) {
|
|
153
|
+
if (Object.hasOwnProperty.call(global, key)) {
|
|
154
|
+
env.addGlobal(key, global[key]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
next();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return async (req, res, next) => {
|
|
162
|
+
res.render("index.njk", {
|
|
163
|
+
title: "Welcome",
|
|
164
|
+
message: "Hello from Nunjucks with custom middleware!",
|
|
165
|
+
cdn: "https://your-cdn-url.com/",
|
|
166
|
+
fingerprint: req.fingerprint,
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = createNunjucksMiddleware;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const CookieParser = (setCookieHeader) => {
|
|
2
|
+
const cookies = {};
|
|
3
|
+
|
|
4
|
+
// تقسيم الكوكيز بناءً على الفواصل، مع التعامل مع الفواصل داخل القيم
|
|
5
|
+
const cookiePairs = setCookieHeader.split(/,(?![^\(\)]+\))/);
|
|
6
|
+
|
|
7
|
+
cookiePairs.forEach(cookieStr => {
|
|
8
|
+
const parts = cookieStr.split(';');
|
|
9
|
+
const [nameValue] = parts;
|
|
10
|
+
const [name, value] = nameValue.split('=');
|
|
11
|
+
|
|
12
|
+
if (name && value) {
|
|
13
|
+
// إزالة المسافات البيضاء
|
|
14
|
+
const trimmedName = name.trim();
|
|
15
|
+
const trimmedValue = value.trim();
|
|
16
|
+
|
|
17
|
+
// تهيئة الكائن للكوكي إذا لم يكن موجوداً
|
|
18
|
+
if (!cookies[trimmedName]) {
|
|
19
|
+
cookies[trimmedName] = {
|
|
20
|
+
value: trimmedValue,
|
|
21
|
+
attributes: {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// إضافة العلامات الأخرى
|
|
26
|
+
for (let i = 1; i < parts.length; i++) {
|
|
27
|
+
const [attributeName, attributeValue] = parts[i].split('=');
|
|
28
|
+
if (attributeName) {
|
|
29
|
+
cookies[trimmedName].attributes[attributeName.trim()] = attributeValue ? attributeValue.trim() : true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return cookies;
|
|
36
|
+
}
|
|
37
|
+
module.exports = CookieParser
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qumra-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "lib/index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"description": "",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"express-fingerprint": "^1.2.2",
|
|
14
|
+
"nunjucks": "^3.2.4"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/views/error.njk
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!-- 404.njk -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>404 - Page Not Found</title>
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: Arial, sans-serif;
|
|
11
|
+
text-align: center;
|
|
12
|
+
background-color: #f4f4f4;
|
|
13
|
+
color: #333;
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding: 0;
|
|
16
|
+
}
|
|
17
|
+
.container {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
align-items: center;
|
|
22
|
+
height: 100vh;
|
|
23
|
+
}
|
|
24
|
+
h1 {
|
|
25
|
+
font-size: 4rem;
|
|
26
|
+
margin: 0;
|
|
27
|
+
}
|
|
28
|
+
p {
|
|
29
|
+
font-size: 1.2rem;
|
|
30
|
+
margin: 1rem 0;
|
|
31
|
+
}
|
|
32
|
+
a {
|
|
33
|
+
color: #007bff;
|
|
34
|
+
text-decoration: none;
|
|
35
|
+
}
|
|
36
|
+
a:hover {
|
|
37
|
+
text-decoration: underline;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<div class="container">
|
|
43
|
+
<h1>404</h1>
|
|
44
|
+
<p>Oops! The page you're looking for doesn't exist.</p>
|
|
45
|
+
<p><a href="/">Go back to the homepage</a></p>
|
|
46
|
+
</div>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|