create-prisma-php-app 1.26.503 → 1.26.504

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.
@@ -381,7 +381,11 @@ function checkForDuplicateRoutes()
381
381
  }
382
382
 
383
383
  if (!empty($errorMessages)) {
384
- $errorMessageString = implode("<br>", $errorMessages);
384
+ if (isAjaxOrXFileRequestOrRouteFile()) {
385
+ $errorMessageString = implode("\n", $errorMessages);
386
+ } else {
387
+ $errorMessageString = implode("<br>", $errorMessages);
388
+ }
385
389
  modifyOutputLayoutForError($errorMessageString);
386
390
  }
387
391
  }
@@ -571,18 +575,36 @@ function modifyOutputLayoutForError($contentToAdd)
571
575
 
572
576
  $errorContent = $contentToAdd;
573
577
 
574
- $layoutFile = APP_PATH . '/layout.php';
575
- if (file_exists($layoutFile)) {
576
-
577
- ob_start();
578
- include_once $errorFile;
579
- MainLayout::$children = ob_get_clean();
580
- include $layoutFile;
578
+ if (isAjaxOrXFileRequestOrRouteFile()) {
579
+ header('Content-Type: application/json');
580
+ echo json_encode([
581
+ 'success' => false,
582
+ 'error' => $errorContent
583
+ ]);
584
+ http_response_code(403);
581
585
  } else {
582
- echo $errorContent;
586
+ $layoutFile = APP_PATH . '/layout.php';
587
+ if (file_exists($layoutFile)) {
588
+
589
+ ob_start();
590
+ require_once $errorFile;
591
+ MainLayout::$children = ob_get_clean();
592
+ require_once $layoutFile;
593
+ } else {
594
+ echo $errorContent;
595
+ }
583
596
  }
584
597
  } else {
585
- echo $contentToAdd;
598
+ if (isAjaxOrXFileRequestOrRouteFile()) {
599
+ header('Content-Type: application/json');
600
+ echo json_encode([
601
+ 'success' => false,
602
+ 'error' => $contentToAdd
603
+ ]);
604
+ http_response_code(403);
605
+ } else {
606
+ echo $contentToAdd;
607
+ }
586
608
  }
587
609
  exit;
588
610
  }
@@ -646,21 +668,34 @@ function authenticateUserToken()
646
668
  }
647
669
 
648
670
  set_exception_handler(function ($exception) {
649
- $errorContent = "<div class='error'>Exception: " . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . "</div>";
671
+ if (isAjaxOrXFileRequestOrRouteFile()) {
672
+ $errorContent = "Exception: " . $exception->getMessage();
673
+ } else {
674
+ $errorContent = "<div class='error'>Exception: " . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . "</div>";
675
+ }
650
676
  modifyOutputLayoutForError($errorContent);
651
677
  });
652
678
 
653
679
  register_shutdown_function(function () {
654
680
  $error = error_get_last();
655
681
  if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR])) {
656
- $formattedError = "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
657
- " in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
658
- " on line " . $error['line'] . "</div>";
659
- $errorContent = $formattedError;
682
+
683
+ if (isAjaxOrXFileRequestOrRouteFile()) {
684
+ $errorContent = "Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line'];
685
+ } else {
686
+ $errorContent = "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
687
+ " in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
688
+ " on line " . $error['line'] . "</div>";
689
+ }
660
690
  modifyOutputLayoutForError($errorContent);
661
691
  }
662
692
  });
663
693
 
694
+ function isAjaxOrXFileRequestOrRouteFile(): bool
695
+ {
696
+ return Request::$isAjax || Request::$isXFileRequest || Request::$fileToInclude === 'route.php';
697
+ }
698
+
664
699
  try {
665
700
  $_determineContentToInclude = determineContentToInclude();
666
701
  $_contentToInclude = $_determineContentToInclude['path'] ?? '';
@@ -800,24 +835,42 @@ try {
800
835
  }
801
836
  } else {
802
837
  if ($_isContentIncluded) {
803
- echo "<div class='error'>The parent layout file does not contain &lt;?php echo MainLayout::\$children; ?&gt; Or &lt;?= MainLayout::\$children ?&gt;<br>" . "<strong>$_parentLayoutPath</strong></div>";
838
+ if (isAjaxOrXFileRequestOrRouteFile()) {
839
+ $_errorDetails = "The layout file does not contain &lt;?php echo MainLayout::\$childLayoutChildren; ?&gt; or &lt;?= MainLayout::\$childLayoutChildren ?&gt;<br><strong>$layoutPath</strong>";
840
+ } else {
841
+ $_errorDetails = "<div class='error'>The parent layout file does not contain &lt;?php echo MainLayout::\$children; ?&gt; Or &lt;?= MainLayout::\$children ?&gt;<br>" . "<strong>$_parentLayoutPath</strong></div>";
842
+ }
843
+ modifyOutputLayoutForError($_errorDetails);
804
844
  } else {
805
- $_errorDetails = "<div class='error'>The layout file does not contain &lt;?php echo MainLayout::\$childLayoutChildren; ?&gt; or &lt;?= MainLayout::\$childLayoutChildren ?&gt;<br><strong>$layoutPath</strong></div>";
845
+ if (isAjaxOrXFileRequestOrRouteFile()) {
846
+ $_errorDetails = "The layout file does not contain &lt;?php echo MainLayout::\$childLayoutChildren; ?&gt; or &lt;?= MainLayout::\$childLayoutChildren ?&gt;<br><strong>$layoutPath</strong>";
847
+ } else {
848
+ $_errorDetails = "<div class='error'>The layout file does not contain &lt;?php echo MainLayout::\$childLayoutChildren; ?&gt; or &lt;?= MainLayout::\$childLayoutChildren ?&gt;<br><strong>$layoutPath</strong></div>";
849
+ }
806
850
  modifyOutputLayoutForError($_errorDetails);
807
851
  }
808
852
  }
