say-under-me 1.4.0 → 1.5.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/dist/index.d.mts CHANGED
@@ -37,6 +37,8 @@ type CrudConfig<T = any> = {
37
37
  ownerField?: string;
38
38
  adminRole?: string;
39
39
  hooks?: LifecycleHooks<T>;
40
+ imageFields?: string[];
41
+ publicDir?: string;
40
42
  };
41
43
  type PaginatedResponse<T> = {
42
44
  data: T[];
@@ -47,6 +49,17 @@ type PaginatedResponse<T> = {
47
49
  totalPages: number;
48
50
  };
49
51
  };
52
+ type UploadConfig = {
53
+ uploadDir?: string;
54
+ maxSize?: number;
55
+ allowedTypes?: string[];
56
+ };
57
+ type UploadResult = {
58
+ url: string;
59
+ name: string;
60
+ size: number;
61
+ type: string;
62
+ };
50
63
  declare class ForbiddenError extends Error {
51
64
  constructor(message?: string);
52
65
  }
@@ -108,6 +121,10 @@ declare class PrismaCrud<T> {
108
121
  * Deletes a record by ID with optional lifecycle hooks.
109
122
  */
110
123
  delete(id: string | number, context?: UserContext): Promise<any>;
124
+ /**
125
+ * Deletes a file from the filesystem if it belongs to the uploads directory.
126
+ */
127
+ private deleteLocalFile;
111
128
  /**
112
129
  * Logic to detect if an ID is a number (Int) or string (UUID/CUID).
113
130
  * This allows the API to handle both `id=1` and `id=uuid-string` automatically.
@@ -133,4 +150,23 @@ declare function createApiHandler<T>(crud: PrismaCrud<T>, options?: {
133
150
  DELETE: (req: NextRequest, context: any) => Promise<NextResponse<unknown>>;
134
151
  };
135
152
 
136
- export { type CrudConfig, ForbiddenError, type LifecycleHooks, type PaginatedResponse, type ParseOptions, type PermissionConfig, PrismaCrud, type PrismaQuery, type UserContext, createApiHandler, parsePrismaQuery, sayHello, type sayHelloProps };
153
+ /**
154
+ * Creates a Next.js App Router handler for file uploads and management.
155
+ * POST: Upload new files
156
+ * GET: List existing files
157
+ */
158
+ declare function createUploadHandler(config?: UploadConfig): {
159
+ GET: () => Promise<NextResponse<any[]> | NextResponse<{
160
+ error: string;
161
+ }>>;
162
+ POST: (req: NextRequest) => Promise<NextResponse<{
163
+ error: string;
164
+ }> | NextResponse<UploadResult | UploadResult[]>>;
165
+ DELETE: (req: NextRequest) => Promise<NextResponse<{
166
+ error: string;
167
+ }> | NextResponse<{
168
+ success: boolean;
169
+ }>>;
170
+ };
171
+
172
+ export { type CrudConfig, ForbiddenError, type LifecycleHooks, type PaginatedResponse, type ParseOptions, type PermissionConfig, PrismaCrud, type PrismaQuery, type UploadConfig, type UploadResult, type UserContext, createApiHandler, createUploadHandler, parsePrismaQuery, sayHello, type sayHelloProps };
package/dist/index.d.ts CHANGED
@@ -37,6 +37,8 @@ type CrudConfig<T = any> = {
37
37
  ownerField?: string;
38
38
  adminRole?: string;
39
39
  hooks?: LifecycleHooks<T>;
40
+ imageFields?: string[];
41
+ publicDir?: string;
40
42
  };
41
43
  type PaginatedResponse<T> = {
42
44
  data: T[];
@@ -47,6 +49,17 @@ type PaginatedResponse<T> = {
47
49
  totalPages: number;
48
50
  };
49
51
  };
52
+ type UploadConfig = {
53
+ uploadDir?: string;
54
+ maxSize?: number;
55
+ allowedTypes?: string[];
56
+ };
57
+ type UploadResult = {
58
+ url: string;
59
+ name: string;
60
+ size: number;
61
+ type: string;
62
+ };
50
63
  declare class ForbiddenError extends Error {
51
64
  constructor(message?: string);
52
65
  }
@@ -108,6 +121,10 @@ declare class PrismaCrud<T> {
108
121
  * Deletes a record by ID with optional lifecycle hooks.
109
122
  */
110
123
  delete(id: string | number, context?: UserContext): Promise<any>;
124
+ /**
125
+ * Deletes a file from the filesystem if it belongs to the uploads directory.
126
+ */
127
+ private deleteLocalFile;
111
128
  /**
112
129
  * Logic to detect if an ID is a number (Int) or string (UUID/CUID).
113
130
  * This allows the API to handle both `id=1` and `id=uuid-string` automatically.
@@ -133,4 +150,23 @@ declare function createApiHandler<T>(crud: PrismaCrud<T>, options?: {
133
150
  DELETE: (req: NextRequest, context: any) => Promise<NextResponse<unknown>>;
134
151
  };
135
152
 
136
- export { type CrudConfig, ForbiddenError, type LifecycleHooks, type PaginatedResponse, type ParseOptions, type PermissionConfig, PrismaCrud, type PrismaQuery, type UserContext, createApiHandler, parsePrismaQuery, sayHello, type sayHelloProps };
153
+ /**
154
+ * Creates a Next.js App Router handler for file uploads and management.
155
+ * POST: Upload new files
156
+ * GET: List existing files
157
+ */
158
+ declare function createUploadHandler(config?: UploadConfig): {
159
+ GET: () => Promise<NextResponse<any[]> | NextResponse<{
160
+ error: string;
161
+ }>>;
162
+ POST: (req: NextRequest) => Promise<NextResponse<{
163
+ error: string;
164
+ }> | NextResponse<UploadResult | UploadResult[]>>;
165
+ DELETE: (req: NextRequest) => Promise<NextResponse<{
166
+ error: string;
167
+ }> | NextResponse<{
168
+ success: boolean;
169
+ }>>;
170
+ };
171
+
172
+ export { type CrudConfig, ForbiddenError, type LifecycleHooks, type PaginatedResponse, type ParseOptions, type PermissionConfig, PrismaCrud, type PrismaQuery, type UploadConfig, type UploadResult, type UserContext, createApiHandler, createUploadHandler, parsePrismaQuery, sayHello, type sayHelloProps };
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -23,6 +33,7 @@ __export(index_exports, {
23
33
  ForbiddenError: () => ForbiddenError,
24
34
  PrismaCrud: () => PrismaCrud,
25
35
  createApiHandler: () => createApiHandler,
36
+ createUploadHandler: () => createUploadHandler,
26
37
  parsePrismaQuery: () => parsePrismaQuery,
27
38
  sayHello: () => sayHello
28
39
  });
@@ -184,6 +195,8 @@ function parseValue(val, op) {
184
195
  }
185
196
 
186
197
  // src/core/prisma-crud.ts
198
+ var import_promises = require("fs/promises");
199
+ var import_path = require("path");
187
200
  var PrismaCrud = class {
188
201
  /**
189
202
  * @param model -The Prisma model delegate (e.g., prisma.user)
@@ -286,7 +299,29 @@ var PrismaCrud = class {
286
299
  if (!this.checkPermission("update", context)) throw new ForbiddenError();
287
300
  try {
288
301
  if (schema) data = schema.parse(data);
289
- const where = this.applySecurity({ id: this.parseId(id) }, context);
302
+ const parsedId = this.parseId(id);
303
+ const where = this.applySecurity({ id: parsedId }, context);
304
+ if (this.config.imageFields && this.config.imageFields.length > 0) {
305
+ const oldRecord = await this.model.findUnique({ where });
306
+ if (oldRecord) {
307
+ for (const field of this.config.imageFields) {
308
+ const newValue = data[field];
309
+ const oldValue = oldRecord[field];
310
+ if (newValue === void 0) continue;
311
+ if (typeof oldValue === "string" && typeof newValue === "string") {
312
+ if (oldValue !== newValue) {
313
+ await this.deleteLocalFile(oldValue);
314
+ }
315
+ } else if (Array.isArray(oldValue)) {
316
+ const newArray = Array.isArray(newValue) ? newValue : [];
317
+ const removedImages = oldValue.filter((img) => !newArray.includes(img));
318
+ for (const img of removedImages) {
319
+ await this.deleteLocalFile(img);
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
290
325
  if (this.config.hooks?.beforeUpdate) {
291
326
  data = await this.config.hooks.beforeUpdate(data);
292
327
  }
@@ -309,8 +344,24 @@ var PrismaCrud = class {
309
344
  async delete(id, context) {
310
345
  if (!this.checkPermission("delete", context)) throw new ForbiddenError();
311
346
  try {
312
- const where = this.applySecurity({ id: this.parseId(id) }, context);
313
347
  const parsedId = this.parseId(id);
348
+ const where = this.applySecurity({ id: parsedId }, context);
349
+ if (this.config.imageFields && this.config.imageFields.length > 0) {
350
+ const record = await this.model.findUnique({ where });
351
+ if (record) {
352
+ for (const field of this.config.imageFields) {
353
+ const value = record[field];
354
+ if (!value) continue;
355
+ if (Array.isArray(value)) {
356
+ for (const img of value) {
357
+ await this.deleteLocalFile(img);
358
+ }
359
+ } else if (typeof value === "string") {
360
+ await this.deleteLocalFile(value);
361
+ }
362
+ }
363
+ }
364
+ }
314
365
  if (this.config.hooks?.beforeDelete) {
315
366
  await this.config.hooks.beforeDelete(parsedId);
316
367
  }
@@ -324,6 +375,20 @@ var PrismaCrud = class {
324
375
  throw error;
325
376
  }
326
377
  }
378
+ /**
379
+ * Deletes a file from the filesystem if it belongs to the uploads directory.
380
+ */
381
+ async deleteLocalFile(relativeUrl) {
382
+ try {
383
+ if (!relativeUrl || relativeUrl.startsWith("http")) return;
384
+ const publicDir = this.config.publicDir || "public";
385
+ const absolutePath = (0, import_path.join)(process.cwd(), publicDir, relativeUrl);
386
+ await (0, import_promises.unlink)(absolutePath);
387
+ console.log(`[underme] Deleted old file: ${absolutePath}`);
388
+ } catch (error) {
389
+ console.warn(`[underme] Failed to delete file at ${relativeUrl}:`, error);
390
+ }
391
+ }
327
392
  /**
328
393
  * Logic to detect if an ID is a number (Int) or string (UUID/CUID).
329
394
  * This allows the API to handle both `id=1` and `id=uuid-string` automatically.
@@ -417,11 +482,103 @@ function createApiHandler(crud, options = {}) {
417
482
  };
418
483
  return { GET, POST, PATCH, DELETE };
419
484
  }
485
+
486
+ // src/adapters/upload-handler.ts
487
+ var import_server2 = require("next/server");
488
+ var import_promises2 = require("fs/promises");
489
+ var import_path2 = __toESM(require("path"));
490
+ var import_crypto = __toESM(require("crypto"));
491
+ function createUploadHandler(config = {}) {
492
+ const uploadDir = config.uploadDir || "public/uploads";
493
+ const maxSize = config.maxSize || 5 * 1024 * 1024;
494
+ const allowedTypes = config.allowedTypes || ["image/jpeg", "image/png", "image/webp", "image/gif"];
495
+ const GET = async () => {
496
+ try {
497
+ const absoluteUploadDir = import_path2.default.join(process.cwd(), uploadDir);
498
+ await (0, import_promises2.mkdir)(absoluteUploadDir, { recursive: true });
499
+ const files = await (0, import_promises2.readdir)(absoluteUploadDir);
500
+ const results = await Promise.all(
501
+ files.map(async (filename) => {
502
+ const filePath = import_path2.default.join(absoluteUploadDir, filename);
503
+ const fileStat = await (0, import_promises2.stat)(filePath);
504
+ if (fileStat.isDirectory()) return null;
505
+ return {
506
+ url: `/${uploadDir.replace("public/", "")}/${filename}`,
507
+ name: filename,
508
+ size: fileStat.size,
509
+ atime: fileStat.atime,
510
+ mtime: fileStat.mtime
511
+ };
512
+ })
513
+ );
514
+ return import_server2.NextResponse.json(results.filter((f) => f !== null).sort((a, b) => b.mtime.getTime() - a.mtime.getTime()));
515
+ } catch (error) {
516
+ return import_server2.NextResponse.json({ error: "Failed to list uploads: " + error.message }, { status: 500 });
517
+ }
518
+ };
519
+ const POST = async (req) => {
520
+ try {
521
+ const formData = await req.formData();
522
+ const files = formData.getAll("file");
523
+ if (!files || files.length === 0) {
524
+ return import_server2.NextResponse.json({ error: "No files uploaded" }, { status: 400 });
525
+ }
526
+ const results = [];
527
+ const absoluteUploadDir = import_path2.default.join(process.cwd(), uploadDir);
528
+ await (0, import_promises2.mkdir)(absoluteUploadDir, { recursive: true });
529
+ for (const file of files) {
530
+ if (file.size > maxSize) {
531
+ return import_server2.NextResponse.json({
532
+ error: `File ${file.name} is too large. Max size is ${maxSize / (1024 * 1024)}MB`
533
+ }, { status: 400 });
534
+ }
535
+ if (!allowedTypes.includes(file.type)) {
536
+ return import_server2.NextResponse.json({
537
+ error: `File type ${file.type} is not allowed.`
538
+ }, { status: 400 });
539
+ }
540
+ const buffer = Buffer.from(await file.arrayBuffer());
541
+ const extension = import_path2.default.extname(file.name);
542
+ const filename = `${import_crypto.default.randomUUID()}${extension}`;
543
+ const filePath = import_path2.default.join(absoluteUploadDir, filename);
544
+ await (0, import_promises2.writeFile)(filePath, buffer);
545
+ results.push({
546
+ url: `/${uploadDir.replace("public/", "")}/${filename}`,
547
+ name: filename,
548
+ // Original filename is replaced with UUID for safety
549
+ size: file.size,
550
+ type: file.type
551
+ });
552
+ }
553
+ return import_server2.NextResponse.json(files.length === 1 ? results[0] : results);
554
+ } catch (error) {
555
+ console.error("Upload Handler Error:", error);
556
+ return import_server2.NextResponse.json({ error: "Upload failed: " + error.message }, { status: 500 });
557
+ }
558
+ };
559
+ const DELETE = async (req) => {
560
+ try {
561
+ const { searchParams } = new URL(req.url);
562
+ const filename = searchParams.get("file");
563
+ if (!filename) {
564
+ return import_server2.NextResponse.json({ error: "Filename is required" }, { status: 400 });
565
+ }
566
+ const sanitizedFilename = import_path2.default.basename(filename);
567
+ const absolutePath = import_path2.default.join(process.cwd(), uploadDir, sanitizedFilename);
568
+ await (0, import_promises2.unlink)(absolutePath);
569
+ return import_server2.NextResponse.json({ success: true });
570
+ } catch (error) {
571
+ return import_server2.NextResponse.json({ error: "Failed to delete file: " + error.message }, { status: 500 });
572
+ }
573
+ };
574
+ return { GET, POST, DELETE };
575
+ }
420
576
  // Annotate the CommonJS export names for ESM import in node:
421
577
  0 && (module.exports = {
422
578
  ForbiddenError,
423
579
  PrismaCrud,
424
580
  createApiHandler,
581
+ createUploadHandler,
425
582
  parsePrismaQuery,
426
583
  sayHello
427
584
  });
package/dist/index.mjs CHANGED
@@ -154,6 +154,8 @@ function parseValue(val, op) {
154
154
  }
155
155
 
156
156
  // src/core/prisma-crud.ts
157
+ import { unlink } from "fs/promises";
158
+ import { join } from "path";
157
159
  var PrismaCrud = class {
158
160
  /**
159
161
  * @param model -The Prisma model delegate (e.g., prisma.user)
@@ -256,7 +258,29 @@ var PrismaCrud = class {
256
258
  if (!this.checkPermission("update", context)) throw new ForbiddenError();
257
259
  try {
258
260
  if (schema) data = schema.parse(data);
259
- const where = this.applySecurity({ id: this.parseId(id) }, context);
261
+ const parsedId = this.parseId(id);
262
+ const where = this.applySecurity({ id: parsedId }, context);
263
+ if (this.config.imageFields && this.config.imageFields.length > 0) {
264
+ const oldRecord = await this.model.findUnique({ where });
265
+ if (oldRecord) {
266
+ for (const field of this.config.imageFields) {
267
+ const newValue = data[field];
268
+ const oldValue = oldRecord[field];
269
+ if (newValue === void 0) continue;
270
+ if (typeof oldValue === "string" && typeof newValue === "string") {
271
+ if (oldValue !== newValue) {
272
+ await this.deleteLocalFile(oldValue);
273
+ }
274
+ } else if (Array.isArray(oldValue)) {
275
+ const newArray = Array.isArray(newValue) ? newValue : [];
276
+ const removedImages = oldValue.filter((img) => !newArray.includes(img));
277
+ for (const img of removedImages) {
278
+ await this.deleteLocalFile(img);
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
260
284
  if (this.config.hooks?.beforeUpdate) {
261
285
  data = await this.config.hooks.beforeUpdate(data);
262
286
  }
@@ -279,8 +303,24 @@ var PrismaCrud = class {
279
303
  async delete(id, context) {
280
304
  if (!this.checkPermission("delete", context)) throw new ForbiddenError();
281
305
  try {
282
- const where = this.applySecurity({ id: this.parseId(id) }, context);
283
306
  const parsedId = this.parseId(id);
307
+ const where = this.applySecurity({ id: parsedId }, context);
308
+ if (this.config.imageFields && this.config.imageFields.length > 0) {
309
+ const record = await this.model.findUnique({ where });
310
+ if (record) {
311
+ for (const field of this.config.imageFields) {
312
+ const value = record[field];
313
+ if (!value) continue;
314
+ if (Array.isArray(value)) {
315
+ for (const img of value) {
316
+ await this.deleteLocalFile(img);
317
+ }
318
+ } else if (typeof value === "string") {
319
+ await this.deleteLocalFile(value);
320
+ }
321
+ }
322
+ }
323
+ }
284
324
  if (this.config.hooks?.beforeDelete) {
285
325
  await this.config.hooks.beforeDelete(parsedId);
286
326
  }
@@ -294,6 +334,20 @@ var PrismaCrud = class {
294
334
  throw error;
295
335
  }
296
336
  }
337
+ /**
338
+ * Deletes a file from the filesystem if it belongs to the uploads directory.
339
+ */
340
+ async deleteLocalFile(relativeUrl) {
341
+ try {
342
+ if (!relativeUrl || relativeUrl.startsWith("http")) return;
343
+ const publicDir = this.config.publicDir || "public";
344
+ const absolutePath = join(process.cwd(), publicDir, relativeUrl);
345
+ await unlink(absolutePath);
346
+ console.log(`[underme] Deleted old file: ${absolutePath}`);
347
+ } catch (error) {
348
+ console.warn(`[underme] Failed to delete file at ${relativeUrl}:`, error);
349
+ }
350
+ }
297
351
  /**
298
352
  * Logic to detect if an ID is a number (Int) or string (UUID/CUID).
299
353
  * This allows the API to handle both `id=1` and `id=uuid-string` automatically.
@@ -387,10 +441,102 @@ function createApiHandler(crud, options = {}) {
387
441
  };
388
442
  return { GET, POST, PATCH, DELETE };
389
443
  }
444
+
445
+ // src/adapters/upload-handler.ts
446
+ import { NextResponse as NextResponse2 } from "next/server";
447
+ import { writeFile, mkdir, readdir, stat, unlink as unlink2 } from "fs/promises";
448
+ import path from "path";
449
+ import crypto from "crypto";
450
+ function createUploadHandler(config = {}) {
451
+ const uploadDir = config.uploadDir || "public/uploads";
452
+ const maxSize = config.maxSize || 5 * 1024 * 1024;
453
+ const allowedTypes = config.allowedTypes || ["image/jpeg", "image/png", "image/webp", "image/gif"];
454
+ const GET = async () => {
455
+ try {
456
+ const absoluteUploadDir = path.join(process.cwd(), uploadDir);
457
+ await mkdir(absoluteUploadDir, { recursive: true });
458
+ const files = await readdir(absoluteUploadDir);
459
+ const results = await Promise.all(
460
+ files.map(async (filename) => {
461
+ const filePath = path.join(absoluteUploadDir, filename);
462
+ const fileStat = await stat(filePath);
463
+ if (fileStat.isDirectory()) return null;
464
+ return {
465
+ url: `/${uploadDir.replace("public/", "")}/${filename}`,
466
+ name: filename,
467
+ size: fileStat.size,
468
+ atime: fileStat.atime,
469
+ mtime: fileStat.mtime
470
+ };
471
+ })
472
+ );
473
+ return NextResponse2.json(results.filter((f) => f !== null).sort((a, b) => b.mtime.getTime() - a.mtime.getTime()));
474
+ } catch (error) {
475
+ return NextResponse2.json({ error: "Failed to list uploads: " + error.message }, { status: 500 });
476
+ }
477
+ };
478
+ const POST = async (req) => {
479
+ try {
480
+ const formData = await req.formData();
481
+ const files = formData.getAll("file");
482
+ if (!files || files.length === 0) {
483
+ return NextResponse2.json({ error: "No files uploaded" }, { status: 400 });
484
+ }
485
+ const results = [];
486
+ const absoluteUploadDir = path.join(process.cwd(), uploadDir);
487
+ await mkdir(absoluteUploadDir, { recursive: true });
488
+ for (const file of files) {
489
+ if (file.size > maxSize) {
490
+ return NextResponse2.json({
491
+ error: `File ${file.name} is too large. Max size is ${maxSize / (1024 * 1024)}MB`
492
+ }, { status: 400 });
493
+ }
494
+ if (!allowedTypes.includes(file.type)) {
495
+ return NextResponse2.json({
496
+ error: `File type ${file.type} is not allowed.`
497
+ }, { status: 400 });
498
+ }
499
+ const buffer = Buffer.from(await file.arrayBuffer());
500
+ const extension = path.extname(file.name);
501
+ const filename = `${crypto.randomUUID()}${extension}`;
502
+ const filePath = path.join(absoluteUploadDir, filename);
503
+ await writeFile(filePath, buffer);
504
+ results.push({
505
+ url: `/${uploadDir.replace("public/", "")}/${filename}`,
506
+ name: filename,
507
+ // Original filename is replaced with UUID for safety
508
+ size: file.size,
509
+ type: file.type
510
+ });
511
+ }
512
+ return NextResponse2.json(files.length === 1 ? results[0] : results);
513
+ } catch (error) {
514
+ console.error("Upload Handler Error:", error);
515
+ return NextResponse2.json({ error: "Upload failed: " + error.message }, { status: 500 });
516
+ }
517
+ };
518
+ const DELETE = async (req) => {
519
+ try {
520
+ const { searchParams } = new URL(req.url);
521
+ const filename = searchParams.get("file");
522
+ if (!filename) {
523
+ return NextResponse2.json({ error: "Filename is required" }, { status: 400 });
524
+ }
525
+ const sanitizedFilename = path.basename(filename);
526
+ const absolutePath = path.join(process.cwd(), uploadDir, sanitizedFilename);
527
+ await unlink2(absolutePath);
528
+ return NextResponse2.json({ success: true });
529
+ } catch (error) {
530
+ return NextResponse2.json({ error: "Failed to delete file: " + error.message }, { status: 500 });
531
+ }
532
+ };
533
+ return { GET, POST, DELETE };
534
+ }
390
535
  export {
391
536
  ForbiddenError,
392
537
  PrismaCrud,
393
538
  createApiHandler,
539
+ createUploadHandler,
394
540
  parsePrismaQuery,
395
541
  sayHello
396
542
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "say-under-me",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",