@jhits/plugin-images 0.0.7 → 0.0.9

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 (102) hide show
  1. package/package.json +8 -9
  2. package/src/api/fallback/route.ts +0 -69
  3. package/src/api/index.ts +0 -10
  4. package/src/api/list/index.ts +0 -96
  5. package/src/api/resolve/route.ts +0 -241
  6. package/src/api/router.ts +0 -85
  7. package/src/api/upload/index.ts +0 -88
  8. package/src/api/uploads/[filename]/route.ts +0 -93
  9. package/src/api-server.ts +0 -11
  10. package/src/assets/noimagefound.jpg +0 -0
  11. package/src/components/BackgroundImage.d.ts +0 -11
  12. package/src/components/BackgroundImage.d.ts.map +0 -1
  13. package/src/components/BackgroundImage.js +0 -35
  14. package/src/components/BackgroundImage.tsx +0 -92
  15. package/src/components/GlobalImageEditor/config.d.ts +0 -9
  16. package/src/components/GlobalImageEditor/config.d.ts.map +0 -1
  17. package/src/components/GlobalImageEditor/config.js +0 -18
  18. package/src/components/GlobalImageEditor/config.ts +0 -21
  19. package/src/components/GlobalImageEditor/eventHandlers.d.ts +0 -20
  20. package/src/components/GlobalImageEditor/eventHandlers.d.ts.map +0 -1
  21. package/src/components/GlobalImageEditor/eventHandlers.js +0 -206
  22. package/src/components/GlobalImageEditor/eventHandlers.ts +0 -267
  23. package/src/components/GlobalImageEditor/imageDetection.d.ts +0 -16
  24. package/src/components/GlobalImageEditor/imageDetection.d.ts.map +0 -1
  25. package/src/components/GlobalImageEditor/imageDetection.js +0 -130
  26. package/src/components/GlobalImageEditor/imageDetection.ts +0 -160
  27. package/src/components/GlobalImageEditor/imageSetup.d.ts +0 -9
  28. package/src/components/GlobalImageEditor/imageSetup.d.ts.map +0 -1
  29. package/src/components/GlobalImageEditor/imageSetup.js +0 -261
  30. package/src/components/GlobalImageEditor/imageSetup.ts +0 -306
  31. package/src/components/GlobalImageEditor/saveLogic.d.ts +0 -26
  32. package/src/components/GlobalImageEditor/saveLogic.d.ts.map +0 -1
  33. package/src/components/GlobalImageEditor/saveLogic.js +0 -99
  34. package/src/components/GlobalImageEditor/saveLogic.ts +0 -133
  35. package/src/components/GlobalImageEditor/stylingDetection.d.ts +0 -9
  36. package/src/components/GlobalImageEditor/stylingDetection.d.ts.map +0 -1
  37. package/src/components/GlobalImageEditor/stylingDetection.js +0 -110
  38. package/src/components/GlobalImageEditor/stylingDetection.ts +0 -122
  39. package/src/components/GlobalImageEditor/transformParsing.d.ts +0 -16
  40. package/src/components/GlobalImageEditor/transformParsing.d.ts.map +0 -1
  41. package/src/components/GlobalImageEditor/transformParsing.js +0 -68
  42. package/src/components/GlobalImageEditor/transformParsing.ts +0 -83
  43. package/src/components/GlobalImageEditor/types.d.ts +0 -36
  44. package/src/components/GlobalImageEditor/types.d.ts.map +0 -1
  45. package/src/components/GlobalImageEditor/types.js +0 -4
  46. package/src/components/GlobalImageEditor/types.ts +0 -39
  47. package/src/components/GlobalImageEditor.d.ts +0 -8
  48. package/src/components/GlobalImageEditor.d.ts.map +0 -1
  49. package/src/components/GlobalImageEditor.js +0 -227
  50. package/src/components/GlobalImageEditor.tsx +0 -327
  51. package/src/components/Image.d.ts +0 -22
  52. package/src/components/Image.d.ts.map +0 -1
  53. package/src/components/Image.js +0 -229
  54. package/src/components/Image.tsx +0 -343
  55. package/src/components/ImageBrowserModal.d.ts +0 -13
  56. package/src/components/ImageBrowserModal.d.ts.map +0 -1
  57. package/src/components/ImageBrowserModal.js +0 -504
  58. package/src/components/ImageBrowserModal.tsx +0 -837
  59. package/src/components/ImageEditor.d.ts +0 -27
  60. package/src/components/ImageEditor.d.ts.map +0 -1
  61. package/src/components/ImageEditor.js +0 -173
  62. package/src/components/ImageEditor.tsx +0 -323
  63. package/src/components/ImageEffectsPanel.tsx +0 -116
  64. package/src/components/ImagePicker.d.ts +0 -3
  65. package/src/components/ImagePicker.d.ts.map +0 -1
  66. package/src/components/ImagePicker.js +0 -143
  67. package/src/components/ImagePicker.tsx +0 -265
  68. package/src/components/ImagesPluginInit.d.ts +0 -24
  69. package/src/components/ImagesPluginInit.d.ts.map +0 -1
  70. package/src/components/ImagesPluginInit.js +0 -28
  71. package/src/components/ImagesPluginInit.tsx +0 -31
  72. package/src/components/index.ts +0 -10
  73. package/src/config.ts +0 -179
  74. package/src/hooks/useImagePicker.d.ts +0 -20
  75. package/src/hooks/useImagePicker.d.ts.map +0 -1
  76. package/src/hooks/useImagePicker.js +0 -322
  77. package/src/hooks/useImagePicker.ts +0 -344
  78. package/src/index.d.ts +0 -23
  79. package/src/index.d.ts.map +0 -1
  80. package/src/index.js +0 -28
  81. package/src/index.server.ts +0 -12
  82. package/src/index.tsx +0 -56
  83. package/src/init.d.ts +0 -33
  84. package/src/init.d.ts.map +0 -1
  85. package/src/init.js +0 -43
  86. package/src/init.tsx +0 -58
  87. package/src/types/index.d.ts +0 -80
  88. package/src/types/index.d.ts.map +0 -1
  89. package/src/types/index.js +0 -4
  90. package/src/types/index.ts +0 -84
  91. package/src/utils/fallback.d.ts +0 -27
  92. package/src/utils/fallback.d.ts.map +0 -1
  93. package/src/utils/fallback.js +0 -63
  94. package/src/utils/fallback.ts +0 -73
  95. package/src/utils/transforms.d.ts +0 -26
  96. package/src/utils/transforms.d.ts.map +0 -1
  97. package/src/utils/transforms.js +0 -38
  98. package/src/utils/transforms.ts +0 -54
  99. package/src/views/ImageManager.d.ts +0 -10
  100. package/src/views/ImageManager.d.ts.map +0 -1
  101. package/src/views/ImageManager.js +0 -9
  102. package/src/views/ImageManager.tsx +0 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhits/plugin-images",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Image management and storage plugin for the JHITS ecosystem",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,13 +17,10 @@