809
853
  } catch (Throwable $e) {
810
- $_errorDetails = "Unhandled Exception: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
811
- $_errorDetails .= "<br>File: " . htmlspecialchars($e->getFile(), ENT_QUOTES, 'UTF-8');
812
- $_errorDetails .= "<br>Line: " . htmlspecialchars($e->getLine(), ENT_QUOTES, 'UTF-8');
813
- $_errorDetails = "<div class='error'>$_errorDetails</div>";
854
+ if (isAjaxOrXFileRequestOrRouteFile()) {
855
+ $_errorDetails = "Unhandled Exception: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine();
856
+ } else {
857
+ $_errorDetails = "Unhandled Exception: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
858
+ $_errorDetails .= "<br>File: " . htmlspecialchars($e->getFile(), ENT_QUOTES, 'UTF-8');
859
+ $_errorDetails .= "<br>Line: " . htmlspecialchars($e->getLine(), ENT_QUOTES, 'UTF-8');
860
+ $_errorDetails = "<div class='error'>$_errorDetails</div>";
861
+ }
814
862
  modifyOutputLayoutForError($_errorDetails);
815
863
  }
816
864
 
817
865
  (function () {
818
866
  $lastErrorCapture = error_get_last();
819
867
  if ($lastErrorCapture !== null) {
820
- $errorContent = "<div class='error'>Error: " . $lastErrorCapture['message'] . " in " . $lastErrorCapture['file'] . " on line " . $lastErrorCapture['line'] . "</div>";
868
+
869
+ if (isAjaxOrXFileRequestOrRouteFile()) {
870
+ $errorContent = "Error: " . $lastErrorCapture['message'] . " in " . $lastErrorCapture['file'] . " on line " . $lastErrorCapture['line'];
871
+ } else {
872
+ $errorContent = "<div class='error'>Error: " . $lastErrorCapture['message'] . " in " . $lastErrorCapture['file'] . " on line " . $lastErrorCapture['line'] . "</div>";
873
+ }
821
874
  modifyOutputLayoutForError($errorContent);
822
875
  }
823
876
  })();
