@xbg.solutions/create-backend 1.0.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.
Files changed (32) hide show
  1. package/bin/create-backend.js +3 -0
  2. package/lib/cli.d.ts +12 -0
  3. package/lib/cli.js +55 -0
  4. package/lib/cli.js.map +1 -0
  5. package/lib/commands/add-util.d.ts +9 -0
  6. package/lib/commands/add-util.js +119 -0
  7. package/lib/commands/add-util.js.map +1 -0
  8. package/lib/commands/init.d.ts +11 -0
  9. package/lib/commands/init.js +372 -0
  10. package/lib/commands/init.js.map +1 -0
  11. package/lib/commands/sync.d.ts +10 -0
  12. package/lib/commands/sync.js +161 -0
  13. package/lib/commands/sync.js.map +1 -0
  14. package/lib/utils-registry.d.ts +25 -0
  15. package/lib/utils-registry.js +187 -0
  16. package/lib/utils-registry.js.map +1 -0
  17. package/package.json +38 -0
  18. package/src/project-template/__examples__/README.md +559 -0
  19. package/src/project-template/__examples__/blog-platform.model.ts +528 -0
  20. package/src/project-template/__examples__/communications-usage.ts +175 -0
  21. package/src/project-template/__examples__/ecommerce-store.model.ts +1200 -0
  22. package/src/project-template/__examples__/saas-multi-tenant.model.ts +798 -0
  23. package/src/project-template/__examples__/user.model.ts +221 -0
  24. package/src/project-template/__scripts__/deploy.js +115 -0
  25. package/src/project-template/__scripts__/generate.js +122 -0
  26. package/src/project-template/__scripts__/setup.js +425 -0
  27. package/src/project-template/__scripts__/validate.js +325 -0
  28. package/src/project-template/firebase.json +32 -0
  29. package/src/project-template/firestore.rules +12 -0
  30. package/src/project-template/functions/jest.config.js +49 -0
  31. package/src/project-template/functions/src/index.ts +46 -0
  32. package/src/project-template/functions/tsconfig.json +38 -0