17
17
  "default": "./dist/index.server.js"
18
18
  }
19
19
  },
20
- "scripts": {
21
- "build": "tsc"
22
- },
23
20
  "dependencies": {
24
- "@jhits/plugin-core": "workspace:*",
25
21
  "lucide-react": "^0.564.0",
26
- "mongodb": "^7.1.0"
22
+ "mongodb": "^7.1.0",
23
+ "@jhits/plugin-core": "0.0.5"
27
24
  },
28
25
  "peerDependencies": {
29
26
  "next": ">=15.0.0",
@@ -45,7 +42,9 @@
45
42
  },
46
43
  "files": [
47
44
  "dist",
48
- "src",
49
45
  "package.json"
50
- ]
51
- }
46
+ ],
47
+ "scripts": {
48
+ "build": "tsc"
49
+ }
50
+ }
@@ -1,69 +0,0 @@
1
- /**
2
- * Fallback Image API Route
3
- * Serves the default "image not found" fallback image
4
- */
5
-
6
- import { NextRequest, NextResponse } from 'next/server';
7
- import { readFile } from 'fs/promises';
8
- import path from 'path';
9
-
10
- export async function GET(request: NextRequest) {
11
- try {
12
- // Try multiple possible paths for the fallback image
13
- const possiblePaths = [
14
- // Path in workspace root (monorepo development)
15
- path.join(process.cwd(), 'packages', 'plugin-images', 'src', 'assets', 'noimagefound.jpg'),
16
- // Path in node_modules (production)
17
- path.join(process.cwd(), 'node_modules', '@jhits', 'plugin-images', 'src', 'assets', 'noimagefound.jpg'),
18
- // Fallback to app's public folder if plugin image not found
19
- path.join(process.cwd(), 'public', 'noimagefound.jpg'),
20
- ];
21
-
22
- let filePath: string | null = null;
23
- for (const testPath of possiblePaths) {
24
- try {
25
- await readFile(testPath);
26
- filePath = testPath;
27
- break;
28
- } catch {
29
- // Continue to next path
30
- }
31
- }
32
-
33
- if (!filePath) {
34
- // If no image found, return a simple placeholder SVG
35
- const svgPlaceholder = `<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
36
- <rect width="400" height="300" fill="#f3f4f6"/>
37
- <text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" font-family="Arial" font-size="16" fill="#9ca3af">Image not found</text>
38
- </svg>`;
39
- return new NextResponse(svgPlaceholder, {
40
- headers: {
41
- 'Content-Type': 'image/svg+xml',
42
- 'Cache-Control': 'public, max-age=3600',
43
- },
44
- });
45
- }
46
-
47
- const fileBuffer = await readFile(filePath);
48
-
49
- return new NextResponse(fileBuffer, {
50
- headers: {
51
- 'Content-Type': 'image/jpeg',
52
- 'Cache-Control': 'public, max-age=31536000, immutable',
53
- },
54
- });
55
- } catch (error: any) {
56
- console.error('[FallbackImage] Error serving fallback image:', error);
57
- // Return SVG placeholder on error
58
- const svgPlaceholder = `<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
59
- <rect width="400" height="300" fill="#f3f4f6"/>
60
- <text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" font-family="Arial" font-size="16" fill="#9ca3af">Image not found</text>
61
- </svg>`;
62
- return new NextResponse(svgPlaceholder, {
63
- headers: {
64
- 'Content-Type': 'image/svg+xml',
65
- 'Cache-Control': 'public, max-age=3600',
66
- },
67
- });
68
- }
69
- }
package/src/api/index.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Plugin Images API Exports
3
- */
4
-
5
- export { handleImagesApi } from './router';
6
- export { GET as GET_LIST } from './list';
7
- export { POST as POST_UPLOAD } from './upload';
8
- export { GET as GET_RESOLVE, POST as POST_RESOLVE } from './resolve/route';
9
- export { GET as GET_UPLOADS, DELETE as DELETE_UPLOADS } from './uploads/[filename]/route';
10
-
@@ -1,96 +0,0 @@
1
- /**
2
- * Image List API Handler
3
- * GET /api/plugin-images/list - Returns paginated list of uploaded images
4
- */
5
-
6
- import { NextRequest, NextResponse } from 'next/server';
7
- import { readdir, stat } from 'fs/promises';
8
- import path from 'path';
9
-
10
- const uploadsDir = path.join(process.cwd(), 'data', 'uploads');
11
-
12
- export async function GET(request: NextRequest) {
13
- try {
14
- const { searchParams } = new URL(request.url);
15
- const page = parseInt(searchParams.get('page') || '1');
16
- const limit = parseInt(searchParams.get('limit') || '20');
17
- const search = searchParams.get('search') || '';
18
-
19
- // Read uploads directory
20
- let files: string[] = [];
21
- try {
22
- files = await readdir(uploadsDir);
23
- } catch (error) {
24
- // Directory doesn't exist yet
25
- return NextResponse.json({
26
- images: [],
27
- total: 0,
28
- page: 1,
29
- limit,
30
- });
31
- }
32
-
33
- // Filter image files and get metadata
34
- const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];
35
- const imageFiles = files.filter(file => {
36
- const ext = path.extname(file).toLowerCase();
37
- return imageExtensions.includes(ext);
38
- });
39
-
40
- // Apply search filter if provided
41
- const filteredFiles = search
42
- ? imageFiles.filter(file => file.toLowerCase().includes(search.toLowerCase()))
43
- : imageFiles;
44
-
45
- // Sort by modification time (newest first)
46
- const filesWithStats = await Promise.all(
47
- filteredFiles.map(async (filename) => {
48
- const filePath = path.join(uploadsDir, filename);
49
- const stats = await stat(filePath);
50
- return {
51
- filename,
52
- mtime: stats.mtime,
53
- size: stats.size,
54
- };
55
- })
56
- );
57
-
58
- filesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
59
-
60
- // Paginate
61
- const start = (page - 1) * limit;
62
- const end = start + limit;
63
- const paginatedFiles = filesWithStats.slice(start, end);
64
-
65
- // Build image metadata
66
- const images = paginatedFiles.map(({ filename, mtime, size }) => {
67
- const ext = path.extname(filename).toLowerCase();
68
- const mimeType = ext === '.png' ? 'image/png' :
69
- ext === '.webp' ? 'image/webp' :
70
- ext === '.gif' ? 'image/gif' : 'image/jpeg';
71
-
72
- return {
73
- id: filename,
74
- filename,
75
- url: `/api/uploads/${filename}`,
76
- size,
77
- mimeType,
78
- uploadedAt: mtime.toISOString(),
79
- };
80
- });
81
-
82
- return NextResponse.json({
83
- images,
84
- total: filteredFiles.length,
85
- page,
86
- limit,
87
- });
88
- } catch (error) {
89
- console.error('List images error:', error);
90
- return NextResponse.json(
91
- { error: 'Failed to list images' },
92
- { status: 500 }
93
- );
94
- }
95
- }
96
-
@@ -1,241 +0,0 @@
1
- /**
2
- * Image ID Resolution API Route
3
- * Resolves semantic image IDs to actual filenames
4
- */
5
-
6
- import { NextRequest, NextResponse } from 'next/server';
7
- import { readFile, writeFile, mkdir } from 'fs/promises';
8
- import path from 'path';
9
-
10
- const mappingsPath = path.join(process.cwd(), 'data', 'image-mappings.json');
11
-
12
- // Simple mutex to prevent concurrent writes
13
- let writeLock = false;
14
- const writeQueue: Array<() => void> = [];
15
-
16
- async function acquireLock(): Promise<() => void> {
17
- return new Promise((resolve) => {
18
- if (!writeLock) {
19
- writeLock = true;
20
- resolve(() => {
21
- writeLock = false;
22
- const next = writeQueue.shift();
23
- if (next) next();
24
- });
25
- } else {
26
- writeQueue.push(() => {
27
- writeLock = true;
28
- resolve(() => {
29
- writeLock = false;
30
- const next = writeQueue.shift();
31
- if (next) next();
32
- });
33
- });
34
- }
35
- });
36
- }
37
-
38
- async function readMappingsSafely(): Promise<Record<string, any>> {
39
- try {
40
- const content = await readFile(mappingsPath, 'utf-8');
41
- // Try to parse, if it fails, try to recover
42
- try {
43
- return JSON.parse(content);
44
- } catch (parseError) {
45
- console.error('JSON parse error, attempting recovery:', parseError);
46
- // Try to extract valid JSON by finding the last complete object
47
- const lines = content.split('\n');
48
- let validContent = '';
49
- let braceCount = 0;
50
- let inString = false;
51
- let escapeNext = false;
52
-
53
- for (const line of lines) {
54
- for (let i = 0; i < line.length; i++) {
55
- const char = line[i];
56
- if (escapeNext) {
57
- escapeNext = false;
58
- validContent += char;
59
- continue;
60
- }
61
- if (char === '\\') {
62
- escapeNext = true;
63
- validContent += char;
64
- continue;
65
- }
66
- if (char === '"') {
67
- inString = !inString;
68
- validContent += char;
69
- continue;
70
- }
71
- if (!inString) {
72
- if (char === '{') braceCount++;
73
- if (char === '}') braceCount--;
74
- }
75
- validContent += char;
76
- }
77
- validContent += '\n';
78
- // If we've closed all braces, we might have valid JSON
79
- if (braceCount === 0 && validContent.trim().endsWith('}')) {
80
- try {
81
- return JSON.parse(validContent.trim());
82
- } catch {
83
- // Continue trying
84
- }
85
- }
86
- }
87
- // If recovery failed, return empty object
88
- console.error('Failed to recover JSON, using empty mappings');
89
- return {};
90
- }
91
- } catch (error) {
92
- console.error('Failed to read mappings file:', error);
93
- return {};
94
- }
95
- }
96
-
97
- async function ensureMappingsFile() {
98
- try {
99
- const dir = path.dirname(mappingsPath);
100
- await mkdir(dir, { recursive: true });
101
-
102
- // Check if file exists, if not create empty mappings
103
- try {
104
- await readFile(mappingsPath);
105
- } catch {
106
- await writeFile(mappingsPath, JSON.stringify({}, null, 2));
107
- }
108
- } catch (error) {
109
- // Ignore errors
110
- }
111
- }
112
-
113
- /**
114
- * GET /api/plugin-images/resolve?id=home-about-card
115
- * Resolves a semantic image ID to the actual filename
116
- */
117
- export async function GET(request: NextRequest) {
118
- try {
119
- await ensureMappingsFile();
120
-
121
- const { searchParams } = new URL(request.url);
122
- const id = searchParams.get('id');
123
-
124
- if (!id) {
125
- return NextResponse.json(
126
- { error: 'Missing id parameter' },
127
- { status: 400 }
128
- );
129
- }
130
-
131
- const mappings = await readMappingsSafely();
132
- const mapping = mappings[id];
133
-
134
- if (!mapping) {
135
- return NextResponse.json(
136
- { error: 'Image ID not found', id },
137
- { status: 404 }
138
- );
139
- }
140
-
141
- // Support both old format (just filename string) and new format (object)
142
- const filename = typeof mapping === 'string' ? mapping : mapping.filename;
143
- const brightness = typeof mapping === 'object' ? (mapping.brightness ?? 100) : 100;
144
- const blur = typeof mapping === 'object' ? (mapping.blur ?? 0) : 0;
145
- // Ensure scale is within valid range (0.1 to 5.0), default to 1.0 if invalid
146
- const rawScale = typeof mapping === 'object' ? (mapping.scale ?? 1.0) : 1.0;
147
- const scale = rawScale > 0 && rawScale <= 5.0 ? Math.max(0.1, rawScale) : 1.0;
148
- const positionX = typeof mapping === 'object' ? (mapping.positionX ?? 0) : 0;
149
- const positionY = typeof mapping === 'object' ? (mapping.positionY ?? 0) : 0;
150
-
151
- return NextResponse.json({
152
- id,
153
- filename,
154
- brightness,
155
- blur,
156
- scale,
157
- positionX,
158
- positionY,
159
- url: `/api/uploads/${encodeURIComponent(filename)}`,
160
- });
161
- } catch (error) {
162
- console.error('Resolve error:', error);
163
- return NextResponse.json(
164
- { error: 'Failed to resolve image ID' },
165
- { status: 500 }
166
- );
167
- }
168
- }
169
-
170
- /**
171
- * POST /api/plugin-images/resolve
172
- * Sets or updates the mapping between an ID and filename
173
- * Body: { id: string, filename: string }
174
- */
175
- export async function POST(request: NextRequest) {
176
- try {
177
- await ensureMappingsFile();
178
-
179
- const body = await request.json();
180
- const { id, filename, brightness, blur, scale, positionX, positionY } = body;
181
-
182
- if (!id || !filename) {
183
- return NextResponse.json(
184
- { error: 'Missing id or filename' },
185
- { status: 400 }
186
- );
187
- }
188
-
189
- // Acquire write lock to prevent concurrent writes
190
- const releaseLock = await acquireLock();
191
-
192
- try {
193
- const mappings = await readMappingsSafely();
194
-
195
- // Store as object with optional brightness, blur, scale, and position
196
- // Merge with existing values to avoid overwriting fields that aren't provided
197
- // Ensure scale is within valid range (0.1 to 5.0)
198
- const safeScale = scale !== undefined ? Math.max(0.1, Math.min(5.0, scale)) : undefined;
199
- const existing = mappings[id] || {};
200
- mappings[id] = {
201
- ...existing, // Preserve existing values
202
- filename, // Always update filename
203
- ...(brightness !== undefined && { brightness }),
204
- ...(blur !== undefined && { blur }),
205
- ...(safeScale !== undefined && { scale: safeScale }),
206
- ...(positionX !== undefined && { positionX }),
207
- ...(positionY !== undefined && { positionY }),
208
- };
209
-
210
- // Write atomically using a temporary file
211
- const tempPath = mappingsPath + '.tmp';
212
- await writeFile(tempPath, JSON.stringify(mappings, null, 2), 'utf-8');
213
-
214
- // Atomic rename (this is atomic on most filesystems)
215
- const { rename } = await import('fs/promises');
216
- await rename(tempPath, mappingsPath);
217
- } finally {
218
- releaseLock();
219
- }
220
-
221
- console.log('SAVING TO DB:', { id, scale, brightness, blur, positionX, positionY });
222
-
223
- return NextResponse.json({
224
- success: true,
225
- id,
226
- filename,
227
- brightness: brightness ?? 100,
228
- blur: blur ?? 0,
229
- scale: scale ?? 1.0,
230
- positionX: positionX ?? 0,
231
- positionY: positionY ?? 0,
232
- });
233
- } catch (error) {
234
- console.error('Set mapping error:', error);
235
- return NextResponse.json(
236
- { error: 'Failed to set image mapping' },
237
- { status: 500 }
238
- );
239
- }
240
- }
241
-
package/src/api/router.ts DELETED
@@ -1,85 +0,0 @@
1
- 'use server';
2
-
3
- /**
4
- * Plugin Images API Router
5
- * Centralized API handler for all images plugin routes
6
- *
7
- * This router handles requests to /api/plugin-images/*
8
- * and routes them to the appropriate handler
9
- */
10
-
11
- import { NextRequest, NextResponse } from 'next/server';
12
-
13
- /**
14
- * Handle images API requests
15
- * Routes requests to appropriate handlers based on path
16
- */
17
- export async function handleImagesApi(
18
- req: NextRequest,
19
- path: string[]
20
- ): Promise<NextResponse> {
21
- const method = req.method;
22
- const route = path[0] || '';
23
-
24
- try {
25
- // Route: /api/plugin-images/list (list images)
26
- if (!route || route === 'list') {
27
- if (method === 'GET') {
28
- const listModule = await import('./list');
29
- return await listModule.GET(req);
30
- }
31
- }
32
-
33
- // Route: /api/plugin-images/upload (upload image)
34
- else if (route === 'upload') {
35
- if (method === 'POST') {
36
- const uploadModule = await import('./upload');
37
- return await uploadModule.POST(req);
38
- }
39
- }
40
-
41
- // Route: /api/plugin-images/resolve (resolve image ID)
42
- else if (route === 'resolve') {
43
- if (method === 'GET' || method === 'POST') {
44
- const resolveModule = await import('./resolve/route');
45
- if (method === 'GET') {
46
- return await resolveModule.GET(req);
47
- }
48
- return await resolveModule.POST(req);
49
- }
50
- }
51
-
52
- // Route: /api/plugin-images/uploads/[filename] (serve/delete image)
53
- else if (route === 'uploads' && path[1]) {
54
- const filename = path[1];
55
- if (method === 'GET' || method === 'DELETE') {
56
- const uploadsModule = await import('./uploads/[filename]/route');
57
- if (method === 'GET') {
58
- return await uploadsModule.GET(req, { params: Promise.resolve({ filename }) });
59
- }
60
- return await uploadsModule.DELETE(req, { params: Promise.resolve({ filename }) });
61
- }
62
- }
63
-
64
- // Route: /api/plugin-images/fallback (serve fallback image)
65
- else if (route === 'fallback') {
66
- if (method === 'GET') {
67
- const fallbackModule = await import('./fallback/route');
68
- return await fallbackModule.GET(req);
69
- }
70
- }
71
-
72
- // Method not allowed
73
- return NextResponse.json(
74
- { error: `Method ${method} not allowed for route: ${route || '/'}` },
75
- { status: 405 }
76
- );
77
- } catch (error: any) {
78
- console.error('[ImagesApiRouter] Error:', error);
79
- return NextResponse.json(
80
- { error: error.message || 'Internal server error' },
81
- { status: 500 }
82
- );
83
- }
84
- }
85
-
@@ -1,88 +0,0 @@
1
- /**
2
- * Image Upload API Handler
3
- * POST /api/plugin-images/upload - Handles image file uploads
4
- */
5
-
6
- import { NextRequest, NextResponse } from 'next/server';
7
- import { writeFile, mkdir } from 'fs/promises';
8
- import path from 'path';
9
- import { randomBytes } from 'crypto';
10
-
11
- // Ensure uploads directory exists
12
- const uploadsDir = path.join(process.cwd(), 'data', 'uploads');
13
-
14
- async function ensureUploadsDir() {
15
- try {
16
- await mkdir(uploadsDir, { recursive: true });
17
- } catch (error) {
18
- // Directory might already exist
19
- }
20
- }
21
-
22
- export async function POST(request: NextRequest) {
23
- try {
24
- await ensureUploadsDir();
25
-
26
- const formData = await request.formData();
27
- const file = formData.get('file') as File;
28
-
29
- if (!file) {
30
- return NextResponse.json(
31
- { success: false, error: 'No file provided' },
32
- { status: 400 }
33
- );
34
- }
35
-
36
- // Validate file type
37
- const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
38
- if (!allowedTypes.includes(file.type)) {
39
- return NextResponse.json(
40
- { success: false, error: 'Invalid file type. Only images are allowed.' },
41
- { status: 400 }
42
- );
43
- }
44
-
45
- // Validate file size (max 10MB)
46
- const maxSize = 10 * 1024 * 1024; // 10MB
47
- if (file.size > maxSize) {
48
- return NextResponse.json(
49
- { success: false, error: 'File size exceeds 10MB limit' },
50
- { status: 400 }
51
- );
52
- }
53
-
54
- // Generate unique filename
55
- const ext = path.extname(file.name);
56
- const uniqueId = randomBytes(16).toString('hex');
57
- const timestamp = Date.now();
58
- const uniqueFilename = `${timestamp}-${uniqueId}${ext}`;
59
- const filePath = path.join(uploadsDir, uniqueFilename);
60
-
61
- // Save file
62
- const bytes = await file.arrayBuffer();
63
- const buffer = Buffer.from(bytes);
64
- await writeFile(filePath, buffer);
65
-
66
- // Get image dimensions (basic - could use sharp for better handling)
67
- const imageMetadata = {
68
- id: uniqueFilename,
69
- filename: file.name,
70
- url: `/api/uploads/${uniqueFilename}`,
71
- size: file.size,
72
- mimeType: file.type,
73
- uploadedAt: new Date().toISOString(),
74
- };
75
-
76
- return NextResponse.json({
77
- success: true,
78
- image: imageMetadata,
79
- });
80
- } catch (error) {
81
- console.error('Upload error:', error);
82
- return NextResponse.json(
83
- { success: false, error: 'Failed to upload image' },
84
- { status: 500 }
85
- );
86
- }
87
- }
88
-