@valentine-efagene/qshelter-common 2.0.149 → 2.0.151

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.
@@ -1,6 +1,6 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
2
  /**
3
3
  * Global error handler middleware for Express applications.
4
- * Handles ZodError, AppError, and generic errors with appropriate responses.
4
+ * Handles ZodError, AppError, Prisma errors, and generic errors with appropriate responses.
5
5
  */
6
6
  export declare function errorHandler(err: Error, req: Request, res: Response, next: NextFunction): Response<any, Record<string, any>>;
@@ -1,10 +1,83 @@
1
1
  import { ZodError } from 'zod';
2
2
  import { AppError } from '../utils/errors';
3
+ /**
4
+ * Extract a user-friendly field name from Prisma's target array.
5
+ * Handles composite unique constraints like `tenantId_cacNumber`.
6
+ */
7
+ function extractFieldName(target) {
8
+ // Filter out common fields that are not user-facing
9
+ const userFacingFields = target.filter((f) => !['tenantId', 'id', 'createdAt', 'updatedAt'].includes(f));
10
+ if (userFacingFields.length === 0) {
11
+ // Fallback: use the last target field and make it readable
12
+ const lastField = target[target.length - 1];
13
+ return lastField.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
14
+ }
15
+ // Convert camelCase to readable format
16
+ return userFacingFields
17
+ .map((f) => f.replace(/([A-Z])/g, ' $1').trim().toLowerCase())
18
+ .join(', ');
19
+ }
20
+ /**
21
+ * Handle Prisma-specific errors with user-friendly messages.
22
+ */
23
+ function handlePrismaError(err) {
24
+ // Check if this is a Prisma error (has code property)
25
+ if (!err.code || typeof err.code !== 'string' || !err.code.startsWith('P')) {
26
+ return null;
27
+ }
28
+ switch (err.code) {
29
+ case 'P2002': {
30
+ // Unique constraint violation
31
+ const target = err.meta?.target;
32
+ const modelName = err.meta?.modelName;
33
+ if (target && target.length > 0) {
34
+ const fieldName = extractFieldName(target);
35
+ const entity = modelName || 'Record';
36
+ return {
37
+ statusCode: 409,
38
+ message: `A ${entity.toLowerCase()} with this ${fieldName} already exists`,
39
+ };
40
+ }
41
+ return {
42
+ statusCode: 409,
43
+ message: 'A record with these values already exists',
44
+ };
45
+ }
46
+ case 'P2003': {
47
+ // Foreign key constraint violation
48
+ const field = err.meta?.field_name;
49
+ return {
50
+ statusCode: 400,
51
+ message: field
52
+ ? `Referenced ${field.replace(/([A-Z])/g, ' $1').trim().toLowerCase()} does not exist`
53
+ : 'Referenced record does not exist',
54
+ };
55
+ }
56
+ case 'P2025': {
57
+ // Record not found for update/delete
58
+ return {
59
+ statusCode: 404,
60
+ message: 'Record not found',
61
+ };
62
+ }
63
+ case 'P2014': {
64
+ // Required relation violation
65
+ return {
66
+ statusCode: 400,
67
+ message: 'This operation would violate a required relation',
68
+ };
69
+ }
70
+ default:
71
+ // Unknown Prisma error - log it but return generic message
72
+ return null;
73
+ }
74
+ }
3
75
  /**
4
76
  * Global error handler middleware for Express applications.
5
- * Handles ZodError, AppError, and generic errors with appropriate responses.
77
+ * Handles ZodError, AppError, Prisma errors, and generic errors with appropriate responses.
6
78
  */
7
79
  export function errorHandler(err, req, res, next) {
80
+ // Handle Zod validation errors
8
81
  if (err instanceof ZodError) {
9
82
  return res.status(400).json({
10
83
  success: false,
@@ -13,6 +86,7 @@ export function errorHandler(err, req, res, next) {
13
86
  details: err.issues,
14
87
  });
15
88
  }
89
+ // Handle custom application errors
16
90
  if (err instanceof AppError) {
17
91
  return res.status(err.statusCode).json({
18
92
  success: false,
@@ -20,6 +94,16 @@ export function errorHandler(err, req, res, next) {
20
94
  error: err.message,
21
95
  });
22
96
  }
97
+ // Handle Prisma errors
98
+ const prismaError = handlePrismaError(err);
99
+ if (prismaError) {
100
+ return res.status(prismaError.statusCode).json({
101
+ success: false,
102
+ message: prismaError.message,
103
+ error: prismaError.message,
104
+ });
105
+ }
106
+ // Unhandled error - log and return generic message
23
107
  console.error('Unhandled error:', err);
24
108
  return res.status(500).json({
25
109
  success: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentine-efagene/qshelter-common",
3
- "version": "2.0.149",
3
+ "version": "2.0.151",
4
4
  "description": "Shared database schemas and utilities for QShelter services",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -0,0 +1,10 @@
1
+ /*
2
+ Warnings:
3
+
4
+ - You are about to drop the column `isPublished` on the `properties` table. All the data in the column will be lost.
5
+ - You are about to alter the column `status` on the `properties` table. The data in that column could be lost. The data in that column will be cast from `VarChar(191)` to `Enum(EnumId(5))`.
6
+
7
+ */
8
+ -- AlterTable
9
+ ALTER TABLE `properties` DROP COLUMN `isPublished`,
10
+ MODIFY `status` ENUM('DRAFT', 'PUBLISHED', 'SOLD_OUT', 'ARCHIVED') NOT NULL DEFAULT 'DRAFT';
@@ -51,6 +51,13 @@ enum PaymentFrequency {
51
51
  CUSTOM
52
52
  }
53
53
 
54
+ enum PropertyStatus {
55
+ DRAFT
56
+ PUBLISHED
57
+ SOLD_OUT
58
+ ARCHIVED
59
+ }
60
+
54
61
  enum ApplicationStatus {
55
62
  DRAFT
56
63
  PENDING
@@ -1112,11 +1119,10 @@ model Property {
1112
1119
  streetAddress String?
1113
1120
  longitude Float?
1114
1121
  latitude Float?
1115
- status String @default("DRAFT") // DRAFT, PUBLISHED, SOLD_OUT, ARCHIVED
1122
+ status PropertyStatus @default(DRAFT)
1116
1123
  description String? @db.Text
1117
1124
  displayImageId String?
1118
1125
  displayImage PropertyMedia? @relation("DisplayImage", fields: [displayImageId], references: [id], onDelete: SetNull)
1119
- isPublished Boolean @default(false)
1120
1126
  publishedAt DateTime?
1121
1127
  createdAt DateTime @default(now())
1122
1128
  updatedAt DateTime @updatedAt