@@ -829,7 +882,11 @@ set_error_handler(function ($severity, $message, $file, $line) {
829
882
  }
830
883
 
831
884
  // Capture the specific severity types, including warnings (E_WARNING)
832
- $errorContent = "<div class='error'>Error: {$severity} - {$message} in {$file} on line {$line}</div>";
885
+ if (isAjaxOrXFileRequestOrRouteFile()) {
886
+ $errorContent = "Error: {$severity} - {$message} in {$file} on line {$line}";
887
+ } else {
888
+ $errorContent = "<div class='error'>Error: {$message} in {$file} on line {$line}</div>";
889
+ }
833
890
 
834
891
  // If needed, log it or output immediately based on severity
835
892
  if ($severity === E_WARNING || $severity === E_NOTICE) {
@@ -7,12 +7,145 @@ import { swaggerConfig } from "./swagger-config.js";
7
7
  import { getFileMeta } from "./utils.js";
8
8
  import prismaSchemaConfigJson from "./prisma-schema-config.json";
9
9
  import prompts from "prompts";
10
+ import { exit } from "process";
10
11
 
11
12
  const { __dirname } = getFileMeta();
12
13
  const prismaSchemaJsonPath = resolve(__dirname, "./prisma-schema.json");
13
14
 
15
+ type PrismaSchemaConfig = {
16
+ swaggerDocsDir: string;
17
+ skipDefaultName: string[];
18
+ skipByPropertyValue: Record<string, boolean>;
19
+ skipFields: string[];
20
+ generateEndpoints: boolean;
21
+ generatePhpClasses: boolean;
22
+ };
23
+
24
+ type Field = {
25
+ name: string;
26
+ kind: string;
27
+ isList: boolean;
28
+ isRequired: boolean;
29
+ isUnique: boolean;
30
+ isId: boolean;
31
+ isReadOnly: boolean;
32
+ hasDefaultValue: boolean;
33
+ type: string;
34
+ isGenerated: boolean;
35
+ isUpdatedAt: boolean;
36
+ default?: {
37
+ name: string;
38
+ args: any[];
39
+ };
40
+ };
41
+
42
+ function shouldSkipField(field: Field): boolean {
43
+ const config: PrismaSchemaConfig = prismaSchemaConfigJson;
44
+
45
+ if (field.kind === "object") {
46
+ return true;
47
+ }
48
+
49
+ // Skip fields that are explicitly marked to be skipped by name
50
+ if (config.skipFields && config.skipFields.includes(field.name)) {
51
+ return true;
52
+ }
53
+
54
+ // Skip fields based on specific property values defined in skipByPropertyValue
55
+ for (const [property, value] of Object.entries(config.skipByPropertyValue)) {
56
+ if ((field as any)[property] === value) {
57
+ return true;
58
+ }
59
+ }
60
+
61
+ // Skip ID fields with auto-creation during creation
62
+ if (config.skipDefaultName.includes(field.default?.name || "")) {
63
+ return true;
64
+ }
65
+
66
+ return false;
67
+ }
68
+
69
+ // Function to determine an appropriate example based on the field type for Prisma ORM
70
+ function getExampleValue(field: Field): any {
71
+ const fieldType = field.type.toLowerCase();
72
+
73
+ if (field.isId) {
74
+ // Provide examples based on common ID types
75
+ if (field.hasDefaultValue) {
76
+ switch (field.default?.name.toLowerCase()) {
77
+ case "uuid(4)":
78
+ return `"123e4567-e89b-12d3-a456-426614174000"`; // Example for UUID IDs
79
+ case "cuid":
80
+ return `"cjrscj5d40002s6s0b6nq9jfg"`; // Example for CUID IDs
81
+ case "autoincrement":
82
+ return 1; // Example for auto-increment IDs
83
+ default:
84
+ return `"${field.name}"`; // Default example for unknown ID types
85
+ }
86
+ } else {
87
+ switch (fieldType) {
88
+ case "int":
89
+ case "bigint":
90
+ return 123; // Example for integer and BigInt IDs
91
+ default:
92
+ return `"${field.name}"`; // Default example for unknown ID types
93
+ }
94
+ }
95
+ }
96
+
97
+ // Example values for other field types
98
+ switch (fieldType) {
99
+ case "int":
100
+ case "bigint":
101
+ return 123; // Example for integer and BigInt types
102
+ case "float":
103
+ case "decimal":
104
+ return 123.45; // Example for floating-point types
105
+ case "boolean":
106
+ return true; // Example for boolean types
107
+ case "string":
108
+ return `"${field.name}"`; // Example for string types
109
+ case "datetime":
110
+ return `"2024-01-01T00:00:00Z"`; // Example for date/time types
111
+ case "json":
112
+ return `{"key": "value"}`; // Example for JSON type
113
+ case "uuid":
114
+ return `"123e4567-e89b-12d3-a456-426614174000"`; // Example for UUID type
115
+ case "cuid":
116
+ return `"cjrscj5d40002s6s0b6nq9jfg"`; // Example for CUID type
117
+ default:
118
+ return `"${field.name}"`; // Default example for unrecognized types
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Convert a Prisma field type to a Swagger-supported type.
124
+ * @param prismaType The type of the field as defined in the Prisma schema
125
+ * @returns A Swagger-compatible type
126
+ */
127
+ function convertPrismaTypeToSwaggerType(prismaType: string): string {
128
+ // Map Prisma types to Swagger-compatible types
129
+ const typeMapping: Record<string, string> = {
130
+ String: "string",
131
+ Int: "integer",
132
+ BigInt: "integer",
133
+ Float: "number",
134
+ Decimal: "number",
135
+ Boolean: "boolean",
136
+ DateTime: "string", // For Swagger, we use "string" with format date-time
137
+ Json: "object",
138
+ UUID: "string",
139
+ CUID: "string",
140
+ Bytes: "string", // Can be represented as base64 strings in Swagger
141
+ };
142
+
143
+ // Default to "string" if the type is not found in the mapping
144
+ return typeMapping[prismaType] || "string";
145
+ }
146
+
14
147
  // Function to generate properties for Swagger annotations
15
- function generateProperties(fields: any[]): {
148
+ function generateProperties(fields: Field[]): {
16
149
  properties: string;
17
150
  allProperties: string;
18
151
  } {
@@ -24,24 +157,21 @@ function generateProperties(fields: any[]): {
24
157
  return;
25
158
  }
26
159
 
27
- const example = field.example || (field.isId ? 1 : `"${field.name}"`);
160
+ const example = getExampleValue(field);
161
+ const fieldType = convertPrismaTypeToSwaggerType(field.type); // Convert Prisma type to Swagger type
28
162
  allProperties += `
29
- * ${field.name}:
30
- * type: ${field.type.toLowerCase()}
31
- * example: ${example}`;
163
+ * ${field.name}:
164
+ * type: ${fieldType}
165
+ * example: ${example}`;
32
166
 
33
- if (prismaSchemaConfigJson.skipFields.includes(field.name)) {
34
- return;
35
- }
36
-
37
- if (prismaSchemaConfigJson.skipDefaultName.includes(field.default?.name)) {
167
+ if (shouldSkipField(field)) {
38
168
  return;
39
169
  }
40
170
 
41
171
  properties += `
42
- * ${field.name}:
43
- * type: ${field.type.toLowerCase()}
44
- * example: ${example}`;
172
+ * ${field.name}:
173
+ * type: ${fieldType}
174
+ * example: ${example}`;
45
175
  });
46
176
 
47
177
  return { properties, allProperties };
@@ -54,170 +184,143 @@ function toKebabCase(str: string): string {
54
184
  .toLowerCase();
55
185
  }
56
186
 
187
+ // Function to find the ID field from the model
188
+ function getIdField(fields: Field[]): Field | undefined {
189
+ return fields.find((field) => field.isId);
190
+ }
191
+
57
192
  // Function to generate Swagger annotation for a CRUD operation
58
- function generateSwaggerAnnotation(modelName: string, fields: any[]): string {
193
+ function generateSwaggerAnnotation(modelName: string, fields: Field[]): string {
194
+ // Extract the ID field dynamically
195
+ const idField = getIdField(fields);
196
+ if (!idField) {
197
+ throw new Error(`No ID field found for model: ${modelName}`);
198
+ }
199
+
200
+ const idFieldName = idField.name;
201
+ const idFieldType = convertPrismaTypeToSwaggerType(idField.type); // Convert Prisma type to Swagger type
59
202
  const { properties, allProperties } = generateProperties(fields);
60
203
  const kebabCaseModelName = toKebabCase(modelName);
61
204
 
62
205
  return `/**
63
- * @swagger
64
- * tags:
65
- * name: ${modelName}
66
- * description: ${modelName} management API
67
- */
68
-
69
- /**
70
- * @swagger
71
- * /${kebabCaseModelName}:
72
- * get:
73
- * summary: Retrieve a list of ${modelName}
74
- * tags:
75
- * - ${modelName}
76
- * responses:
77
- * 200:
78
- * description: A list of ${modelName}
79
- * content:
80
- * application/json:
81
- * schema:
82
- * type: array
83
- * items:
84
- * type: object
85
- * properties:${allProperties}
86
- */
87
-
88
- /**
89
- * @swagger
90
- * /${kebabCaseModelName}/{id}:
91
- * get:
92
- * summary: Retrieve a single ${modelName} by ID
93
- * tags:
94
- * - ${modelName}
95
- * parameters:
96
- * - in: path
97
- * name: id
98
- * required: true
99
- * description: The ${modelName} ID
100
- * schema:
101
- * type: string
102
- * responses:
103
- * 200:
104
- * description: A single ${modelName} object
105
- * content:
106
- * application/json:
107
- * schema:
108
- * type: object
109
- * properties:${allProperties}
110
- * 404:
111
- * description: ${modelName} not found
112
- */
113
-
114
- /**
115
- * @swagger
116
- * /${kebabCaseModelName}/create:
117
- * post:
118
- * summary: Create a new ${modelName}
119
- * tags:
120
- * - ${modelName}
121
- * requestBody:
122
- * required: true
123
- * content:
124
- * application/json:
125
- * schema:
126
- * type: object
127
- * properties:${properties}
128
- * responses:
129
- * 201:
130
- * description: ${modelName} created successfully.
131
- */
132
-
133
- /**
134
- * @swagger
135
- * /${kebabCaseModelName}/update/{id}:
136
- * put:
137
- * summary: Update a ${modelName} by ID
138
- * tags:
139
- * - ${modelName}
140
- * parameters:
141
- * - in: path
142
- * name: id
143
- * required: true
144
- * description: The ${modelName} ID
145
- * schema:
146
- * type: string
147
- * requestBody:
148
- * required: true
149
- * content:
150
- * application/json:
151
- * schema:
152
- * type: object
153
- * properties:${properties}
154
- * responses:
155
- * 200:
156
- * description: ${modelName} updated successfully.
157
- * 404:
158
- * description: ${modelName} not found
159
- */
160
-
161
- /**
162
- * @swagger
163
- * /${kebabCaseModelName}/delete/{id}:
164
- * delete:
165
- * summary: Delete a ${modelName} by ID
166
- * tags:
167
- * - ${modelName}
168
- * parameters:
169
- * - in: path
170
- * name: id
171
- * required: true
172
- * description: The ${modelName} ID
173
- * schema:
174
- * type: string
175
- * responses:
176
- * 204:
177
- * description: ${modelName} successfully deleted
178
- * 404:
179
- * description: ${modelName} not found
180
- */
181
- `;
182
- }
183
-
184
- // Function to generate dynamic validation rules and request payloads
185
- function generateValidationAndPayload(fields: any[]) {
186
- let validations = "";
187
- let payload = "";
188
- let variableAssignments = "";
189
-
190
- fields.forEach((field) => {
191
- if (field.kind === "object") return; // Skip relations for now
192
-
193
- // Skip fields that are explicitly marked to be skipped
194
- if (prismaSchemaConfigJson.skipFields.includes(field.name)) {
195
- return;
196
- }
197
-
198
- // Skip ID fields with auto-creation during creation
199
- if (prismaSchemaConfigJson.skipDefaultName.includes(field.default?.name)) {
200
- return;
201
- }
202
-
203
- // Define variable assignments
204
- const variableName = field.name;
205
- variableAssignments += `$${variableName} = Request::$params->${variableName} ?? null;\n`;
206
-
207
- // Dynamic validation for required fields (excluding skipped fields)
208
- if (field.isRequired) {
209
- const fieldType = field.type.toLowerCase();
210
- validations += `
211
- if (!Validator::${fieldType}($${variableName})) {
212
- Boom::badRequest("Invalid ${variableName}")->toResponse();
213
- }`;
214
- }
215
-
216
- // Prepare payload dynamically
217
- payload += `'${variableName}' => $${variableName},\n `;
218
- });
219
-
220
- return { validations, payload, variableAssignments };
206
+ * @swagger
207
+ * tags:
208
+ * name: ${modelName}
209
+ * description: ${modelName} management API
210
+ */
211
+
212
+ /**
213
+ * @swagger
214
+ * /${kebabCaseModelName}:
215
+ * get:
216
+ * summary: Retrieve a list of ${modelName}
217
+ * tags:
218
+ * - ${modelName}
219
+ * responses:
220
+ * 200:
221
+ * description: A list of ${modelName}
222
+ * content:
223
+ * application/json:
224
+ * schema:
225
+ * type: array
226
+ * items:
227
+ * type: object
228
+ * properties:${allProperties}
229
+ */
230
+
231
+ /**
232
+ * @swagger
233
+ * /${kebabCaseModelName}/{${idFieldName}}:
234
+ * get:
235
+ * summary: Retrieve a single ${modelName} by ${idFieldName}
236
+ * tags:
237
+ * - ${modelName}
238
+ * parameters:
239
+ * - in: path
240
+ * name: ${idFieldName}
241
+ * required: true
242
+ * description: The ${modelName} ${idFieldName}
243
+ * schema:
244
+ * type: ${idFieldType}
245
+ * responses:
246
+ * 200:
247
+ * description: A single ${modelName} object
248
+ * content:
249
+ * application/json:
250
+ * schema:
251
+ * type: object
252
+ * properties:${allProperties}
253
+ * 404:
254
+ * description: ${modelName} not found
255
+ */
256
+
257
+ /**
258
+ * @swagger
259
+ * /${kebabCaseModelName}/create:
260
+ * post:
261
+ * summary: Create a new ${modelName}
262
+ * tags:
263
+ * - ${modelName}
264
+ * requestBody:
265
+ * required: true
266
+ * content:
267
+ * application/json:
268
+ * schema:
269
+ * type: object
270
+ * properties:${properties}
271
+ * responses:
272
+ * 201:
273
+ * description: ${modelName} created successfully.
274
+ */
275
+
276
+ /**
277
+ * @swagger
278
+ * /${kebabCaseModelName}/update/{${idFieldName}}:
279
+ * put:
280
+ * summary: Update a ${modelName} by ${idFieldName}
281
+ * tags:
282
+ * - ${modelName}
283
+ * parameters:
284
+ * - in: path
285
+ * name: ${idFieldName}
286
+ * required: true
287
+ * description: The ${modelName} ${idFieldName}
288
+ * schema:
289
+ * type: ${idFieldType}
290
+ * requestBody:
291
+ * required: true
292
+ * content:
293
+ * application/json:
294
+ * schema:
295
+ * type: object
296
+ * properties:${properties}
297
+ * responses:
298
+ * 200:
299
+ * description: ${modelName} updated successfully.
300
+ * 404:
301
+ * description: ${modelName} not found
302
+ */
303
+
304
+ /**
305
+ * @swagger
306
+ * /${kebabCaseModelName}/delete/{${idFieldName}}:
307
+ * delete:
308
+ * summary: Delete a ${modelName} by ${idFieldName}
309
+ * tags:
310
+ * - ${modelName}
311
+ * parameters:
312
+ * - in: path
313
+ * name: ${idFieldName}
314
+ * required: true
315
+ * description: The ${modelName} ${idFieldName}
316
+ * schema:
317
+ * type: ${idFieldType}
318
+ * responses:
319
+ * 204:
320
+ * description: ${modelName} successfully deleted
321
+ * 404:
322
+ * description: ${modelName} not found
323
+ */`;
221
324
  }
222
325
 
223
326
  // Function to generate dynamic ID validation logic for update and find-by-ID routes
@@ -241,6 +344,10 @@ function generateEndpoints(modelName: string, fields: any[]): void {
241
344
  modelName.charAt(0).toLowerCase() + modelName.slice(1);
242
345
  const baseDir = `src/app/${kebabCasedModelName}`;
243
346
  const idField = fields.find((field) => field.isId);
347
+ const fieldsToCreateAndUpdate = fields.filter(
348
+ (field) => shouldSkipField(field) === false
349
+ );
350
+ const idFieldName = idField.name;
244
351
  const baseDirPath = resolve(__dirname, `../${baseDir}`);
245
352
 
246
353
  mkdirSync(baseDirPath, { recursive: true });
@@ -279,7 +386,7 @@ ${idValidationLogic}
279
386
 
280
387
  $${camelCaseModelName} = $prisma->${camelCaseModelName}->findUnique([
281
388
  'where' => [
282
- 'id' => $id
389
+ '${idFieldName}' => $id
283
390
  ]
284
391
  ]);
285
392
 
@@ -295,15 +402,10 @@ echo json_encode($${camelCaseModelName});`;
295
402
  );
296
403
 
297
404
  // Endpoint: POST /{kebabCasedModelName}/create
298
- const {
299
- validations: createValidations,
300
- payload: createPayload,
301
- variableAssignments,
302
- } = generateValidationAndPayload(fields);
303
-
304
405
  const createDir = `${baseDir}/create`;
305
406
  mkdirSync(resolve(__dirname, `../${createDir}`), { recursive: true });
306
407
  const createRoutePath = `${createDir}/route.php`;
408
+
307
409
  const createRouteContent = `<?php
308
410
 
309
411
  use Lib\\Prisma\\Classes\\Prisma;
@@ -312,26 +414,57 @@ use Lib\\Headers\\Boom;
312
414
  use Lib\\Request;
313
415
 
314
416
  $prisma = Prisma::getInstance();
315
- ${
316
- variableAssignments.length > 0
317
- ? variableAssignments
318
- : "// Your custom variable assignments here"
319
- }
320
- ${
321
- createValidations.length > 0
322
- ? createValidations
323
- : "\n// Your custom validation logic here"
417
+
418
+ // Define fields with their types, required status, and validation functions
419
+ $fieldsWithTypesAndStatus = [
420
+ ${fieldsToCreateAndUpdate
421
+ .map(
422
+ (field) =>
423
+ ` '${field.name}' => [
424
+ 'type' => '${field.type.toLowerCase()}',
425
+ 'required' => ${field.isRequired ? "true" : "false"},
426
+ 'validate' => fn($value) => is_null($value) || $value === '' || Validator::${field.type.toLowerCase()}($value)
427
+ ]`
428
+ )
429
+ .join(",\n")}
430
+ ];
431
+
432
+ $data = [];
433
+ foreach ($fieldsWithTypesAndStatus as $field => $details) {
434
+ $isRequired = $details['required'];
435
+ $type = $details['type'];
436
+ $validationFn = $details['validate'];
437
+
438
+ // Check if the field is required and missing in the request
439
+ if ($isRequired && !isset(Request::$params->$field)) {
440
+ Boom::badRequest("Missing {$field}")->toResponse();
441
+ }
442
+
443
+ // Check if the field is present in the request
444
+ if (isset(Request::$params->$field)) {
445
+ $value = Request::$params->$field;
446
+
447
+ // Validate the field using the validation function
448
+ if (!$validationFn($value)) {
449
+ Boom::badRequest("Invalid {$field}", ["Expected type '{$type}'"])->toResponse();
450
+ }
451
+
452
+ // Assign the validated value to the data array
453
+ $data[$field] = $value;
454
+ }
324
455
  }
325
456
 
457
+ // Create the new record using the Prisma instance
326
458
  $new${modelName} = $prisma->${camelCaseModelName}->create([
327
- 'data' => [
328
- ${createPayload}
329
- ]
459
+ 'data' => $data
330
460
  ]);
331
461
 
462
+ // Handle potential internal server error
332
463
  if (!$new${modelName}) {
333
464
  Boom::internal()->toResponse();
334
465
  }
466
+
467
+ // Return the newly created record in JSON format
335
468
  echo json_encode($new${modelName});`;
336
469
 
337
470
  writeFileSync(
@@ -341,12 +474,10 @@ echo json_encode($new${modelName});`;
341
474
  );
342
475
 
343
476
  // Endpoint: PUT /{kebabCasedModelName}/update/{id}
344
- const { validations: updateValidations, payload: updatePayload } =
345
- generateValidationAndPayload(fields);
346
-
347
477
  const updateDir = `${baseDir}/update/[id]`;
348
478
  mkdirSync(resolve(__dirname, `../${updateDir}`), { recursive: true });
349
479
  const updateRoutePath = `${updateDir}/route.php`;
480
+
350
481
  const updateRouteContent = `<?php
351
482
 
352
483
  use Lib\\Prisma\\Classes\\Prisma;
@@ -356,29 +487,65 @@ use Lib\\Request;
356
487
 
357
488
  $prisma = Prisma::getInstance();
358
489
  $id = Request::$dynamicParams->id ?? null;
359
- ${
360
- variableAssignments.length > 0
361
- ? variableAssignments
362
- : "// Your custom variable assignments here"
490
+
491
+ // Perform validation for the ID
492
+ if (!Validator::int($id)) {
493
+ Boom::badRequest("Invalid id")->toResponse();
363
494
  }
364
- ${idValidationLogic}
365
- ${
366
- updateValidations.length > 0
367
- ? updateValidations
368
- : "\n// Your custom validation logic here"
495
+
496
+ // Define fields with their types, required status, and validation functions
497
+ $fieldsWithTypesAndStatus = [
498
+ ${fieldsToCreateAndUpdate
499
+ .map(
500
+ (field) =>
501
+ ` '${field.name}' => [
502
+ 'type' => '${field.type.toLowerCase()}',
503
+ 'required' => ${field.isRequired ? "true" : "false"},
504
+ 'validate' => fn($value) => is_null($value) || $value === '' || Validator::${field.type.toLowerCase()}($value)
505
+ ]`
506
+ )
507
+ .join(",\n")}
508
+ ];
509
+
510
+ $data = [];
511
+ foreach ($fieldsWithTypesAndStatus as $field => $details) {
512
+ $isRequired = $details['required'];
513
+ $type = $details['type'];
514
+ $validationFn = $details['validate'];
515
+
516
+ // Check if the field is required and missing in the request
517
+ if ($isRequired && !isset(Request::$params->$field)) {
518
+ Boom::badRequest("Missing {$field}")->toResponse();
519
+ }
520
+
521
+ // Check if the field is present in the request
522
+ if (isset(Request::$params->$field)) {
523
+ $value = Request::$params->$field;
524
+
525
+ // Validate the field using the validation function
526
+ if (!$validationFn($value)) {
527
+ Boom::badRequest("Invalid {$field}", ["Expected type '{$type}'"])->toResponse();
528
+ }
529
+
530
+ // Assign the validated value to the data array
531
+ $data[$field] = $value;
532
+ }
369
533
  }
370
534
 
535
+ // Update the record
371
536
  $updated${modelName} = $prisma->${camelCaseModelName}->update([
372
- 'where' => ['id' => $id],
373
- 'data' => [
374
- ${updatePayload}
375
- ]
537
+ 'where' => ['${idFieldName}' => $id],
538
+ 'data' => $data,
376
539
  ]);
377
540
 
541
+ // Handle potential internal server error
378
542
  if (!$updated${modelName}) {
379
- Boom::notFound()->toResponse();
543
+ Boom::notFound()->toResponse();
380
544
  }
381
- echo json_encode($updated${modelName});`;
545
+
546
+ // Return the updated record in JSON format
547
+ echo json_encode($updated${modelName});
548
+ `;
382
549
 
383
550
  writeFileSync(
384
551
  resolve(__dirname, `../${updateRoutePath}`),
@@ -403,7 +570,7 @@ ${idValidationLogic}
403
570
 
404
571
  $deleted${modelName} = $prisma->${camelCaseModelName}->delete([
405
572
  'where' => [
406
- 'id' => $id
573
+ '${idFieldName}' => $id
407
574
  ]
408
575
  ]);
409
576
 
@@ -421,6 +588,33 @@ echo json_encode($deleted${modelName});`;
421
588
 
422
589
  async function promptUserForGenerationOptions() {
423
590
  const response = await prompts([
591
+ {
592
+ type: "confirm",
593
+ name: "generateApisOnly",
594
+ message: "Do you want to generate swagger docs only?",
595
+ initial: false,
596
+ },
597
+ ]);
598
+
599
+ // If the user wants to generate only Swagger docs
600
+ if (response.generateApisOnly) {
601
+ // Update the configuration
602
+ prismaSchemaConfigJson.generateSwaggerDocsOnly = true;
603
+
604
+ // Save the updated settings back to the JSON file if needed
605
+ writeFileSync(
606
+ resolve(__dirname, "./prisma-schema-config.json"),
607
+ JSON.stringify(prismaSchemaConfigJson, null, 2),
608
+ "utf-8"
609
+ );
610
+
611
+ // Generate Swagger docs and exit
612
+ await swaggerConfig();
613
+ exit(0); // Exit the process here
614
+ }
615
+
616
+ // If the user did not select generateApisOnly, ask for other options
617
+ const otherResponses = await prompts([
424
618
  {
425
619
  type: "confirm",
426
620
  name: "generateEndpoints",
@@ -435,11 +629,12 @@ async function promptUserForGenerationOptions() {
435
629
  },
436
630
  ]);
437
631
 
438
- // Update the configuration based on user input
439
- prismaSchemaConfigJson.generateEndpoints = response.generateEndpoints;
440
- prismaSchemaConfigJson.generatePhpClasses = response.generatePhpClasses;
632
+ // Update the configuration based on other responses
633
+ prismaSchemaConfigJson.generateSwaggerDocsOnly = false;
634
+ prismaSchemaConfigJson.generateEndpoints = otherResponses.generateEndpoints;
635
+ prismaSchemaConfigJson.generatePhpClasses = otherResponses.generatePhpClasses;
441
636
 
442
- // Optionally, you can save the updated settings back to the JSON file
637
+ // Save the updated settings back to the JSON file
443
638
  writeFileSync(
444
639
  resolve(__dirname, "./prisma-schema-config.json"),
445
640
  JSON.stringify(prismaSchemaConfigJson, null, 2),
@@ -2,7 +2,7 @@ import { createProxyMiddleware } from "http-proxy-middleware";
2
2
  import { writeFileSync } from "fs";
3
3
  import chokidar from "chokidar";
4
4
  import browserSync, { BrowserSyncInstance } from "browser-sync";
5
- import prismaPhpConfig from "../prisma-php.json";
5
+ import prismaPhpConfigJson from "../prisma-php.json";
6
6
  import { generateFileListJson } from "./files-list.js";
7
7
  import { join } from "path";
8
8
  import { getFileMeta } from "./utils.js";
@@ -43,7 +43,7 @@ bs.init(
43
43
  next();
44
44
  },
45
45
  createProxyMiddleware({
46
- target: prismaPhpConfig.bsTarget,
46
+ target: prismaPhpConfigJson.bsTarget,
47
47
  changeOrigin: true,
48
48
  pathRewrite: {},
49
49
  }),
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "swaggerDocsDir": "src/app/swagger-docs/apis",
3
- "skipDefaultName": ["autoincrement", "cuid", "uuid", "now"],
4
- "skipFields": ["updatedAt"],
3
+ "skipDefaultName": [
4
+ "autoincrement",
5
+ "cuid",
6
+ "uuid",
7
+ "now"
8
+ ],
9
+ "skipByPropertyValue": {
10
+ "isUpdatedAt": true
11
+ },
12
+ "skipFields": [],
13
+ "generateSwaggerDocsOnly": true,
5
14
  "generateEndpoints": false,
6
- "generatePhpClasses": true
7
- }
15
+ "generatePhpClasses": false
16
+ }
@@ -1,6 +1,6 @@
1
1
  import { existsSync, unlink, writeFile } from "fs";
2
2
  import { join, basename, dirname, normalize, sep } from "path";
3
- import prismaPhpConfig from "../prisma-php.json";
3
+ import prismaPhpConfigJson from "../prisma-php.json";
4
4
  import { getFileMeta } from "./utils.js";
5
5
 
6
6
  const { __dirname } = getFileMeta();
@@ -15,20 +15,23 @@ function updateProjectNameInConfig(
15
15
  const filePathDir = dirname(filePath);
16
16
 
17
17
  // Update the projectName directly in the imported config
18
- prismaPhpConfig.projectName = newProjectName;
18
+ prismaPhpConfigJson.projectName = newProjectName;
19
19
 
20
20
  // Update other paths
21
- prismaPhpConfig.projectRootPath = filePathDir;
21
+ prismaPhpConfigJson.projectRootPath = filePathDir;
22
22
 
23
- const targetPath = getTargetPath(filePathDir, prismaPhpConfig.phpEnvironment);
23
+ const targetPath = getTargetPath(
24
+ filePathDir,
25
+ prismaPhpConfigJson.phpEnvironment
26
+ );
24
27
 
25
- prismaPhpConfig.bsTarget = `http://localhost${targetPath}`;
26
- prismaPhpConfig.bsPathRewrite["^/"] = targetPath;
28
+ prismaPhpConfigJson.bsTarget = `http://localhost${targetPath}`;
29
+ prismaPhpConfigJson.bsPathRewrite["^/"] = targetPath;
27
30
 
28
31
  // Save the updated config back to the JSON file
29
32
  writeFile(
30
33
  filePath,
31
- JSON.stringify(prismaPhpConfig, null, 2),
34
+ JSON.stringify(prismaPhpConfigJson, null, 2),
32
35
  "utf8",
33
36
  (err) => {
34
37
  if (err) {
@@ -3,7 +3,8 @@ import { writeFileSync } from "fs";
3
3
  import { join } from "path";
4
4
  import chalk from "chalk";
5
5
  import { getFileMeta } from "./utils.js";
6
- import bsConnectionInfo from "./bs-config.json";
6
+ import bsConfigJson from "./bs-config.json";
7
+ import prismaPhpConfigJson from "../prisma-php.json";
7
8
 
8
9
  const { __dirname } = getFileMeta();
9
10
 
@@ -23,7 +24,7 @@ export async function swaggerConfig(): Promise<void> {
23
24
  },
24
25
  servers: [
25
26
  {
26
- url: bsConnectionInfo.local, // For Development
27
+ url: bsConfigJson.local, // For Development
27
28
  description: "Development Server",
28
29
  },
29
30
  {
@@ -65,5 +66,5 @@ export async function swaggerConfig(): Promise<void> {
65
66
  }
66
67
  }
67
68
 
68
- // Call the function to generate the Swagger JSON
69
- swaggerConfig();
69
+ if (prismaPhpConfigJson.swaggerDocs && !prismaPhpConfigJson.prisma)
70
+ swaggerConfig();
@@ -13,10 +13,10 @@ use Lib\Request;
13
13
 
14
14
  class Auth
15
15
  {
16
- public const PAYLOAD_NAME = 'role';
16
+ public const PAYLOAD_NAME = 'payload_name_8639D';
17
17
  public const ROLE_NAME = '';
18
- public const PAYLOAD_SESSION_KEY = 'payload_2183A';
19
- public const COOKIE_NAME = 'pphp_aut_token_D36E5';
18
+ public const PAYLOAD_SESSION_KEY = 'payload_session_key_2183A';
19
+ public const COOKIE_NAME = 'cookie_name_D36E5';
20
20
 
21
21
  private static ?Auth $instance = null;
22
22
  private const PPHPAUTH = 'pphpauth';
@@ -2,23 +2,69 @@
2
2
 
3
3
  namespace Lib;
4
4
 
5
+ class BSPathRewrite
6
+ {
7
+ public string $pattern;
8
+ public string $replacement;
9
+
10
+ public function __construct(array $data)
11
+ {
12
+ $this->pattern = $data['pattern'] ?? '';
13
+ $this->replacement = $data['replacement'] ?? '';
14
+ }
15
+ }
16
+
17
+ class PrismaSettings
18
+ {
19
+ public string $projectName;
20
+ public string $projectRootPath;
21
+ public string $phpEnvironment;
22
+ public string $phpRootPathExe;
23
+ public string $phpGenerateClassPath;
24
+ public string $bsTarget;
25
+ public BSPathRewrite $bsPathRewrite;
26
+ public bool $backendOnly;
27
+ public bool $swaggerDocs;
28
+ public bool $tailwindcss;
29
+ public bool $websocket;
30
+ public bool $prisma;
31
+ public bool $docker;
32
+ public string $version;
33
+ public array $excludeFiles;
34
+
35
+ public function __construct(array $data)
36
+ {
37
+ $this->projectName = $data['projectName'] ?? '';
38
+ $this->projectRootPath = $data['projectRootPath'] ?? '';
39
+ $this->phpEnvironment = $data['phpEnvironment'] ?? '';
40
+ $this->phpRootPathExe = $data['phpRootPathExe'] ?? '';
41
+ $this->phpGenerateClassPath = $data['phpGenerateClassPath'] ?? '';
42
+ $this->bsTarget = $data['bsTarget'] ?? '';
43
+ $this->bsPathRewrite = new BSPathRewrite($data['bsPathRewrite'] ?? []);
44
+ $this->backendOnly = $data['backendOnly'] ?? false;
45
+ $this->swaggerDocs = $data['swaggerDocs'] ?? true;
46
+ $this->tailwindcss = $data['tailwindcss'] ?? true;
47
+ $this->websocket = $data['websocket'] ?? true;
48
+ $this->prisma = $data['prisma'] ?? true;
49
+ $this->docker = $data['docker'] ?? false;
50
+ $this->version = $data['version'] ?? '';
51
+ $this->excludeFiles = $data['excludeFiles'] ?? [];
52
+ }
53
+ }
54
+
5
55
  class PrismaPHPSettings
6
56
  {
7
57
  /**
8
58
  * The settings from the prisma-php.json file.
9
59
  *
10
- * @var \stdClass
11
- * @access public
12
- * @static
60
+ * @var PrismaSettings
13
61
  */
14
- public static \ArrayObject $option;
62
+ public static PrismaSettings $option;
15
63
 
16
64
  /**
17
65
  * The list of route files from the files-list.json file.
18
66
  *
19
67
  * @var array
20
- * @access public
21
- * @static
22
68
  */
23
69
  public static array $routeFiles = [];
24
70
 
@@ -28,20 +74,28 @@ class PrismaPHPSettings
28
74
  self::$routeFiles = self::getRoutesFileList();
29
75
  }
30
76
 
31
- private static function getPrismaSettings(): \ArrayObject
77
+ /**
78
+ * Get Prisma settings from the JSON file.
79
+ *
80
+ * @return PrismaSettings
81
+ * @throws Exception if the JSON file cannot be decoded.
82
+ */
83
+ private static function getPrismaSettings(): PrismaSettings
32
84
  {
33
85
  $prismaPHPSettingsJson = DOCUMENT_PATH . '/prisma-php.json';
34
86
 
35
- if (file_exists($prismaPHPSettingsJson)) {
36
- $jsonContent = file_get_contents($prismaPHPSettingsJson);
37
- $decodedJson = json_decode($jsonContent, true);
87
+ if (!file_exists($prismaPHPSettingsJson)) {
88
+ throw new \Exception("Settings file not found: $prismaPHPSettingsJson");
89
+ }
90
+
91
+ $jsonContent = file_get_contents($prismaPHPSettingsJson);
92
+ $decodedJson = json_decode($jsonContent, true);
38
93
 
39
- if (json_last_error() === JSON_ERROR_NONE) {
40
- return new \ArrayObject($decodedJson, \ArrayObject::ARRAY_AS_PROPS);
41
- } else {
42
- return new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
43
- }
94
+ if (json_last_error() !== JSON_ERROR_NONE) {
95
+ throw new \Exception("Failed to decode JSON: " . json_last_error_msg());
44
96
  }
97
+
98
+ return new PrismaSettings($decodedJson);
45
99
  }
46
100
 
47
101
  private static function getRoutesFileList(): array
@@ -195,8 +195,36 @@ class Request
195
195
  */
196
196
  private static function isAjaxRequest(): bool
197
197
  {
198
- return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
199
- || in_array(strtoupper(self::$method), ['POST', 'PUT', 'PATCH', 'DELETE']);
198
+ $isAjax = false;
199
+
200
+ // Check for standard AJAX header
201
+ if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
202
+ $isAjax = true;
203
+ }
204
+
205
+ // Check for common AJAX content types
206
+ if (!empty($_SERVER['CONTENT_TYPE'])) {
207
+ $ajaxContentTypes = [
208
+ 'application/json',
209
+ 'application/x-www-form-urlencoded',
210
+ 'multipart/form-data',
211
+ ];
212
+
213
+ foreach ($ajaxContentTypes as $contentType) {
214
+ if (strpos($_SERVER['CONTENT_TYPE'], $contentType) !== false) {
215
+ $isAjax = true;
216
+ break;
217
+ }
218
+ }
219
+ }
220
+
221
+ // Check for common AJAX request methods
222
+ $ajaxMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
223
+ if (in_array(strtoupper($_SERVER['REQUEST_METHOD']), $ajaxMethods)) {
224
+ $isAjax = true;
225
+ }
226
+
227
+ return $isAjax;
200
228
  }
201
229
 
202
230
  /**
@@ -217,8 +245,8 @@ class Request
217
245
  {
218
246
  $serverFetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? '';
219
247
  if (isset($serverFetchSite) && $serverFetchSite === 'same-origin') {
220
- $headers = getallheaders();
221
- return isset($headers['http_pphp_x_file_request']) && strtolower($headers['http_pphp_x_file_request']) === 'true';
248
+ $headers = array_change_key_case(getallheaders(), CASE_LOWER);
249
+ return isset($headers['http_pphp_x_file_request']) && $headers['http_pphp_x_file_request'] === 'true';
222
250
  }
223
251
 
224
252
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "1.26.503",
3
+ "version": "1.26.504",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",