markdown-notes-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.
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Upload Routes
3
+ * Handles image and video uploads
4
+ */
5
+
6
+ const express = require('express');
7
+ const fileUpload = require('express-fileupload');
8
+ const router = express.Router();
9
+
10
+ // File upload middleware
11
+ router.use(fileUpload());
12
+
13
+ // Upload image (multipart form)
14
+ router.post('/upload-image', async (req, res) => {
15
+ try {
16
+ if (!req.files || !req.files.image) {
17
+ return res.status(400).json({ error: 'No image file provided' });
18
+ }
19
+
20
+ const imageFile = req.files.image;
21
+ const folder = req.body.folder || '';
22
+
23
+ const { storageClient } = req.notesEngine;
24
+ const imageUrl = await storageClient.uploadImage(
25
+ imageFile.data,
26
+ imageFile.name,
27
+ folder
28
+ );
29
+
30
+ res.json({ imageUrl });
31
+ } catch (error) {
32
+ console.error('Error uploading image:', error);
33
+ res.status(500).json({ error: 'Failed to upload image' });
34
+ }
35
+ });
36
+
37
+ // Upload image (base64)
38
+ router.post('/upload-image-base64', async (req, res) => {
39
+ try {
40
+ const { image, filename, folder = '' } = req.body;
41
+
42
+ if (!image || !filename) {
43
+ return res.status(400).json({ error: 'Image data and filename are required' });
44
+ }
45
+
46
+ // Remove data URL prefix if present
47
+ const base64Data = image.replace(/^data:image\/\w+;base64,/, '');
48
+ const buffer = Buffer.from(base64Data, 'base64');
49
+
50
+ const { storageClient } = req.notesEngine;
51
+ const imageUrl = await storageClient.uploadImage(buffer, filename, folder);
52
+
53
+ res.json({ imageUrl });
54
+ } catch (error) {
55
+ console.error('Error uploading image:', error);
56
+ res.status(500).json({ error: 'Failed to upload image' });
57
+ }
58
+ });
59
+
60
+ // Upload video
61
+ router.post('/upload-video', async (req, res) => {
62
+ try {
63
+ if (!req.files || !req.files.video) {
64
+ return res.status(400).json({ error: 'No video file provided' });
65
+ }
66
+
67
+ const videoFile = req.files.video;
68
+ const folder = req.body.folder || '';
69
+
70
+ const { storageClient } = req.notesEngine;
71
+ const videoUrl = await storageClient.uploadVideo(
72
+ videoFile.data,
73
+ videoFile.name,
74
+ folder
75
+ );
76
+
77
+ res.json({ videoUrl });
78
+ } catch (error) {
79
+ console.error('Error uploading video:', error);
80
+ res.status(500).json({ error: 'Failed to upload video' });
81
+ }
82
+ });
83
+
84
+ // Delete image
85
+ router.delete('/image', async (req, res) => {
86
+ try {
87
+ const { imageUrl } = req.body;
88
+
89
+ if (!imageUrl) {
90
+ return res.status(400).json({ error: 'Image URL is required' });
91
+ }
92
+
93
+ const { storageClient } = req.notesEngine;
94
+ await storageClient.deleteFile(imageUrl);
95
+
96
+ res.json({ success: true });
97
+ } catch (error) {
98
+ console.error('Error deleting image:', error);
99
+ res.status(500).json({ error: 'Failed to delete image' });
100
+ }
101
+ });
102
+
103
+ // Delete video
104
+ router.delete('/video', async (req, res) => {
105
+ try {
106
+ const { videoUrl } = req.body;
107
+
108
+ if (!videoUrl) {
109
+ return res.status(400).json({ error: 'Video URL is required' });
110
+ }
111
+
112
+ const { storageClient } = req.notesEngine;
113
+ await storageClient.deleteFile(videoUrl);
114
+
115
+ res.json({ success: true });
116
+ } catch (error) {
117
+ console.error('Error deleting video:', error);
118
+ res.status(500).json({ error: 'Failed to delete video' });
119
+ }
120
+ });
121
+
122
+ module.exports = router;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Storage Client
3
+ * Handles file uploads to R2 or S3
4
+ */
5
+
6
+ const { S3Client, PutObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
7
+
8
+ class StorageClient {
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.publicUrl = config.publicUrl;
12
+
13
+ const s3Config = {
14
+ region: config.type === 'r2' ? 'auto' : (config.region || 'us-east-1'),
15
+ credentials: {
16
+ accessKeyId: config.accessKeyId,
17
+ secretAccessKey: config.secretAccessKey
18
+ }
19
+ };
20
+
21
+ // R2-specific endpoint
22
+ if (config.type === 'r2') {
23
+ s3Config.endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
24
+ }
25
+
26
+ this.s3Client = new S3Client(s3Config);
27
+ this.bucketName = config.bucketName;
28
+ }
29
+
30
+ /**
31
+ * Upload an image file
32
+ * @param {Buffer} fileBuffer - File buffer
33
+ * @param {string} filename - Original filename
34
+ * @param {string} [folder=''] - Optional folder path
35
+ * @returns {Promise<string>} Public URL of uploaded file
36
+ */
37
+ async uploadImage(fileBuffer, filename, folder = '') {
38
+ const timestamp = Date.now();
39
+ const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_');
40
+ const key = folder
41
+ ? `images/${folder}/${timestamp}-${sanitizedFilename}`
42
+ : `images/${timestamp}-${sanitizedFilename}`;
43
+
44
+ await this.s3Client.send(new PutObjectCommand({
45
+ Bucket: this.bucketName,
46
+ Key: key,
47
+ Body: fileBuffer,
48
+ ContentType: this._getContentType(filename)
49
+ }));
50
+
51
+ return `${this.publicUrl}/${key}`;
52
+ }
53
+
54
+ /**
55
+ * Upload a video file
56
+ * @param {Buffer} fileBuffer - File buffer
57
+ * @param {string} filename - Original filename
58
+ * @param {string} [folder=''] - Optional folder path
59
+ * @returns {Promise<string>} Public URL of uploaded file
60
+ */
61
+ async uploadVideo(fileBuffer, filename, folder = '') {
62
+ const timestamp = Date.now();
63
+ const sanitizedFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_');
64
+ const key = folder
65
+ ? `videos/${folder}/${timestamp}-${sanitizedFilename}`
66
+ : `videos/${timestamp}-${sanitizedFilename}`;
67
+
68
+ await this.s3Client.send(new PutObjectCommand({
69
+ Bucket: this.bucketName,
70
+ Key: key,
71
+ Body: fileBuffer,
72
+ ContentType: this._getContentType(filename)
73
+ }));
74
+
75
+ return `${this.publicUrl}/${key}`;
76
+ }
77
+
78
+ /**
79
+ * Delete a file
80
+ * @param {string} fileUrl - Public URL of the file
81
+ * @returns {Promise<void>}
82
+ */
83
+ async deleteFile(fileUrl) {
84
+ // Extract key from URL
85
+ const key = fileUrl.replace(this.publicUrl + '/', '');
86
+
87
+ await this.s3Client.send(new DeleteObjectCommand({
88
+ Bucket: this.bucketName,
89
+ Key: key
90
+ }));
91
+ }
92
+
93
+ /**
94
+ * Get content type from filename
95
+ * @private
96
+ */
97
+ _getContentType(filename) {
98
+ const ext = filename.split('.').pop().toLowerCase();
99
+
100
+ const contentTypes = {
101
+ // Images
102
+ jpg: 'image/jpeg',
103
+ jpeg: 'image/jpeg',
104
+ png: 'image/png',
105
+ gif: 'image/gif',
106
+ webp: 'image/webp',
107
+ svg: 'image/svg+xml',
108
+
109
+ // Videos
110
+ mp4: 'video/mp4',
111
+ webm: 'video/webm',
112
+ mov: 'video/quicktime',
113
+ avi: 'video/x-msvideo',
114
+ mkv: 'video/x-matroska'
115
+ };
116
+
117
+ return contentTypes[ext] || 'application/octet-stream';
118
+ }
119
+ }
120
+
121
+ module.exports = { StorageClient };