@feardread/fear 2.0.6 → 2.0.7
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/FEAR.js +10 -2
- package/FEARServer.js +1 -5
- package/controllers/crud/crud.js +22 -22
- package/libs/cloud/index.js +102 -94
- package/libs/greenlock/setup.js +1 -1
- package/models/blog.js +6 -14
- package/models/brand.js +0 -6
- package/models/product.js +6 -1
- package/package.json +1 -1
package/FEAR.js
CHANGED
|
@@ -15,6 +15,15 @@ module.exports = FEAR = (() => {
|
|
|
15
15
|
const DEFAULT_JSON_LIMIT = '10mb';
|
|
16
16
|
const DEFAULT_ROUTE_PATH = '/fear/api';
|
|
17
17
|
const AGENT_ROUTE_PATH = '/fear/api/agent';
|
|
18
|
+
const FEAR_LOGO=`
|
|
19
|
+
_________________________
|
|
20
|
+
| ___| ____| / \ | _ \
|
|
21
|
+
| |_ | _| / _ \ | |_) |
|
|
22
|
+
| _| | |___ / ___ \| _ <
|
|
23
|
+
|_| |_____/_/ \_\_| \_\
|
|
24
|
+
----The Quieter we become----
|
|
25
|
+
-- the more we are able to hear.--
|
|
26
|
+
`
|
|
18
27
|
|
|
19
28
|
// Constructor function
|
|
20
29
|
const FEAR = function (config) {
|
|
@@ -47,7 +56,6 @@ module.exports = FEAR = (() => {
|
|
|
47
56
|
this.setupRoutes();
|
|
48
57
|
};
|
|
49
58
|
|
|
50
|
-
|
|
51
59
|
FEAR.prototype = {
|
|
52
60
|
constructor: FEAR,
|
|
53
61
|
getStripe() {
|
|
@@ -114,7 +122,7 @@ module.exports = FEAR = (() => {
|
|
|
114
122
|
this.db = require("./libs/db");
|
|
115
123
|
this.handler = require("./libs/handler");
|
|
116
124
|
this.validator = require("./libs/validator");
|
|
117
|
-
this.logo =
|
|
125
|
+
this.logo = FEAR_LOGO;
|
|
118
126
|
this.origins = this.getAllowedOrigins();
|
|
119
127
|
},
|
|
120
128
|
|
package/FEARServer.js
CHANGED
|
@@ -278,10 +278,6 @@ const FearServer = (function () {
|
|
|
278
278
|
return https.createServer(httpsOptions, this.fear.getApp());
|
|
279
279
|
},
|
|
280
280
|
|
|
281
|
-
/**
|
|
282
|
-
* Create HTTP to HTTPS redirect server
|
|
283
|
-
* @private
|
|
284
|
-
*/
|
|
285
281
|
_createHttpRedirectServer(httpsPort) {
|
|
286
282
|
const redirectApp = express();
|
|
287
283
|
|
|
@@ -438,7 +434,7 @@ const FearServer = (function () {
|
|
|
438
434
|
};
|
|
439
435
|
|
|
440
436
|
this.fear.getApp().use((req, res, next) => {
|
|
441
|
-
res.header('Access-Control-Allow-Origin',
|
|
437
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
442
438
|
res.header('Access-Control-Allow-Methods', defaultOptions.methods.join(', '));
|
|
443
439
|
res.header('Access-Control-Allow-Headers', defaultOptions.allowedHeaders.join(', '));
|
|
444
440
|
|
package/controllers/crud/crud.js
CHANGED
|
@@ -34,7 +34,7 @@ const sanitizeUpdateData = (data) => {
|
|
|
34
34
|
*/
|
|
35
35
|
const processImages = (images) => {
|
|
36
36
|
if (!images) return Promise.resolve(null);
|
|
37
|
-
|
|
37
|
+
console.log('processing images = ', images);
|
|
38
38
|
const imageArray = Array.isArray(images)
|
|
39
39
|
? images
|
|
40
40
|
: images.split(',').map(item => item.trim());
|
|
@@ -132,34 +132,34 @@ exports.read = tryCatch((Model, req, res) => {
|
|
|
132
132
|
*/
|
|
133
133
|
exports.create = tryCatch((Model, req, res) => {
|
|
134
134
|
const documentData = { ...req.body };
|
|
135
|
+
const featured = documentData.featuredImage;
|
|
136
|
+
if (featured && documentData.images) documentData.images.push(featured);
|
|
137
|
+
let imagePromise;
|
|
138
|
+
|
|
139
|
+
if (documentData.images && documentData.images.length !== 0) {
|
|
140
|
+
imagePromise = processImages(documentData.images);
|
|
141
|
+
} else {
|
|
142
|
+
imagePromise = Promise.resolve(null);
|
|
143
|
+
}
|
|
135
144
|
|
|
136
|
-
return
|
|
137
|
-
|
|
145
|
+
return imagePromise
|
|
146
|
+
.then((imageLinks) => {
|
|
138
147
|
if (imageLinks) {
|
|
139
148
|
documentData.images = imageLinks;
|
|
140
149
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const document = new Model(documentData);
|
|
144
|
-
|
|
150
|
+
|
|
151
|
+
const document = new Model(documentData);
|
|
145
152
|
return document.save();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return res.status(201)
|
|
149
|
-
result,
|
|
150
|
-
success: true,
|
|
151
|
-
message: `Document created successfully in ${Model.modelName} collection`
|
|
153
|
+
})
|
|
154
|
+
.then((result) => {
|
|
155
|
+
return res.status(201)
|
|
156
|
+
.json({ result, success: true, message: `Document created successfully in ${Model.modelName} collection`
|
|
152
157
|
});
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
})
|
|
159
|
+
.catch((error) => {
|
|
155
160
|
console.error('Error creating document:', error);
|
|
156
|
-
return res.status(500).json({
|
|
157
|
-
|
|
158
|
-
success: false,
|
|
159
|
-
message: "Error creating document",
|
|
160
|
-
error: error.message
|
|
161
|
-
});
|
|
162
|
-
});
|
|
161
|
+
return res.status(500).json({ result: null, success: false, message: "Error creating document", error: error.message });
|
|
162
|
+
})
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
/**
|
package/libs/cloud/index.js
CHANGED
|
@@ -26,21 +26,21 @@ const convertToBase64 = (file) => {
|
|
|
26
26
|
* @param {string} file - Base64 encoded file or file path
|
|
27
27
|
* @returns {Promise<Object>} Avatar object with public_id and url
|
|
28
28
|
*/
|
|
29
|
-
const uploadAvatar =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const uploadAvatar = (file) => {
|
|
30
|
+
return cloudinary.uploader.upload(file, {
|
|
31
|
+
folder: "avatar",
|
|
32
|
+
width: 150,
|
|
33
|
+
crop: "scale",
|
|
34
|
+
})
|
|
35
|
+
.then((result) => {
|
|
36
|
+
return {
|
|
37
|
+
public_id: result.public_id,
|
|
38
|
+
url: result.secure_url,
|
|
39
|
+
};
|
|
40
|
+
})
|
|
41
|
+
.catch((error) => {
|
|
42
|
+
throw new Error(`Avatar upload failed: ${error.message}`);
|
|
35
43
|
});
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
public_id: result.public_id,
|
|
39
|
-
url: result.secure_url,
|
|
40
|
-
};
|
|
41
|
-
} catch (error) {
|
|
42
|
-
throw new Error(`Avatar upload failed: ${error.message}`);
|
|
43
|
-
}
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -49,41 +49,49 @@ const uploadAvatar = async (file) => {
|
|
|
49
49
|
* @param {number} chunkSize - Number of files to upload concurrently
|
|
50
50
|
* @returns {Promise<Object[]>} Array of image objects with public_id and url
|
|
51
51
|
*/
|
|
52
|
-
const uploadImages =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
52
|
+
const uploadImages = (files, chunkSize = 3) => {
|
|
53
|
+
// Normalize input to array
|
|
54
|
+
const imageArray = Array.isArray(files) ? [...files] : [files];
|
|
55
|
+
|
|
56
|
+
if (imageArray.length === 0) {
|
|
57
|
+
return Promise.resolve([]);
|
|
58
|
+
}
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
const imageLinks = [];
|
|
61
|
+
|
|
62
|
+
// Create a promise chain for sequential chunk processing
|
|
63
|
+
let promiseChain = Promise.resolve();
|
|
64
|
+
|
|
65
|
+
// Process images in chunks to avoid overwhelming the API
|
|
66
|
+
for (let i = 0; i < imageArray.length; i += chunkSize) {
|
|
67
|
+
const chunk = imageArray.slice(i, i + chunkSize);
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
for (let i = 0; i < imageArray.length; i += chunkSize) {
|
|
65
|
-
const chunk = imageArray.slice(i, i + chunkSize);
|
|
66
|
-
|
|
69
|
+
promiseChain = promiseChain.then(() => {
|
|
67
70
|
const uploadPromises = chunk.map((image) =>
|
|
68
71
|
cloudinary.uploader.upload(image, {
|
|
69
72
|
folder: "products",
|
|
70
73
|
})
|
|
71
74
|
);
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return imageLinks;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
throw new Error(`Image upload failed: ${error}`);
|
|
76
|
+
return Promise.all(uploadPromises)
|
|
77
|
+
.then((results) => {
|
|
78
|
+
const chunkResults = results.map((result) => ({
|
|
79
|
+
public_id: result.public_id,
|
|
80
|
+
url: result.secure_url,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
imageLinks.push(...chunkResults);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
return promiseChain
|
|
89
|
+
.then(() => {
|
|
90
|
+
return imageLinks;
|
|
91
|
+
})
|
|
92
|
+
.catch((error) => {
|
|
93
|
+
throw new Error('Image upload failed: ', error);
|
|
94
|
+
});
|
|
87
95
|
};
|
|
88
96
|
|
|
89
97
|
/**
|
|
@@ -130,56 +138,55 @@ const uploadPhoto = multer({
|
|
|
130
138
|
* @param {Object} res - Express response object
|
|
131
139
|
* @param {Function} next - Express next function
|
|
132
140
|
*/
|
|
133
|
-
const resizeImages =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Clean up original file
|
|
163
|
-
if (fs.existsSync(originalPath)) {
|
|
164
|
-
fs.unlinkSync(originalPath);
|
|
165
|
-
}
|
|
166
|
-
} catch (imageError) {
|
|
167
|
-
console.error(`Failed to process image ${file.filename}:`, imageError);
|
|
168
|
-
// Clean up files on error
|
|
169
|
-
[originalPath, targetPath].forEach(filePath => {
|
|
170
|
-
if (fs.existsSync(filePath)) {
|
|
171
|
-
fs.unlinkSync(filePath);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
throw imageError;
|
|
141
|
+
const resizeImages = (req, res, next) => {
|
|
142
|
+
// Skip if no files uploaded or no directory specified
|
|
143
|
+
if (!req.files || !req.directory) {
|
|
144
|
+
return next();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Ensure directory exists
|
|
148
|
+
const targetDir = path.join("public/images", req.directory);
|
|
149
|
+
if (!fs.existsSync(targetDir)) {
|
|
150
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Process all files concurrently
|
|
154
|
+
const processPromises = req.files.map((file) => {
|
|
155
|
+
const originalPath = file.path;
|
|
156
|
+
const targetPath = path.join(targetDir, file.filename);
|
|
157
|
+
|
|
158
|
+
return sharp(originalPath)
|
|
159
|
+
.resize(300, 300, {
|
|
160
|
+
fit: 'cover',
|
|
161
|
+
position: 'center'
|
|
162
|
+
})
|
|
163
|
+
.jpeg({ quality: 90 })
|
|
164
|
+
.toFile(targetPath)
|
|
165
|
+
.then(() => {
|
|
166
|
+
// Clean up original file
|
|
167
|
+
if (fs.existsSync(originalPath)) {
|
|
168
|
+
fs.unlinkSync(originalPath);
|
|
175
169
|
}
|
|
176
170
|
})
|
|
177
|
-
|
|
171
|
+
.catch((imageError) => {
|
|
172
|
+
console.error(`Failed to process image ${file.filename}:`, imageError);
|
|
173
|
+
// Clean up files on error
|
|
174
|
+
[originalPath, targetPath].forEach(filePath => {
|
|
175
|
+
if (fs.existsSync(filePath)) {
|
|
176
|
+
fs.unlinkSync(filePath);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
throw imageError;
|
|
180
|
+
});
|
|
181
|
+
});
|
|
178
182
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
Promise.all(processPromises)
|
|
184
|
+
.then(() => {
|
|
185
|
+
next();
|
|
186
|
+
})
|
|
187
|
+
.catch((error) => {
|
|
188
|
+
next(new Error(`Image resize failed: ${error.message}`));
|
|
189
|
+
});
|
|
183
190
|
};
|
|
184
191
|
|
|
185
192
|
/**
|
|
@@ -187,13 +194,14 @@ const resizeImages = async (req, res, next) => {
|
|
|
187
194
|
* @param {string} publicId - Public ID of the image to delete
|
|
188
195
|
* @returns {Promise<Object>} Deletion result
|
|
189
196
|
*/
|
|
190
|
-
const deleteImage =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
const deleteImage = (publicId) => {
|
|
198
|
+
return cloudinary.uploader.destroy(publicId)
|
|
199
|
+
.then((result) => {
|
|
200
|
+
return result;
|
|
201
|
+
})
|
|
202
|
+
.catch((error) => {
|
|
203
|
+
throw new Error(`Image deletion failed: ${error.message}`);
|
|
204
|
+
});
|
|
197
205
|
};
|
|
198
206
|
|
|
199
207
|
/**
|
package/libs/greenlock/setup.js
CHANGED
package/models/blog.js
CHANGED
|
@@ -3,13 +3,9 @@ const slugify = require("slugify");
|
|
|
3
3
|
|
|
4
4
|
const blogSchema = new mongoose.Schema(
|
|
5
5
|
{
|
|
6
|
-
|
|
7
|
-
title: {
|
|
8
|
-
type: String,
|
|
6
|
+
title: {type: String, trim: true, index: true,
|
|
9
7
|
required: [true, "Blog title is required"],
|
|
10
|
-
|
|
11
|
-
maxlength: [200, "Title cannot exceed 200 characters"],
|
|
12
|
-
index: true
|
|
8
|
+
maxlength: [300, "Title cannot exceed 200 characters"],
|
|
13
9
|
},
|
|
14
10
|
|
|
15
11
|
slug: {
|
|
@@ -22,13 +18,13 @@ const blogSchema = new mongoose.Schema(
|
|
|
22
18
|
subtitle: {
|
|
23
19
|
type: String,
|
|
24
20
|
trim: true,
|
|
25
|
-
maxlength: [
|
|
21
|
+
maxlength: [350, "Subtitle cannot exceed 250 characters"]
|
|
26
22
|
},
|
|
27
23
|
|
|
28
24
|
excerpt: {
|
|
29
25
|
type: String,
|
|
30
26
|
trim: true,
|
|
31
|
-
maxlength: [
|
|
27
|
+
maxlength: [600, "Excerpt cannot exceed 500 characters"]
|
|
32
28
|
},
|
|
33
29
|
|
|
34
30
|
content: {
|
|
@@ -43,10 +39,6 @@ const blogSchema = new mongoose.Schema(
|
|
|
43
39
|
images: [{
|
|
44
40
|
public_id: { type: String },
|
|
45
41
|
url: { type: String },
|
|
46
|
-
secure_url: { type: String },
|
|
47
|
-
alt: { type: String },
|
|
48
|
-
caption: { type: String },
|
|
49
|
-
order: { type: Number, default: 0 }
|
|
50
42
|
}],
|
|
51
43
|
|
|
52
44
|
video: {
|
|
@@ -285,11 +277,11 @@ const blogSchema = new mongoose.Schema(
|
|
|
285
277
|
seo: {
|
|
286
278
|
metaTitle: {
|
|
287
279
|
type: String,
|
|
288
|
-
maxlength: [
|
|
280
|
+
maxlength: [170, "Meta title cannot exceed 170 characters"]
|
|
289
281
|
},
|
|
290
282
|
metaDescription: {
|
|
291
283
|
type: String,
|
|
292
|
-
maxlength: [
|
|
284
|
+
maxlength: [260, "Meta description cannot exceed 260 characters"]
|
|
293
285
|
},
|
|
294
286
|
metaKeywords: [{ type: String }],
|
|
295
287
|
focusKeyword: { type: String },
|
package/models/brand.js
CHANGED
|
@@ -43,7 +43,6 @@ brandSchema.index({ createdAt: -1 });
|
|
|
43
43
|
// Text index for search
|
|
44
44
|
brandSchema.index({
|
|
45
45
|
name: "text",
|
|
46
|
-
title: "text",
|
|
47
46
|
tags: "text"
|
|
48
47
|
});
|
|
49
48
|
|
|
@@ -83,11 +82,6 @@ brandSchema.pre("save", async function(next) {
|
|
|
83
82
|
this.active = this.isActive;
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
// Set title to name if not provided
|
|
87
|
-
if (!this.title) {
|
|
88
|
-
this.title = this.name;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
85
|
next();
|
|
92
86
|
});
|
|
93
87
|
|
package/models/product.js
CHANGED