generator-pninja 1.0.3 → 1.0.4

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.
@@ -116,7 +116,7 @@ export default class extends Generator {
116
116
  type: "list",
117
117
  name: "dbms",
118
118
  message: "Your DBMS",
119
- default: "sqlite",
119
+ default: this.config.get('dbms') || 'sqlite',
120
120
  choices: [
121
121
  {
122
122
  name: `SQLite ${colors.dim('(Lightweight, serverless, file-based)')}`,
@@ -16,6 +16,12 @@ Start your **development client** (http://localhost:5173/) with:
16
16
  npm run client
17
17
  ```
18
18
 
19
+ Alternatively, you can start both the **development client and server** with:
20
+
21
+ ```sh
22
+ npm run dev
23
+ ```
24
+
19
25
  To **initialize** the database:
20
26
 
21
27
  ```sh
@@ -7,6 +7,7 @@
7
7
  "dependencies": {
8
8
  },
9
9
  "scripts": {
10
+ "dev": "concurrently \"npm run server\" \"npm run client\"",
10
11
  "client": "cd client && npm i --loglevel=error && npm run dev",
11
12
  "dump-server": "cd server && php artisan dump-server",
12
13
  "migrate": "cd server && php artisan migrate",
@@ -24,5 +25,8 @@
24
25
  <% } -%>
25
26
  "seed": "cd server && php artisan db:seed",
26
27
  "server": "cd server && php artisan serve"
27
- }
28
+ },
29
+ "devDependencies": {
30
+ "concurrently": "^9.2.1"
31
+ }
28
32
  }
@@ -43,6 +43,7 @@ export default class AuthGenerator extends Generator {
43
43
  type: 'checkbox',
44
44
  name: 'languages',
45
45
  message: 'Please choose additional languages to install',
46
+ default: this.config.get('languages') || [],
46
47
  choices: (answers) => {
47
48
  return getAvailableLanguages(answers.nativeLanguage);
48
49
  },
@@ -181,6 +181,8 @@ interface <%= entity.name %>FormProps {
181
181
  onCancel: () => void;
182
182
  }
183
183
 
184
+ const canDelete = () => true; // Placeholder for permission logic
185
+
184
186
  export function <%= entity.name %>Form({ <%= to.camel(entity.name)%>, onSubmit, onCancel }: <%= entity.name%>FormProps) {
185
187
  const { t } = useTranslation();
186
188
  useAutoShortcuts();
@@ -392,7 +394,7 @@ export function <%= entity.name %>Form({ <%= to.camel(entity.name)%>, onSubmit,
392
394
  <kbd className="kdb-shortcut">{isMac ? '⌘' : 'Ctrl'}</kbd>
393
395
  <kbd className="kdb-shortcut">↵</kbd>
394
396
  </button>
395
- {<%= to.camel(entity.name)-%>?.id && <<%= entity.name %>DeleteButton id={<%= to.camel(entity.name)-%>.id} />}
397
+ {<%= to.camel(entity.name)-%>?.id && canDelete() && <<%= entity.name %>DeleteButton id={<%= to.camel(entity.name)-%>.id} />}
396
398
  </div>
397
399
  </form>
398
400
  </FormProvider>
@@ -91,7 +91,10 @@
91
91
  }
92
92
  tbody {
93
93
  td {
94
- @apply px-6 py-4 truncate max-w-50;
94
+ @apply px-6 py-4;
95
+ &.truncate {
96
+ @apply truncate max-w-50;
97
+ }
95
98
  }
96
99
  tr {
97
100
  @apply odd:bg-slate-100 even:bg-slate-200 text-slate-900 odd:dark:bg-slate-700 even:dark:bg-slate-800 dark:text-slate-100;
@@ -148,6 +148,9 @@ export default function <%= entity.name %>List() {
148
148
  document.title = t('entities:<%-to.snake(entity.name)%>.<%- pluralize(entity.name)%>');
149
149
  }, [i18n.language, t]);
150
150
 
151
+ const canEdit = () => true; // Placeholder for permission logic
152
+ const canView = () => true; // Placeholder for permission logic
153
+
151
154
  return (
152
155
  <>
153
156
  <h1 className="text-2xl mb-4">{t('entities:<%-to.snake(entity.name)%>.<%- pluralize(entity.name)%>')}</h1>
@@ -190,7 +193,7 @@ export default function <%= entity.name %>List() {
190
193
  <% for(column of columns) { -%>
191
194
  <SortHeader field="<%-column%>" label={t('entities:<%-to.snake(entity.name)%>.<%-column%>')} />
192
195
  <% } -%>
193
- <% if (false) for(r of relatedEntities) { -%>
196
+ <% for(r of relatedEntities) { -%>
194
197
  <th scope="col">{t('entities:<%-to.snake(entity.name)%>.<%-r.field%>')}</th>
195
198
  <% } -%>
196
199
  <th scope="col">{t('actions.crud.actions')}</th>
@@ -220,7 +223,7 @@ export default function <%= entity.name %>List() {
220
223
  </td>
221
224
  <% } -%>
222
225
  <% } -%>
223
- <% if (false) for(r of relatedEntities) { -%>
226
+ <% for(r of relatedEntities) { -%>
224
227
  <td>
225
228
  <% if(r.isArray) { -%>
226
229
  {row?.<%-r.field%>?.map((row, index) => (<Fragment key={index}>{index !== 0 ? ', ': ''}<Link className="text-link" to={`/entities/<%=to.slug(pluralize(r.related))%>/view/${row?.id}`}>{row?.<%-to.snake(r.labelField)%>}</Link></Fragment>))}
@@ -231,8 +234,8 @@ export default function <%= entity.name %>List() {
231
234
  <% } -%>
232
235
  <td className="text-nowrap w-0 uppercase">
233
236
  <div className="flex space-x-2">
234
- <Link to={`/entities/<%=to.slug(pluralize(entity.name))%>/view/${row.id}`} className="text-link">{t('actions.crud.view')}</Link>
235
- <Link to={`/entities/<%=to.slug(pluralize(entity.name))%>/edit/${row.id}`} className="text-link">{t('actions.crud.edit')}</Link>
237
+ {canView() && <Link to={`/entities/<%=to.slug(pluralize(entity.name))%>/view/${row.id}`} className="text-link">{t('actions.crud.view')}</Link>}
238
+ {canEdit() && <Link to={`/entities/<%=to.slug(pluralize(entity.name))%>/edit/${row.id}`} className="text-link">{t('actions.crud.edit')}</Link>}
236
239
  </div>
237
240
  </td>
238
241
  </tr>
@@ -27,6 +27,8 @@ interface <%= entity.name %>ViewParams extends Record<string, string | undefined
27
27
  id: string;
28
28
  }
29
29
 
30
+ const canDelete = () => true; // Placeholder for permission logic
31
+
30
32
  export function <%= entity.name %>DataView({ data }: <%= entity.name %>DataViewParams) {
31
33
  const { t } = useTranslation();
32
34
  if (!data) return '';
@@ -163,7 +165,7 @@ export default function <%= entity.name %>View() {
163
165
  <Link to={`/entities/<%-to.slug(pluralize(entity.name))%>/edit/${id}`} className="primary-button">
164
166
  {t('actions.crud.edit')}
165
167
  </Link>
166
- <<%= entity.name %>DeleteButton id={id} />
168
+ {canDelete() && <<%= entity.name %>DeleteButton id={id} />}
167
169
  </div>
168
170
  )}
169
171
  </>
@@ -144,6 +144,7 @@ export default class EntityGenerator extends Generator {
144
144
 
145
145
  this.fs.copyTpl(this.templatePath("blobs/dummy.pdf"), this.destinationPath(`server/database/factories/dummy.pdf`));
146
146
  this.fs.copyTpl(this.templatePath("blobs/dummy.png"), this.destinationPath(`server/database/factories/dummy.png`));
147
+ this.fs.copyTpl(this.templatePath(".gitkeep.ejs"), this.destinationPath(`server/database/seeders/csv/.gitkeep`));
147
148
  this.fs.copyTpl(this.templatePath(".gitkeep.ejs"), this.destinationPath(`server/storage/app/private/uploads/.gitkeep`));
148
149
  this.fs.copyTpl(this.templatePath(".gitkeep.ejs"), this.destinationPath(`server/storage/app/public/uploads/.gitkeep`));
149
150
  this.fs.copyTpl(this.templatePath("SessionAuth.php.ejs"), this.destinationPath(`server/app/Http/Middleware/SessionAuth.php`));
@@ -3,6 +3,7 @@
3
3
  namespace Database\Seeders;
4
4
 
5
5
  use Illuminate\Database\Seeder;
6
+ use Illuminate\Support\Facades\File;
6
7
  <% entities.forEach(function(entity) { %>
7
8
  use App\Models\<%= entity.name %>;<% }); %>
8
9
  class DatabaseSeeder extends Seeder
@@ -12,12 +13,103 @@ class DatabaseSeeder extends Seeder
12
13
  */
13
14
  public function run(): void
14
15
  {
15
- <% entities.forEach(function(entity) { %> while(<%= entity.name %>::count() < <%= n %>) <%= entity.name %>::factory()->create();<%= "\n"%><% }); %>
16
- // Many-to-many
17
- <% manyToMany.forEach(function(rel) { %> <%= rel.fromEntity %>::all()->each(function ($entity) {
18
- $entity-><%= rel.relPropery %>()->attach(
19
- <%= rel.toEntity %>::all()->random(<%= Math.round(n/2) %>)->pluck('id')->toArray()
20
- );
21
- });
22
- <% }); %> }
23
- }
16
+ <% entities.forEach(function(entity) { -%>
17
+ $this->seedModel(<%= entity.name %>::class, '<%= entity.name %>.csv', <%= n %>);
18
+ <% }); -%>
19
+
20
+ // Many-to-many relationships
21
+ <% manyToMany.forEach(function(rel) { -%>
22
+ $this->seedManyToMany(<%= rel.fromEntity %>::class, '<%= rel.relPropery %>', <%= rel.toEntity %>::class, '<%= rel.fromEntity %><%= rel.toEntity %>.csv', <%- Math.round(n/2) %>);
23
+ <% }); -%>
24
+ }
25
+
26
+ /**
27
+ * Seed from CSV or factory
28
+ */
29
+ private function seedModel(string $modelClass, string $csvFilename, int $minCount): void
30
+ {
31
+ $csvPath = database_path("seeders/csv/{$csvFilename}");
32
+
33
+ if (File::exists($csvPath)) {
34
+ $this->seedFromCsv($modelClass, $csvPath);
35
+ } else {
36
+ // Use the factory
37
+ while ($modelClass::count() < $minCount) {
38
+ $modelClass::factory()->create();
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Load data from a CSV file
45
+ */
46
+ private function seedFromCsv(string $modelClass, string $csvPath): void
47
+ {
48
+ $csv = array_map('str_getcsv', file($csvPath));
49
+ $headers = array_shift($csv);
50
+
51
+ foreach ($csv as $row) {
52
+ $data = array_combine($headers, $row);
53
+
54
+ // Rimuovi campi vuoti o null
55
+ $data = array_filter($data, function($value) {
56
+ return $value !== '' && $value !== null;
57
+ });
58
+
59
+ $modelClass::create($data);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Seed many-to-many relationships
65
+ */
66
+ private function seedManyToMany(
67
+ string $modelClass,
68
+ string $relationName,
69
+ string $relatedClass,
70
+ string $csvFilename,
71
+ int $randomCount
72
+ ): void {
73
+ $csvPath = database_path("seeders/csv/{$csvFilename}");
74
+
75
+ if (File::exists($csvPath)) {
76
+ $this->seedManyToManyFromCsv($modelClass, $relationName, $csvPath);
77
+ } else {
78
+ // Use random associations
79
+ $modelClass::all()->each(function ($entity) use ($relationName, $relatedClass, $randomCount) {
80
+ $entity->$relationName()->attach(
81
+ $relatedClass::all()->random($randomCount)->pluck('id')->toArray()
82
+ );
83
+ });
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Load many-to-many relationships from CSV
89
+ * The CSV must have columns: model_id, related_id
90
+ */
91
+ private function seedManyToManyFromCsv(string $modelClass, string $relationName, string $csvPath): void
92
+ {
93
+ $csv = array_map('str_getcsv', file($csvPath));
94
+ $headers = array_shift($csv);
95
+
96
+ $grouped = [];
97
+ foreach ($csv as $row) {
98
+ $data = array_combine($headers, $row);
99
+ $modelId = $data['model_id'];
100
+ $relatedId = $data['related_id'];
101
+
102
+ if (!isset($grouped[$modelId])) {
103
+ $grouped[$modelId] = [];
104
+ }
105
+ $grouped[$modelId][] = $relatedId;
106
+ }
107
+
108
+ foreach ($grouped as $modelId => $relatedIds) {
109
+ $model = $modelClass::find($modelId);
110
+ if ($model) {
111
+ $model->$relationName()->attach($relatedIds);
112
+ }
113
+ }
114
+ }
115
+ }
@@ -27,11 +27,12 @@ class <%- className %>Controller extends Controller
27
27
  // use HandlesUserRoles;
28
28
 
29
29
  // private $viewerRole = 'viewer';
30
+ // private $editorRole = 'editor';
30
31
 
31
32
  public function index(Request $request)
32
33
  {
33
34
  try {
34
- // if (!$this->userHasRole($this->viewerRole)) return response()->json(['error' => 'Forbidden'], 403);
35
+ if (!$this->canViewData()) return response()->json(['error' => 'Forbidden'], 403);
35
36
  <% if (searchEngine !== 'null') {-%>
36
37
  $search = $request->has('search') ? ScoutQuerySanitizer::sanitize($request->get('search')) : null;
37
38
  <% } -%>
@@ -112,6 +113,7 @@ class <%- className %>Controller extends Controller
112
113
  public function show($id)
113
114
  {
114
115
  try {
116
+ if (!$this->canViewData()) return response()->json(['error' => 'Forbidden'], 403);
115
117
  <% if (withs) { -%>
116
118
  $<%- entityName %> = <%- className %>::with(<%- withs %>)->findOrFail($id);
117
119
  <% } else { -%>
@@ -126,6 +128,7 @@ class <%- className %>Controller extends Controller
126
128
  public function store(Request $request)
127
129
  {
128
130
  try {
131
+ if (!$this->canCreateData()) return response()->json(['error' => 'Forbidden'], 403);
129
132
  <% if(isEmpty(validationsStore)) {-%>
130
133
  $data = $request->all();
131
134
  <% } else {-%>
@@ -184,6 +187,8 @@ class <%- className %>Controller extends Controller
184
187
  public function update($id, Request $request)
185
188
  {
186
189
  try {
190
+ if (!$this->canModifyData()) return response()->json(['error' => 'Forbidden'], 403);
191
+
187
192
  <% if (withs) { -%>
188
193
  $<%- entityName %> = <%- className %>::with(<%- withs %>)->findOrFail($id);
189
194
  <% } else { -%>
@@ -257,6 +262,7 @@ class <%- className %>Controller extends Controller
257
262
  public function destroy($id)
258
263
  {
259
264
  try {
265
+ if (!$this->canDeleteData()) return response()->json(['error' => 'Forbidden'], 403);
260
266
  $<%- entityName %> = <%- className %>::findOrFail($id);
261
267
  $<%- entityName %>->delete();
262
268
  return response(null, 200);
@@ -292,4 +298,32 @@ class <%- className %>Controller extends Controller
292
298
  }
293
299
  }
294
300
  <% }-%>
301
+
302
+ private function canViewData()
303
+ {
304
+ // Implement role checking logic here
305
+ // return $this->userHasRole($this->viewerRole);
306
+ return true;
307
+ }
308
+
309
+ private function canCreateData()
310
+ {
311
+ // Implement role checking logic here
312
+ // return $this->userHasRole($this->editorRole);
313
+ return true;
314
+ }
315
+
316
+ private function canModifyData()
317
+ {
318
+ // Implement role checking logic here
319
+ // return $this->userHasRole($this->editorRole);
320
+ return true;
321
+ }
322
+
323
+ private function canDeleteData()
324
+ {
325
+ // Implement role checking logic here
326
+ // return $this->userHasRole($this->editorRole);
327
+ return true;
328
+ }
295
329
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-pninja",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Generator for PHP/Laravel + SQL + Vite + React applications",
5
5
  "type": "module",
6
6
  "files": [