@@ -0,0 +1,798 @@
1
+ /**
2
+ * SaaS Multi-Tenant Application Data Model
3
+ *
4
+ * A complete B2B SaaS platform with:
5
+ * - Multi-tenant workspace architecture
6
+ * - Team collaboration features
7
+ * - Role-based access control
8
+ * - Subscription management
9
+ * - Usage tracking and billing
10
+ * - API key management
11
+ *
12
+ * Features demonstrated:
13
+ * - Multi-tenancy patterns
14
+ * - Hierarchical permissions (Organization → Workspace → Project)
15
+ * - Subscription and billing
16
+ * - Usage metering
17
+ * - Invitations and team management
18
+ * - API authentication
19
+ *
20
+ * To use this model:
21
+ * 1. Copy to your project
22
+ * 2. Run: npm run generate __examples__/saas-multi-tenant.model.ts
23
+ * 3. Integrate payment provider (Stripe subscriptions)
24
+ * 4. Set up usage tracking
25
+ * 5. Configure billing webhooks
26
+ */
27
+
28
+ import { DataModelSpecification } from '../functions/src/generator/types';
29
+
30
+ export const SaaSMultiTenantModel: DataModelSpecification = {
31
+ entities: {
32
+ User: {
33
+ fields: {
34
+ email: {
35
+ type: 'email',
36
+ unique: true,
37
+ required: true,
38
+ },
39
+ firstName: {
40
+ type: 'string',
41
+ required: true,
42
+ },
43
+ lastName: {
44
+ type: 'string',
45
+ required: true,
46
+ },
47
+ avatarUrl: {
48
+ type: 'url',
49
+ required: false,
50
+ },
51
+ timezone: {
52
+ type: 'string',
53
+ default: 'UTC',
54
+ },
55
+ language: {
56
+ type: 'string',
57
+ default: 'en',
58
+ },
59
+ isVerified: {
60
+ type: 'boolean',
61
+ default: false,
62
+ },
63
+ lastActiveAt: {
64
+ type: 'timestamp',
65
+ required: false,
66
+ },
67
+ createdAt: {
68
+ type: 'timestamp',
69
+ required: true,
70
+ },
71
+ updatedAt: {
72
+ type: 'timestamp',
73
+ required: true,
74
+ },
75
+ },
76
+ relationships: {
77
+ memberships: {
78
+ type: 'one-to-many',
79
+ entity: 'OrganizationMember',
80
+ foreignKey: 'userId',
81
+ },
82
+ createdOrganizations: {
83
+ type: 'one-to-many',
84
+ entity: 'Organization',
85
+ foreignKey: 'ownerId',
86
+ },
87
+ },
88
+ access: {
89
+ create: ['public'],
90
+ read: ['self', 'team', 'admin'],
91
+ update: ['self', 'admin'],
92
+ delete: ['self', 'admin'],
93
+ },
94
+ validation: {
95
+ email: 'Must be a valid email',
96
+ },
97
+ businessRules: [
98
+ 'Email must be verified before joining organizations',
99
+ 'Users can belong to multiple organizations',
100
+ ],
101
+ indexes: [
102
+ { fields: ['email'], unique: true },
103
+ { fields: ['lastActiveAt'] },
104
+ ],
105
+ },
106
+
107
+ Organization: {
108
+ fields: {
109
+ name: {
110
+ type: 'string',
111
+ required: true,
112
+ },
113
+ slug: {
114
+ type: 'string',
115
+ unique: true,
116
+ required: true,
117
+ },
118
+ logoUrl: {
119
+ type: 'url',
120
+ required: false,
121
+ },
122
+ websiteUrl: {
123
+ type: 'url',
124
+ required: false,
125
+ },
126
+ ownerId: {
127
+ type: 'reference',
128
+ entity: 'User',
129
+ required: true,
130
+ },
131
+ subscriptionPlan: {
132
+ type: 'enum',
133
+ values: ['free', 'starter', 'professional', 'enterprise'],
134
+ default: 'free',
135
+ },
136
+ subscriptionStatus: {
137
+ type: 'enum',
138
+ values: ['trial', 'active', 'past_due', 'cancelled', 'suspended'],
139
+ default: 'trial',
140
+ },
141
+ stripeCustomerId: {
142
+ type: 'string',
143
+ required: false,
144
+ },
145
+ stripeSubscriptionId: {
146
+ type: 'string',
147
+ required: false,
148
+ },
149
+ trialEndsAt: {
150
+ type: 'timestamp',
151
+ required: false,
152
+ },
153
+ subscriptionEndsAt: {
154
+ type: 'timestamp',
155
+ required: false,
156
+ },
157
+ memberCount: {
158
+ type: 'number',
159
+ default: 1,
160
+ },
161
+ maxMembers: {
162
+ type: 'number',
163
+ default: 5,
164
+ },
165
+ workspaceCount: {
166
+ type: 'number',
167
+ default: 0,
168
+ },
169
+ maxWorkspaces: {
170
+ type: 'number',
171
+ default: 1,
172
+ },
173
+ apiCallsThisMonth: {
174
+ type: 'number',
175
+ default: 0,
176
+ },
177
+ apiCallsLimit: {
178
+ type: 'number',
179
+ default: 1000,
180
+ },
181
+ storageUsedGB: {
182
+ type: 'number',
183
+ default: 0,
184
+ },
185
+ storageLimit GB: {
186
+ type: 'number',
187
+ default: 5,
188
+ },
189
+ settings: {
190
+ type: 'json',
191
+ required: false,
192
+ },
193
+ createdAt: {
194
+ type: 'timestamp',
195
+ required: true,
196
+ },
197
+ updatedAt: {
198
+ type: 'timestamp',
199
+ required: true,
200
+ },
201
+ },
202
+ relationships: {
203
+ owner: {
204
+ type: 'many-to-one',
205
+ entity: 'User',
206
+ },
207
+ members: {
208
+ type: 'one-to-many',
209
+ entity: 'OrganizationMember',
210
+ foreignKey: 'organizationId',
211
+ },
212
+ workspaces: {
213
+ type: 'one-to-many',
214
+ entity: 'Workspace',
215
+ foreignKey: 'organizationId',
216
+ },
217
+ apiKeys: {
218
+ type: 'one-to-many',
219
+ entity: 'ApiKey',
220
+ foreignKey: 'organizationId',
221
+ },
222
+ usageLogs: {
223
+ type: 'one-to-many',
224
+ entity: 'UsageLog',
225
+ foreignKey: 'organizationId',
226
+ },
227
+ },
228
+ access: {
229
+ create: ['authenticated'],
230
+ read: ['member', 'admin'],
231
+ update: ['owner', 'admin'],
232
+ delete: ['owner', 'admin'],
233
+ },
234
+ validation: {
235
+ name: 'Must be 2-100 characters',
236
+ slug: 'Must be URL-safe and unique',
237
+ },
238
+ businessRules: [
239
+ 'Free plan limited to 5 members, 1 workspace',
240
+ 'Trial lasts 14 days',
241
+ 'Exceeding limits suspends account',
242
+ 'Owner cannot leave organization',
243
+ ],
244
+ indexes: [
245
+ { fields: ['slug'], unique: true },
246
+ { fields: ['ownerId'] },
247
+ { fields: ['subscriptionStatus'] },
248
+ { fields: ['stripeCustomerId'], unique: true },
249
+ ],
250
+ },
251
+
252
+ OrganizationMember: {
253
+ fields: {
254
+ organizationId: {
255
+ type: 'reference',
256
+ entity: 'Organization',
257
+ required: true,
258
+ },
259
+ userId: {
260
+ type: 'reference',
261
+ entity: 'User',
262
+ required: true,
263
+ },
264
+ role: {
265
+ type: 'enum',
266
+ values: ['owner', 'admin', 'member', 'guest'],
267
+ required: true,
268
+ },
269
+ invitedBy: {
270
+ type: 'reference',
271
+ entity: 'User',
272
+ required: false,
273
+ },
274
+ invitedAt: {
275
+ type: 'timestamp',
276
+ required: false,
277
+ },
278
+ joinedAt: {
279
+ type: 'timestamp',
280
+ required: true,
281
+ },
282
+ },
283
+ relationships: {
284
+ organization: {
285
+ type: 'many-to-one',
286
+ entity: 'Organization',
287
+ },
288
+ user: {
289
+ type: 'many-to-one',
290
+ entity: 'User',
291
+ },
292
+ },
293
+ access: {
294
+ create: ['admin', 'owner'],
295
+ read: ['member'],
296
+ update: ['admin', 'owner'],
297
+ delete: ['admin', 'owner', 'self'],
298
+ },
299
+ businessRules: [
300
+ 'Each user can only be a member once per organization',
301
+ 'Owner role is unique per organization',
302
+ 'Removing last owner is prevented',
303
+ ],
304
+ indexes: [
305
+ { fields: ['organizationId', 'userId'], unique: true },
306
+ { fields: ['userId'] },
307
+ ],
308
+ },
309
+
310
+ Workspace: {
311
+ fields: {
312
+ name: {
313
+ type: 'string',
314
+ required: true,
315
+ },
316
+ slug: {
317
+ type: 'string',
318
+ required: true,
319
+ },
320
+ description: {
321
+ type: 'text',
322
+ required: false,
323
+ },
324
+ organizationId: {
325
+ type: 'reference',
326
+ entity: 'Organization',
327
+ required: true,
328
+ },
329
+ createdById: {
330
+ type: 'reference',
331
+ entity: 'User',
332
+ required: true,
333
+ },
334
+ isArchived: {
335
+ type: 'boolean',
336
+ default: false,
337
+ },
338
+ projectCount: {
339
+ type: 'number',
340
+ default: 0,
341
+ },
342
+ settings: {
343
+ type: 'json',
344
+ required: false,
345
+ },
346
+ createdAt: {
347
+ type: 'timestamp',
348
+ required: true,
349
+ },
350
+ updatedAt: {
351
+ type: 'timestamp',
352
+ required: true,
353
+ },
354
+ },
355
+ relationships: {
356
+ organization: {
357
+ type: 'many-to-one',
358
+ entity: 'Organization',
359
+ },
360
+ createdBy: {
361
+ type: 'many-to-one',
362
+ entity: 'User',
363
+ },
364
+ projects: {
365
+ type: 'one-to-many',
366
+ entity: 'Project',
367
+ foreignKey: 'workspaceId',
368
+ },
369
+ members: {
370
+ type: 'one-to-many',
371
+ entity: 'WorkspaceMember',
372
+ foreignKey: 'workspaceId',
373
+ },
374
+ },
375
+ access: {
376
+ create: ['member'],
377
+ read: ['member'],
378
+ update: ['admin', 'workspace_admin'],
379
+ delete: ['admin', 'workspace_admin'],
380
+ },
381
+ validation: {
382
+ name: 'Must be 2-100 characters',
383
+ },
384
+ businessRules: [
385
+ 'Workspace slug must be unique within organization',
386
+ 'Archived workspaces are read-only',
387
+ ],
388
+ indexes: [
389
+ { fields: ['organizationId', 'slug'], unique: true },
390
+ { fields: ['createdById'] },
391
+ ],
392
+ },
393
+
394
+ WorkspaceMember: {
395
+ fields: {
396
+ workspaceId: {
397
+ type: 'reference',
398
+ entity: 'Workspace',
399
+ required: true,
400
+ },
401
+ userId: {
402
+ type: 'reference',
403
+ entity: 'User',
404
+ required: true,
405
+ },
406
+ role: {
407
+ type: 'enum',
408
+ values: ['admin', 'member', 'viewer'],
409
+ required: true,
410
+ },
411
+ addedAt: {
412
+ type: 'timestamp',
413
+ required: true,
414
+ },
415
+ },
416
+ relationships: {
417
+ workspace: {
418
+ type: 'many-to-one',
419
+ entity: 'Workspace',
420
+ },
421
+ user: {
422
+ type: 'many-to-one',
423
+ entity: 'User',
424
+ },
425
+ },
426
+ access: {
427
+ create: ['workspace_admin'],
428
+ read: ['workspace_member'],
429
+ update: ['workspace_admin'],
430
+ delete: ['workspace_admin', 'self'],
431
+ },
432
+ businessRules: [
433
+ 'User must be organization member to join workspace',
434
+ ],
435
+ indexes: [
436
+ { fields: ['workspaceId', 'userId'], unique: true },
437
+ ],
438
+ },
439
+
440
+ Project: {
441
+ fields: {
442
+ name: {
443
+ type: 'string',
444
+ required: true,
445
+ },
446
+ description: {
447
+ type: 'text',
448
+ required: false,
449
+ },
450
+ workspaceId: {
451
+ type: 'reference',
452
+ entity: 'Workspace',
453
+ required: true,
454
+ },
455
+ status: {
456
+ type: 'enum',
457
+ values: ['active', 'paused', 'completed', 'archived'],
458
+ default: 'active',
459
+ },
460
+ priority: {
461
+ type: 'enum',
462
+ values: ['low', 'medium', 'high', 'urgent'],
463
+ default: 'medium',
464
+ },
465
+ startDate: {
466
+ type: 'timestamp',
467
+ required: false,
468
+ },
469
+ dueDate: {
470
+ type: 'timestamp',
471
+ required: false,
472
+ },
473
+ createdById: {
474
+ type: 'reference',
475
+ entity: 'User',
476
+ required: true,
477
+ },
478
+ createdAt: {
479
+ type: 'timestamp',
480
+ required: true,
481
+ },
482
+ updatedAt: {
483
+ type: 'timestamp',
484
+ required: true,
485
+ },
486
+ },
487
+ relationships: {
488
+ workspace: {
489
+ type: 'many-to-one',
490
+ entity: 'Workspace',
491
+ },
492
+ createdBy: {
493
+ type: 'many-to-one',
494
+ entity: 'User',
495
+ },
496
+ },
497
+ access: {
498
+ create: ['workspace_member'],
499
+ read: ['workspace_member'],
500
+ update: ['workspace_admin', 'workspace_member'],
501
+ delete: ['workspace_admin'],
502
+ },
503
+ indexes: [
504
+ { fields: ['workspaceId'] },
505
+ { fields: ['status'] },
506
+ ],
507
+ },
508
+
509
+ Invitation: {
510
+ fields: {
511
+ email: {
512
+ type: 'email',
513
+ required: true,
514
+ },
515
+ organizationId: {
516
+ type: 'reference',
517
+ entity: 'Organization',
518
+ required: true,
519
+ },
520
+ role: {
521
+ type: 'enum',
522
+ values: ['admin', 'member', 'guest'],
523
+ required: true,
524
+ },
525
+ token: {
526
+ type: 'string',
527
+ unique: true,
528
+ required: true,
529
+ },
530
+ invitedById: {
531
+ type: 'reference',
532
+ entity: 'User',
533
+ required: true,
534
+ },
535
+ status: {
536
+ type: 'enum',
537
+ values: ['pending', 'accepted', 'expired', 'cancelled'],
538
+ default: 'pending',
539
+ },
540
+ expiresAt: {
541
+ type: 'timestamp',
542
+ required: true,
543
+ },
544
+ acceptedAt: {
545
+ type: 'timestamp',
546
+ required: false,
547
+ },
548
+ createdAt: {
549
+ type: 'timestamp',
550
+ required: true,
551
+ },
552
+ },
553
+ relationships: {
554
+ organization: {
555
+ type: 'many-to-one',
556
+ entity: 'Organization',
557
+ },
558
+ invitedBy: {
559
+ type: 'many-to-one',
560
+ entity: 'User',
561
+ },
562
+ },
563
+ access: {
564
+ create: ['admin'],
565
+ read: ['public'], // Public can read to accept invitation
566
+ update: ['admin'],
567
+ delete: ['admin'],
568
+ },
569
+ businessRules: [
570
+ 'Invitations expire after 7 days',
571
+ 'Email can only have one pending invitation per organization',
572
+ ],
573
+ indexes: [
574
+ { fields: ['token'], unique: true },
575
+ { fields: ['organizationId', 'email', 'status'] },
576
+ { fields: ['expiresAt'] },
577
+ ],
578
+ },
579
+
580
+ ApiKey: {
581
+ fields: {
582
+ name: {
583
+ type: 'string',
584
+ required: true,
585
+ },
586
+ organizationId: {
587
+ type: 'reference',
588
+ entity: 'Organization',
589
+ required: true,
590
+ },
591
+ key: {
592
+ type: 'string',
593
+ unique: true,
594
+ required: true,
595
+ },
596
+ keyPrefix: {
597
+ type: 'string',
598
+ required: true,
599
+ },
600
+ keyHash: {
601
+ type: 'string',
602
+ required: true,
603
+ },
604
+ scopes: {
605
+ type: 'array',
606
+ required: true,
607
+ },
608
+ isActive: {
609
+ type: 'boolean',
610
+ default: true,
611
+ },
612
+ lastUsedAt: {
613
+ type: 'timestamp',
614
+ required: false,
615
+ },
616
+ expiresAt: {
617
+ type: 'timestamp',
618
+ required: false,
619
+ },
620
+ createdById: {
621
+ type: 'reference',
622
+ entity: 'User',
623
+ required: true,
624
+ },
625
+ createdAt: {
626
+ type: 'timestamp',
627
+ required: true,
628
+ },
629
+ },
630
+ relationships: {
631
+ organization: {
632
+ type: 'many-to-one',
633
+ entity: 'Organization',
634
+ },
635
+ createdBy: {
636
+ type: 'many-to-one',
637
+ entity: 'User',
638
+ },
639
+ },
640
+ access: {
641
+ create: ['admin'],
642
+ read: ['admin'],
643
+ update: ['admin'],
644
+ delete: ['admin'],
645
+ },
646
+ businessRules: [
647
+ 'API key is shown once at creation',
648
+ 'Keys are hashed before storage',
649
+ 'Expired keys are automatically deactivated',
650
+ ],
651
+ indexes: [
652
+ { fields: ['keyHash'], unique: true },
653
+ { fields: ['organizationId'] },
654
+ { fields: ['keyPrefix'] },
655
+ ],
656
+ },
657
+
658
+ UsageLog: {
659
+ fields: {
660
+ organizationId: {
661
+ type: 'reference',
662
+ entity: 'Organization',
663
+ required: true,
664
+ },
665
+ userId: {
666
+ type: 'reference',
667
+ entity: 'User',
668
+ required: false,
669
+ },
670
+ apiKeyId: {
671
+ type: 'reference',
672
+ entity: 'ApiKey',
673
+ required: false,
674
+ },
675
+ endpoint: {
676
+ type: 'string',
677
+ required: true,
678
+ },
679
+ method: {
680
+ type: 'enum',
681
+ values: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
682
+ required: true,
683
+ },
684
+ statusCode: {
685
+ type: 'number',
686
+ required: true,
687
+ },
688
+ responseTime: {
689
+ type: 'number',
690
+ required: true,
691
+ },
692
+ timestamp: {
693
+ type: 'timestamp',
694
+ required: true,
695
+ },
696
+ },
697
+ relationships: {
698
+ organization: {
699
+ type: 'many-to-one',
700
+ entity: 'Organization',
701
+ },
702
+ user: {
703
+ type: 'many-to-one',
704
+ entity: 'User',
705
+ optional: true,
706
+ },
707
+ apiKey: {
708
+ type: 'many-to-one',
709
+ entity: 'ApiKey',
710
+ optional: true,
711
+ },
712
+ },
713
+ access: {
714
+ create: ['system'],
715
+ read: ['admin'],
716
+ update: ['system'],
717
+ delete: ['admin'],
718
+ },
719
+ businessRules: [
720
+ 'Usage logs are aggregated for billing',
721
+ 'Logs older than 90 days are archived',
722
+ ],
723
+ indexes: [
724
+ { fields: ['organizationId', 'timestamp'] },
725
+ { fields: ['apiKeyId', 'timestamp'] },
726
+ ],
727
+ },
728
+
729
+ AuditLog: {
730
+ fields: {
731
+ organizationId: {
732
+ type: 'reference',
733
+ entity: 'Organization',
734
+ required: true,
735
+ },
736
+ userId: {
737
+ type: 'reference',
738
+ entity: 'User',
739
+ required: true,
740
+ },
741
+ action: {
742
+ type: 'string',
743
+ required: true,
744
+ },
745
+ resourceType: {
746
+ type: 'string',
747
+ required: true,
748
+ },
749
+ resourceId: {
750
+ type: 'string',
751
+ required: false,
752
+ },
753
+ changes: {
754
+ type: 'json',
755
+ required: false,
756
+ },
757
+ ipAddress: {
758
+ type: 'string',
759
+ required: false,
760
+ },
761
+ userAgent: {
762
+ type: 'string',
763
+ required: false,
764
+ },
765
+ timestamp: {
766
+ type: 'timestamp',
767
+ required: true,
768
+ },
769
+ },
770
+ relationships: {
771
+ organization: {
772
+ type: 'many-to-one',
773
+ entity: 'Organization',
774
+ },
775
+ user: {
776
+ type: 'many-to-one',
777
+ entity: 'User',
778
+ },
779
+ },
780
+ access: {
781
+ create: ['system'],
782
+ read: ['admin'],
783
+ update: ['system'],
784
+ delete: ['admin'],
785
+ },
786
+ businessRules: [
787
+ 'All sensitive actions are logged',
788
+ 'Audit logs are immutable',
789
+ 'Logs retained for compliance (7 years)',
790
+ ],
791
+ indexes: [
792
+ { fields: ['organizationId', 'timestamp'] },
793
+ { fields: ['userId', 'timestamp'] },
794
+ { fields: ['resourceType', 'resourceId'] },
795
+ ],
796
+ },
797
+ },
798
+ };