@api-client/core 0.18.26 → 0.18.28

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 (48) hide show
  1. package/build/src/modeling/ApiModel.d.ts +18 -9
  2. package/build/src/modeling/ApiModel.d.ts.map +1 -1
  3. package/build/src/modeling/ApiModel.js +141 -13
  4. package/build/src/modeling/ApiModel.js.map +1 -1
  5. package/build/src/modeling/Semantics.d.ts +7 -0
  6. package/build/src/modeling/Semantics.d.ts.map +1 -1
  7. package/build/src/modeling/Semantics.js +23 -0
  8. package/build/src/modeling/Semantics.js.map +1 -1
  9. package/build/src/modeling/helpers/endpointHelpers.d.ts +2 -0
  10. package/build/src/modeling/helpers/endpointHelpers.d.ts.map +1 -0
  11. package/build/src/modeling/helpers/endpointHelpers.js +6 -0
  12. package/build/src/modeling/helpers/endpointHelpers.js.map +1 -0
  13. package/build/src/modeling/helpers/keying.d.ts +9 -0
  14. package/build/src/modeling/helpers/keying.d.ts.map +1 -0
  15. package/build/src/modeling/helpers/keying.js +10 -0
  16. package/build/src/modeling/helpers/keying.js.map +1 -0
  17. package/build/src/modeling/templates/meta/blog-publishing-platform.json +1 -1
  18. package/build/src/modeling/templates/meta/financial-services-platform.json +1 -1
  19. package/build/src/modeling/templates/meta/index.d.ts +1 -1
  20. package/build/src/modeling/templates/meta/index.js +1 -1
  21. package/build/src/modeling/templates/meta/index.js.map +1 -1
  22. package/build/src/modeling/templates/verticals/business-services/financial-services-domain.d.ts.map +1 -1
  23. package/build/src/modeling/templates/verticals/business-services/financial-services-domain.js +1 -0
  24. package/build/src/modeling/templates/verticals/business-services/financial-services-domain.js.map +1 -1
  25. package/build/src/modeling/templates/verticals/technology-media/blog-domain.d.ts.map +1 -1
  26. package/build/src/modeling/templates/verticals/technology-media/blog-domain.js +12 -4
  27. package/build/src/modeling/templates/verticals/technology-media/blog-domain.js.map +1 -1
  28. package/build/src/modeling/types.d.ts +77 -8
  29. package/build/src/modeling/types.d.ts.map +1 -1
  30. package/build/src/modeling/types.js.map +1 -1
  31. package/build/tsconfig.tsbuildinfo +1 -1
  32. package/data/models/example-generator-api.json +6 -6
  33. package/package.json +2 -1
  34. package/src/modeling/ApiModel.ts +159 -15
  35. package/src/modeling/Semantics.ts +23 -0
  36. package/src/modeling/helpers/endpointHelpers.ts +5 -0
  37. package/src/modeling/helpers/keying.ts +11 -0
  38. package/src/modeling/readme.md +153 -7
  39. package/src/modeling/templates/meta/blog-publishing-platform.json +1 -1
  40. package/src/modeling/templates/meta/financial-services-platform.json +1 -1
  41. package/src/modeling/templates/verticals/business-services/financial-services-domain.ts +1 -0
  42. package/src/modeling/templates/verticals/technology-media/blog-domain.ts +12 -4
  43. package/src/modeling/types.ts +84 -8
  44. package/tests/unit/modeling/api_model.spec.ts +25 -137
  45. package/tests/unit/modeling/api_model_expose_entity.spec.ts +190 -0
  46. package/tests/unit/modeling/api_model_remove_entity.spec.ts +82 -0
  47. package/tests/unit/modeling/helpers/endpointHelpers.spec.ts +10 -0
  48. package/tests/unit/modeling/username_semantic.spec.ts +81 -0
@@ -42068,10 +42068,10 @@
42068
42068
  "@id": "#194"
42069
42069
  },
42070
42070
  {
42071
- "@id": "#200"
42071
+ "@id": "#197"
42072
42072
  },
42073
42073
  {
42074
- "@id": "#197"
42074
+ "@id": "#200"
42075
42075
  },
42076
42076
  {
42077
42077
  "@id": "#203"
@@ -43478,7 +43478,7 @@
43478
43478
  "doc:ExternalDomainElement",
43479
43479
  "doc:DomainElement"
43480
43480
  ],
43481
- "doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
43481
+ "doc:raw": "code: '5'\ndescription: 'Limited company'\n",
43482
43482
  "core:mediaType": "application/yaml",
43483
43483
  "sourcemaps:sources": [
43484
43484
  {
@@ -43499,7 +43499,7 @@
43499
43499
  "doc:ExternalDomainElement",
43500
43500
  "doc:DomainElement"
43501
43501
  ],
43502
- "doc:raw": "code: '5'\ndescription: 'Limited company'\n",
43502
+ "doc:raw": "class: '3'\ndescription: '150 - 300'\nnumberOfFte: 5500\nnumberOfEmployees: 5232\n",
43503
43503
  "core:mediaType": "application/yaml",
43504
43504
  "sourcemaps:sources": [
43505
43505
  {
@@ -44766,12 +44766,12 @@
44766
44766
  {
44767
44767
  "@id": "#199/source-map/lexical/element_0",
44768
44768
  "sourcemaps:element": "amf://id#199",
44769
- "sourcemaps:value": "[(1,0)-(5,0)]"
44769
+ "sourcemaps:value": "[(1,0)-(3,0)]"
44770
44770
  },
44771
44771
  {
44772
44772
  "@id": "#202/source-map/lexical/element_0",
44773
44773
  "sourcemaps:element": "amf://id#202",
44774
- "sourcemaps:value": "[(1,0)-(3,0)]"
44774
+ "sourcemaps:value": "[(1,0)-(5,0)]"
44775
44775
  },
44776
44776
  {
44777
44777
  "@id": "#205/source-map/lexical/element_0",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@api-client/core",
3
3
  "description": "The API Client's core client library. Works in NodeJS and in a ES enabled browser.",
4
- "version": "0.18.26",
4
+ "version": "0.18.28",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
7
7
  "./browser.js": {
@@ -87,6 +87,7 @@
87
87
  "@api-client/graph": "^0.3.5",
88
88
  "@api-client/json": "^0.2.0",
89
89
  "@esm-bundle/chai": "^4.3.4-fix.0",
90
+ "@jarrodek/pluralize": "^1.0.2",
90
91
  "@metrichor/jmespath": "^0.3.1",
91
92
  "@pawel-up/csv": "^0.2.0",
92
93
  "@pawel-up/data-mock": "^0.4.0",
@@ -11,10 +11,13 @@ import type {
11
11
  RolesBasedAccessControl,
12
12
  SessionConfiguration,
13
13
  UsernamePasswordConfiguration,
14
+ ExposeOptions,
14
15
  } from './types.js'
15
16
  import { DataDomain } from './DataDomain.js'
16
17
  import { DependentModel, type DependentModelSchema, type DomainDependency } from './DependentModel.js'
17
18
  import { observed, toRaw } from '../decorators/observed.js'
19
+ import pluralize from '@jarrodek/pluralize'
20
+ import { createDomainKey } from './helpers/keying.js'
18
21
 
19
22
  /**
20
23
  * Contact information for the exposed API.
@@ -54,12 +57,12 @@ export interface ApiModelSchema extends DependentModelSchema {
54
57
  */
55
58
  kind: typeof ApiModelKind
56
59
  /**
57
- * The unique key of the data domain schema.
60
+ * The unique key of the API model schema.
58
61
  * This is a stable identifier that does not change across versions.
59
62
  */
60
63
  key: string
61
64
  /**
62
- * Contains the name, display name, description, and the version of the data domain schema.
65
+ * Contains the name, display name, description, and the version of the API model schema.
63
66
  */
64
67
  info: IThing
65
68
 
@@ -376,41 +379,182 @@ export class ApiModel extends DependentModel {
376
379
  /**
377
380
  * Exposes a new entity in the API model.
378
381
  * If the entity already exists, it returns the existing one.
379
- * @param entityKey The key of the entity to expose.
382
+ * @param entity The entity key and domain to expose.
380
383
  * @returns The exposed entity.
381
384
  */
382
- exposeEntity(entityKey: string): ExposedEntity {
383
- const existing = this.exposes.find((e) => e.key === entityKey)
385
+ exposeEntity(entity: AssociationTarget, options?: ExposeOptions): ExposedEntity {
386
+ const domain = this.domain
387
+ if (!domain) {
388
+ throw new Error(`No domain attached to API model`)
389
+ }
390
+ // checks whether the entity is already exposed as a root exposure.
391
+ const existing = this.exposes.find(
392
+ (e) => e.isRoot && e.entity.key === entity.key && e.entity.domain === entity.domain
393
+ )
384
394
  if (existing) {
395
+ // quietly return the existing exposure
396
+ // TBD: should we throw an error here?
385
397
  return existing
386
398
  }
399
+ const domainEntity = domain.findEntity(entity.key, entity.domain)
400
+ if (!domainEntity) {
401
+ throw new Error(`Entity not found in domain: ${entity.key}`)
402
+ }
403
+ const name = domainEntity.info.name || ''
387
404
  const newEntity: ExposedEntity = {
388
- key: entityKey,
405
+ key: nanoid(),
406
+ entity: { ...entity },
389
407
  actions: [],
408
+ isRoot: true,
409
+ path: pluralize(name.toLocaleLowerCase()),
410
+ }
411
+ if (options) {
412
+ newEntity.exposeOptions = { ...options }
390
413
  }
391
414
  this.exposes.push(newEntity)
415
+
416
+ // Follow associations if requested
417
+ if (options?.followAssociations) {
418
+ if (options?.maxDepth === undefined || options.maxDepth > 0) {
419
+ this.followEntityAssociations(newEntity, options)
420
+ }
421
+ }
392
422
  this.notifyChange()
393
423
  return newEntity
394
424
  }
395
425
 
396
426
  /**
397
- * Removes an entity from the API model.
398
- * @param entityKey The key of the entity to remove.
427
+ * Follows associations for a newly exposed entity if configured to do so.
428
+ * This creates nested exposures based on the entity's associations.
429
+ *
430
+ * @param parentExposure The root exposure to follow associations from
431
+ * @param options The expose options containing follow configuration
399
432
  */
400
- removeEntity(entityKey: string): void {
401
- const index = this.exposes.findIndex((e) => e.key === entityKey)
402
- if (index !== -1) {
403
- this.exposes.splice(index, 1)
404
- this.notifyChange()
433
+ private followEntityAssociations(parentExposure: ExposedEntity, options: ExposeOptions): void {
434
+ const domain = this.domain
435
+ if (!domain) {
436
+ return
437
+ }
438
+ const maxDepth = options.maxDepth ?? 6
439
+ const visited = new Set<string>()
440
+ // Add parent entity's key to the visited set so we won't skip it when traversing
441
+ // associations.
442
+ visited.add(createDomainKey(parentExposure.entity))
443
+
444
+ const follow = (currentEntity: AssociationTarget, parentKey: string, depth: number) => {
445
+ // Find the domain entity
446
+ const domainEntity = domain.findEntity(currentEntity.key, currentEntity.domain)
447
+ if (!domainEntity) return
448
+
449
+ // Iterate through associations
450
+ for (const association of domainEntity.listAssociations()) {
451
+ for (const target of association.targets) {
452
+ // Skip self-referencing associations
453
+ if (target.key === currentEntity.key && target.domain === currentEntity.domain) {
454
+ continue
455
+ }
456
+
457
+ // Create unique identifier for circular detection
458
+ const visitKey = createDomainKey(target)
459
+ if (visited.has(visitKey)) {
460
+ continue // Skip circular references
461
+ }
462
+ visited.add(visitKey)
463
+
464
+ // Check if this nested exposure already exists
465
+ const existingNested = this.exposes.find(
466
+ (e) =>
467
+ !e.isRoot &&
468
+ e.entity.key === target.key &&
469
+ e.entity.domain === target.domain &&
470
+ e.parent?.key === parentKey
471
+ )
472
+
473
+ if (existingNested) {
474
+ continue // Already exposed under this parent
475
+ }
476
+
477
+ // Find the target domain entity for path generation
478
+ const targetDomainEntity = domain.findEntity(target.key, target.domain)
479
+ if (!targetDomainEntity) continue
480
+
481
+ const name = association.info.name || ''
482
+ // Create nested exposure
483
+ const nestedExposure: ExposedEntity = {
484
+ key: nanoid(),
485
+ entity: { ...target },
486
+ actions: [],
487
+ isRoot: false,
488
+ path: pluralize(name.toLocaleLowerCase()),
489
+ parent: {
490
+ key: parentKey,
491
+ association: {
492
+ key: association.key,
493
+ domain: currentEntity.domain,
494
+ },
495
+ depth: depth + 1,
496
+ },
497
+ }
498
+
499
+ this.exposes.push(nestedExposure)
500
+ if (depth + 1 >= maxDepth) {
501
+ nestedExposure.truncated = true
502
+ } else {
503
+ // Recursively follow associations
504
+ follow(target, nestedExposure.key, depth + 1)
505
+ }
506
+ }
507
+ }
508
+ }
509
+
510
+ // Start following from the root exposure
511
+ follow(parentExposure.entity, parentExposure.key, 0)
512
+ }
513
+
514
+ /**
515
+ * Removes an exposed entity from the API model.
516
+ * @param entity The entity to remove.
517
+ */
518
+ removeEntity(entity: AssociationTarget): void {
519
+ const current = this.exposes.find((e) => e.entity.key === entity.key && e.entity.domain === entity.domain)
520
+ if (!current) {
521
+ return
522
+ }
523
+ this.removeWithChildren(current.key)
524
+ this.notifyChange()
525
+ }
526
+
527
+ private removeWithChildren(key: string): void {
528
+ const index = this.exposes.findIndex((e) => e.key === key)
529
+ if (index < 0) {
530
+ return
405
531
  }
532
+ // Remove the parent itself
533
+ this.exposes.splice(index, 1)
534
+ // Remove all children recursively
535
+ const removeChildren = (parentKey: string) => {
536
+ // Find all exposures whose parent.key matches parentKey
537
+ const children = this.exposes.filter((e) => e.parent?.key === parentKey)
538
+ for (const child of children) {
539
+ removeChildren(child.key)
540
+ const childIndex = this.exposes.findIndex((e) => e.key === child.key)
541
+ if (childIndex >= 0) {
542
+ this.exposes.splice(childIndex, 1)
543
+ }
544
+ }
545
+ }
546
+ // Then also remove children
547
+ removeChildren(key)
548
+ this.notifyChange()
406
549
  }
550
+
407
551
  /**
408
552
  * Returns the exposed entity by its key.
409
553
  * @param entityKey The key of the entity to find.
410
554
  * @returns The exposed entity or undefined if not found.
411
555
  */
412
- getExposedEntity(entityKey: string): ExposedEntity | undefined {
413
- return this.exposes.find((e) => e.key === entityKey)
556
+ getExposedEntity(entity: AssociationTarget): ExposedEntity | undefined {
557
+ return this.exposes.find((e) => e.entity.key === entity.key && e.entity.domain === entity.domain)
414
558
  }
415
559
 
416
560
  /**
@@ -26,6 +26,13 @@ export enum SemanticType {
26
26
  * ensuring it is encrypted and not exposed in API responses.
27
27
  */
28
28
  Password = 'Semantic#Password',
29
+ /**
30
+ * Annotates the field as the username for user authentication.
31
+ * This identifies which field should be used for login purposes.
32
+ * Can be applied to dedicated username fields or email fields that serve as usernames.
33
+ * The runtime uses this for authentication, password reset, and user lookup operations.
34
+ */
35
+ Username = 'Semantic#Username',
29
36
  /**
30
37
  * Designates a Data Property as the `createdAt` timestamp of an entity.
31
38
  * This is used to track when the entity was first created.
@@ -651,6 +658,22 @@ export const DataSemantics: Record<SemanticType, DataSemantic> = {
651
658
  ],
652
659
  },
653
660
  },
661
+ [SemanticType.Username]: {
662
+ id: SemanticType.Username,
663
+ displayName: 'Username',
664
+ scope: SemanticScope.Property,
665
+ description: 'User authentication identifier',
666
+ category: SemanticCategory.Identity,
667
+ applicableDataTypes: ['string'],
668
+ hasConfig: false,
669
+ runtime: {
670
+ timing: SemanticTiming.Before,
671
+ operations: [SemanticOperation.Create, SemanticOperation.Update, SemanticOperation.Read],
672
+ priority: 15, // High priority for authentication
673
+ canDisable: false, // Security semantics cannot be disabled
674
+ timeoutMs: 100, // Fast operation
675
+ },
676
+ },
654
677
  [SemanticType.UserRole]: {
655
678
  id: SemanticType.UserRole,
656
679
  displayName: 'User Role Field',
@@ -0,0 +1,5 @@
1
+ export function paramNameFor(entityKeyLocal: string): string {
2
+ const parts = entityKeyLocal.split(':')
3
+ const key = parts[parts.length - 1]
4
+ return `${key}Id`
5
+ }
@@ -0,0 +1,11 @@
1
+ import { AssociationTarget } from '../types.js'
2
+
3
+ /**
4
+ * Creates a consistent key for a given association target within its domain.
5
+ *
6
+ * @param target The association target to create a key for.
7
+ * @returns A unique key for the association target.
8
+ */
9
+ export function createDomainKey(target: AssociationTarget): string {
10
+ return target.domain ? `${target.domain}:${target.key}` : target.key
11
+ }
@@ -122,13 +122,159 @@ Let's imagine a simple e-commerce domain:
122
122
  - **Entity-Parent**: A `DomainEntity` can have `DomainEntity` instances as parents.
123
123
  - **Namespace-Foreign**: A `DomainNamespace` can have references to `DomainNamespace` instances.
124
124
 
125
+ ## API Modeling
126
+
127
+ Beyond data modeling, the system also provides **API Modeling** capabilities that allow you to define how your data domain is exposed as a secure, production-ready API.
128
+
129
+ ### ApiModel
130
+
131
+ The `ApiModel` class extends the data modeling system to define how Data Entities from a `DataDomain` are exposed via HTTP APIs. It provides configuration for:
132
+
133
+ **Core API Configuration:**
134
+
135
+ - **Exposed Entities**: Select which Data Entities from your Data Domain should be accessible via the API
136
+ - **API Actions**: Define what operations (List, Read, Create, Update, Delete, Search) are available for each entity
137
+ - **User Entity**: Designate which Data Entity represents a "User" for authentication purposes
138
+
139
+ **Security & Authentication:**
140
+
141
+ - **Authentication**: Configure how users prove their identity (e.g., username/password)
142
+ - **Authorization**: Define what authenticated users are allowed to do (e.g., Role-Based Access Control)
143
+ - **Session Management**: Configure transport and payload for user sessions (JWT, cookies)
144
+ - **Access Rules**: Define fine-grained access control policies for entities and actions
145
+
146
+ **API Metadata:**
147
+
148
+ - **Contact Information**: API maintainer details
149
+ - **License Information**: Legal information about API usage
150
+ - **Terms of Service**: Link to API usage terms
151
+ - **Rate Limiting**: Protect the API from overuse and abuse
152
+
153
+ ### How API Modeling Works with Data Modeling
154
+
155
+ The API modeling system builds on top of the data modeling foundation:
156
+
157
+ 1. **Start with a Data Domain**: Create your `DataDomain` with entities, properties, and associations
158
+ 2. **Create an API Model**: Instantiate an `ApiModel` and attach your `DataDomain`
159
+ 3. **Expose Entities**: Select which entities should be available via the API using `exposeEntity()`
160
+ 4. **Configure Security**: Set up authentication, authorization, and session management
161
+ 5. **Define Actions**: Configure what operations are available for each exposed entity
162
+ 6. **Set API Metadata**: Add contact info, license, terms of service
163
+
164
+ ### Example: E-Commerce API
165
+
166
+ Building on the e-commerce data domain example:
167
+
168
+ ```typescript
169
+ // 1. Create the data domain (as shown in previous example)
170
+ const domain = new DataDomain({ key: 'ecommerce', info: { name: 'E-Commerce Platform' } })
171
+ const userModel = domain.addModel({ info: { name: 'User Management' } })
172
+ const userEntity = userModel.addEntity({
173
+ info: { name: 'user' },
174
+ semantics: [{ type: SemanticType.User }] // Mark as User entity
175
+ })
176
+
177
+ // 2. Create an API model
178
+ const apiModel = new ApiModel({
179
+ info: { name: 'E-Commerce API', version: '1.0.0' },
180
+ termsOfService: 'https://example.com/terms',
181
+ contact: { name: 'API Team', email: 'api@example.com' },
182
+ license: { name: 'MIT', url: 'https://opensource.org/licenses/MIT' }
183
+ })
184
+
185
+ // 3. Attach the data domain
186
+ apiModel.attachDataDomain(domain)
187
+
188
+ // 4. Configure security
189
+ apiModel.user = { key: userEntity.key }
190
+ apiModel.authentication = { strategy: 'UsernamePassword', passwordKey: 'password' }
191
+ apiModel.authorization = { strategy: 'RBAC', roleKey: 'role' }
192
+ apiModel.session = {
193
+ secret: 'your-secure-secret',
194
+ properties: ['email', 'role'],
195
+ cookie: { enabled: true, kind: 'cookie', lifetime: '7d' }
196
+ }
197
+
198
+ // 5. Expose entities and configure actions
199
+ const exposedUser = apiModel.exposeEntity(userEntity.key)
200
+ exposedUser.actions = [
201
+ { type: 'Read', enabled: true },
202
+ { type: 'Update', enabled: true },
203
+ { type: 'Create', enabled: true }
204
+ ]
205
+ ```
206
+
207
+ ### Key API Modeling Concepts
208
+
209
+ #### ExposedEntity
210
+
211
+ Represents a Data Entity that is exposed via the API. Contains:
212
+
213
+ - **Key**: Reference to the Data Entity
214
+ - **Actions**: List of available API operations (CRUD, Search)
215
+ - **Access Rules**: Entity-specific access control
216
+ - **Rate Limiting**: Entity-specific rate limiting rules
217
+
218
+ #### API Actions
219
+
220
+ Standard RESTful operations that can be enabled for each entity:
221
+
222
+ - **List**: Retrieve collections of entities with filtering and pagination
223
+ - **Read**: Retrieve a single entity by ID
224
+ - **Create**: Create new entities
225
+ - **Update**: Modify existing entities
226
+ - **Delete**: Remove entities
227
+ - **Search**: Full-text or advanced search across entities
228
+
229
+ #### Authentication Configuration
230
+
231
+ Defines how users prove their identity:
232
+
233
+ - **UsernamePassword**: Traditional email/password authentication
234
+ - Extensible for future strategies (SSO, OAuth, etc.)
235
+
236
+ #### Authorization Configuration
237
+
238
+ Defines what authenticated users can do:
239
+
240
+ - **RBAC (Role-Based Access Control)**: Users have roles, permissions granted to roles
241
+ - Extensible for future strategies (PBAC - Permission-Based Access Control)
242
+
243
+ #### Session Configuration
244
+
245
+ Manages user session data:
246
+
247
+ - **Properties**: Which User entity properties to include in session
248
+ - **Transport**: How session data is transmitted (JWT tokens, cookies)
249
+ - **Security**: Encryption, lifetime, and security settings
250
+
251
+ ### Business-First API Design
252
+
253
+ The API modeling system follows the same business-first philosophy as data modeling:
254
+
255
+ - **Semantic Awareness**: Uses semantic types to automatically configure security (e.g., Password properties are write-only)
256
+ - **Standard Compliance**: Generates OpenAPI-compliant specifications
257
+ - **Security by Default**: Requires explicit configuration for authentication, authorization, and sessions
258
+ - **User-Friendly**: Clear error messages and validation feedback
259
+ - **Production Ready**: Built-in support for rate limiting, access control, and monitoring
260
+
125
261
  ## Summary
126
262
 
127
- This data modeling system provides a flexible and powerful way to define complex data domains. It allows you to:
263
+ This comprehensive modeling system provides both **Data Modeling** and **API Modeling** capabilities:
264
+
265
+ **Data Modeling** allows you to:
266
+
267
+ - **Organize**: Group related data into namespaces and models
268
+ - **Structure**: Define entities with properties and relationships
269
+ - **Reuse**: Inherit from other entities and reference foreign namespaces
270
+ - **Translate**: Define bindings to map the model to different formats
271
+ - **Validate**: Validate the data model definition
272
+ - **Generate**: Generate AMF shapes and examples
273
+
274
+ **API Modeling** extends this to:
128
275
 
129
- - **Organize**: Group related data into namespaces and models.
130
- - **Structure**: Define entities with properties and relationships.
131
- - **Reuse**: Inherit from other entities and reference foreign namespaces.
132
- - **Translate**: Define bindings to map the model to different formats.
133
- - **Validate**: Validate the data model definition.
134
- - **Generate**: Generate AMF shapes and examples.
276
+ - **Expose**: Selectively expose Data Entities as API resources
277
+ - **Secure**: Configure authentication, authorization, and session management
278
+ - **Control**: Define fine-grained access rules and rate limiting
279
+ - **Document**: Generate complete API documentation with business context
280
+ - **Deploy**: Create production-ready APIs with proper security and monitoring
@@ -1 +1 @@
1
- {"id":"blog-publishing-platform","name":"Blog Publishing Platform","description":"A comprehensive content management and publishing platform for blogs, magazines, and digital publications. Includes content management, user roles, publishing workflow, and social features.","createdAt":"2025-07-27T21:14:57.328Z","updatedAt":"2025-07-27T21:14:57.328Z","version":"1.0.0","author":"API Now! Core Team","tags":["blog","cms","publishing","content","media","editorial"],"structure":{"domain":{"name":"Blog Publishing Platform","description":"A comprehensive content management and publishing platform for blogs, magazines, and digital publications","totalEntities":8,"totalProperties":69,"totalAssociations":14},"namespaces":[{"name":"ContentManagement","displayName":"Content Management","description":"Core content creation, editing, and organization features","modelCount":2,"entityCount":4,"models":[{"name":"Publications","displayName":"Publications Management","description":"Individual blogs, magazines, or publications within the platform","entityCount":1,"entities":[{"name":"publication","displayName":"Publication","description":"Individual blog or publication site","propertyCount":8,"associationCount":0,"properties":[{"name":"id","displayName":"Publication ID","description":"Unique identifier for the publication","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"name","displayName":"Publication Name","description":"Display name of the publication","type":"string","semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the publication","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"description","displayName":"Description","description":"Publication description and tagline","type":"string","semantics":["Semantic#Description"]},{"name":"domain","displayName":"Custom Domain","description":"Custom domain name for the publication","type":"string","semantics":[]},{"name":"logo_url","displayName":"Logo URL","description":"URL to publication logo image","type":"string","semantics":["Semantic#ImageURL"]},{"name":"status","displayName":"Publication Status","description":"Current status of the publication","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["active","suspended","archived"],"defaultValue":"active"},{"name":"created_at","displayName":"Created At","description":"When the publication was created","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[],"semantics":[]}]},{"name":"Content","displayName":"Content Management","description":"Posts, pages, and other content types","entityCount":3,"entities":[{"name":"category","displayName":"Content Category","description":"Content categorization for organization and navigation","propertyCount":5,"associationCount":2,"properties":[{"name":"id","displayName":"Category ID","description":"Unique identifier for the category","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"name","displayName":"Category Name","description":"Display name of the category","type":"string","semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the category","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"description","displayName":"Description","description":"Category description","type":"string","semantics":["Semantic#Description"]},{"name":"color","displayName":"Color","description":"Theme color for the category","type":"string","semantics":[]}],"associations":[{"name":"publication","displayName":"Publication","description":"Publication this category belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"},{"name":"parentCategory","displayName":"Parent Category","description":"Parent category for hierarchical organization","required":false,"multiple":false,"targetEntities":["category"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]},{"name":"Tag","displayName":"Content Tag","description":"Tags for flexible content labeling and discovery","propertyCount":3,"associationCount":1,"properties":[{"name":"id","displayName":"Tag ID","description":"Unique identifier for the tag","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"name","displayName":"Tag Name","description":"Display name of the tag","type":"string","semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the tag","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]}],"associations":[{"name":"publication","displayName":"Publication","description":"Publication this tag belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]},{"name":"Post","displayName":"Blog Post","description":"Individual blog posts and articles","propertyCount":16,"associationCount":4,"properties":[{"name":"id","displayName":"Post ID","description":"Unique identifier for the post","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"title","displayName":"Post Title","description":"Title of the blog post","type":"string","required":true,"semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the post","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"excerpt","displayName":"Excerpt","description":"Brief summary or excerpt of the post","type":"string","semantics":["Semantic#Summary"]},{"name":"content","displayName":"Content","description":"Full content of the post in HTML or Markdown","type":"string","required":true,"semantics":["Semantic#HTML"]},{"name":"content_format","displayName":"Content Format","description":"Format of the content (HTML, Markdown, etc.)","type":"string","required":true,"semantics":[],"enumValues":["html","markdown","richtext"],"defaultValue":"markdown"},{"name":"featured_image_url","displayName":"Featured Image URL","description":"URL to featured image","type":"string","semantics":["Semantic#ImageURL"]},{"name":"status","displayName":"Post Status","description":"Current publishing status of the post","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["draft","pending_review","scheduled","published","archived"],"defaultValue":"draft"},{"name":"published_at","displayName":"Published At","description":"When the post was published","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]},{"name":"scheduled_at","displayName":"Scheduled At","description":"When the post is scheduled to be published","type":"datetime","semantics":[]},{"name":"view_count","displayName":"View Count","description":"Number of times the post has been viewed","type":"number","required":true,"semantics":[]},{"name":"reading_time","displayName":"Reading Time","description":"Estimated reading time in minutes","type":"number","readOnly":true,"semantics":["Semantic#Calculated"]},{"name":"word_count","displayName":"Word Count","description":"Number of words in the post content","type":"number","readOnly":true,"semantics":["Semantic#Calculated"]},{"name":"meta_title","displayName":"Meta Title","description":"SEO meta title","type":"string","semantics":[]},{"name":"meta_description","displayName":"Meta Description","description":"SEO meta description","type":"string","semantics":[]},{"name":"updated_at","displayName":"Updated At","description":"When the post was last updated","type":"datetime","readOnly":true,"semantics":["Semantic#UpdatedTimestamp"]}],"associations":[{"name":"publication","displayName":"Publication","description":"Publication this post belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"},{"name":"categories","displayName":"Post Categories","description":"Categories this post belongs to","required":false,"multiple":true,"targetEntities":["category"],"semantics":["Semantic#Categories"],"cardinality":"One-to-Many"},{"name":"tags","displayName":"Post Tags","description":"Tags associated with this post","required":false,"multiple":true,"targetEntities":["Tag"],"semantics":["Semantic#Tags"],"cardinality":"One-to-Many"},{"name":"author","displayName":"Post Author","description":"Author who wrote this post","required":true,"multiple":false,"targetEntities":["user"],"semantics":["Semantic#ResourceOwnerIdentifier"],"cardinality":"One-to-One"}],"semantics":[]}]}]},{"name":"UserManagement","displayName":"User Management","description":"Authors, editors, subscribers, and user roles management","modelCount":1,"entityCount":1,"models":[{"name":"Users","displayName":"User Management","description":"User accounts and authentication","entityCount":1,"entities":[{"name":"user","displayName":"User Account","description":"User account for authors, editors, and subscribers","propertyCount":13,"associationCount":0,"properties":[{"name":"id","displayName":"User ID","description":"Unique identifier for the user","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"email","displayName":"Email Address","description":"User email address for login and communication","type":"string","required":true,"semantics":["Semantic#Email"]},{"name":"password","displayName":"Password","description":"Encrypted password for authentication","type":"string","required":true,"semantics":["Semantic#Password"]},{"name":"username","displayName":"Username","description":"Unique username for the user","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"display_name","displayName":"Display Name","description":"Public display name for the user","type":"string","required":true,"semantics":[]},{"name":"first_name","displayName":"First Name","description":"User first name","type":"string","semantics":[]},{"name":"last_name","displayName":"Last Name","description":"User last name","type":"string","semantics":[]},{"name":"bio","displayName":"Biography","description":"User biography and description","type":"string","semantics":["Semantic#Description"]},{"name":"avatar_url","displayName":"Avatar URL","description":"URL to user profile picture","type":"string","semantics":["Semantic#ImageURL"]},{"name":"website","displayName":"Website","description":"User personal website URL","type":"string","semantics":["Semantic#URL"]},{"name":"role","displayName":"User Role","description":"User role for permission management","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["subscriber","author","editor","admin","super_admin"],"defaultValue":"subscriber"},{"name":"emailVerified","displayName":"Email Verified","description":"Whether the user has verified their email","type":"boolean","required":true,"semantics":[],"defaultValue":"false"},{"name":"created_at","displayName":"Created At","description":"When the user account was created","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[],"semantics":["Semantic#User"]}]}]},{"name":"SocialFeatures","displayName":"Social Features","description":"Comments, likes, shares, and social interactions","modelCount":1,"entityCount":1,"models":[{"name":"Comments","displayName":"Comment System","description":"Post comments and replies","entityCount":1,"entities":[{"name":"comment","displayName":"Comment","description":"User comments on posts","propertyCount":7,"associationCount":3,"properties":[{"name":"id","displayName":"Comment ID","description":"Unique identifier for the comment","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"content","displayName":"Comment Content","description":"Content of the comment","type":"string","required":true,"semantics":[]},{"name":"author_name","displayName":"Author Name","description":"Name of the comment author (for guest comments)","type":"string","semantics":[]},{"name":"author_email","displayName":"Author Email","description":"Email of the comment author (for guest comments)","type":"string","required":true,"semantics":["Semantic#Email"]},{"name":"status","displayName":"Comment Status","description":"Moderation status of the comment","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["pending","approved","rejected","spam"],"defaultValue":"pending"},{"name":"user_agent","displayName":"User Agent","description":"Browser user agent string","type":"string","semantics":[]},{"name":"created_at","displayName":"Created At","description":"When the comment was created","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[{"name":"post","displayName":"Post","description":"Post this comment belongs to","required":true,"multiple":false,"targetEntities":["Post"],"semantics":[],"cardinality":"One-to-One"},{"name":"author","displayName":"Comment Author","description":"Registered user who wrote this comment","required":false,"multiple":false,"targetEntities":["user"],"semantics":["Semantic#ResourceOwnerIdentifier"],"cardinality":"One-to-One"},{"name":"parent_comment","displayName":"Parent Comment","description":"Parent comment for replies","required":false,"multiple":false,"targetEntities":["comment"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]}]}]},{"name":"Analytics","displayName":"Analytics & Tracking","description":"Analytics, metrics, and performance tracking","modelCount":1,"entityCount":1,"models":[{"name":"Analytics","displayName":"Content Analytics","description":"Content performance and user engagement metrics","entityCount":1,"entities":[{"name":"page_view","displayName":"Page View","description":"Individual page view tracking record","propertyCount":7,"associationCount":2,"properties":[{"name":"id","displayName":"Page View ID","description":"Unique identifier for the page view","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"path","displayName":"Page Path","description":"URL path of the viewed page","type":"string","required":true,"semantics":[]},{"name":"referrer","displayName":"Referrer","description":"Referring URL","type":"string","semantics":["Semantic#URL"]},{"name":"ip_address","displayName":"IP Address","description":"Visitor IP address","type":"string","semantics":["Semantic#ClientIPAddress"]},{"name":"user_agent","displayName":"User Agent","description":"Browser user agent string","type":"string","semantics":[]},{"name":"session_id","displayName":"Session ID","description":"Visitor session identifier","type":"string","semantics":[]},{"name":"viewed_at","displayName":"Viewed At","description":"When the page was viewed","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[{"name":"post","displayName":"Viewed Post","description":"Post that was viewed (if applicable)","required":false,"multiple":false,"targetEntities":["Post"],"semantics":[],"cardinality":"One-to-One"},{"name":"publication","displayName":"Publication","description":"Publication this page view belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]}]}]},{"name":"MediaManagement","displayName":"Media Management","description":"Image, video, and file upload management","modelCount":1,"entityCount":1,"models":[{"name":"Media","displayName":"Media Library","description":"Uploaded files, images, and media assets","entityCount":1,"entities":[{"name":"media_file","displayName":"Media File","description":"Uploaded media files (images, videos, documents)","propertyCount":10,"associationCount":2,"properties":[{"name":"id","displayName":"Media File ID","description":"Unique identifier for the media file","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"filename","displayName":"File Name","description":"Original filename of the uploaded file","type":"string","required":true,"semantics":[]},{"name":"storage_key","displayName":"Storage Key","description":"Unique storage key for the file","type":"string","required":true,"unique":true,"semantics":[]},{"name":"url","displayName":"File URL","description":"Public URL to access the file","type":"string","required":true,"semantics":["Semantic#URL"]},{"name":"mime_type","displayName":"MIME Type","description":"MIME type of the file","type":"string","required":true,"semantics":[]},{"name":"file_size","displayName":"File Size","description":"File size in bytes","type":"number","required":true,"semantics":[]},{"name":"width","displayName":"Image Width","description":"Width in pixels (for images)","type":"number","required":true,"semantics":[]},{"name":"height","displayName":"Image Height","description":"Height in pixels (for images)","type":"number","required":true,"semantics":[]},{"name":"alt_text","displayName":"Alt Text","description":"Alternative text for accessibility","type":"string","semantics":[]},{"name":"uploaded_at","displayName":"Uploaded At","description":"When the file was uploaded","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[{"name":"uploader","displayName":"File Uploader","description":"User who uploaded this file","required":true,"multiple":false,"targetEntities":["user"],"semantics":["Semantic#ResourceOwnerIdentifier"],"cardinality":"One-to-One"},{"name":"publication","displayName":"Publication","description":"Publication this media file belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]}]}]}]}}
1
+ {"id":"blog-publishing-platform","name":"Blog Publishing Platform","description":"A comprehensive content management and publishing platform for blogs, magazines, and digital publications. Includes content management, user roles, publishing workflow, and social features.","createdAt":"2025-07-27T21:14:57.328Z","updatedAt":"2025-07-27T21:14:57.328Z","version":"1.0.0","author":"API Now! Core Team","tags":["blog","cms","publishing","content","media","editorial"],"structure":{"domain":{"name":"Blog Publishing Platform","description":"A comprehensive content management and publishing platform for blogs, magazines, and digital publications","totalEntities":8,"totalProperties":70,"totalAssociations":14},"namespaces":[{"name":"ContentManagement","displayName":"Content Management","description":"Core content creation, editing, and organization features","modelCount":2,"entityCount":4,"models":[{"name":"Publications","displayName":"Publications Management","description":"Individual blogs, magazines, or publications within the platform","entityCount":1,"entities":[{"name":"publication","displayName":"Publication","description":"Individual blog or publication site","propertyCount":8,"associationCount":0,"properties":[{"name":"id","displayName":"Publication ID","description":"Unique identifier for the publication","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"name","displayName":"Publication Name","description":"Display name of the publication","type":"string","semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the publication","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"description","displayName":"Description","description":"Publication description and tagline","type":"string","semantics":["Semantic#Description"]},{"name":"domain","displayName":"Custom Domain","description":"Custom domain name for the publication","type":"string","semantics":[]},{"name":"logo_url","displayName":"Logo URL","description":"URL to publication logo image","type":"string","semantics":["Semantic#ImageURL"]},{"name":"status","displayName":"Publication Status","description":"Current status of the publication","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["active","suspended","archived"],"defaultValue":"active"},{"name":"created_at","displayName":"Created At","description":"When the publication was created","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[],"semantics":[]}]},{"name":"Content","displayName":"Content Management","description":"Posts, pages, and other content types","entityCount":3,"entities":[{"name":"category","displayName":"Content Category","description":"Content categorization for organization and navigation","propertyCount":5,"associationCount":2,"properties":[{"name":"id","displayName":"Category ID","description":"Unique identifier for the category","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"name","displayName":"Category Name","description":"Display name of the category","type":"string","semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the category","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"description","displayName":"Description","description":"Category description","type":"string","semantics":["Semantic#Description"]},{"name":"color","displayName":"Color","description":"Theme color for the category","type":"string","semantics":[]}],"associations":[{"name":"publication","displayName":"Publication","description":"Publication this category belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"},{"name":"parentCategory","displayName":"Parent Category","description":"Parent category for hierarchical organization","required":false,"multiple":false,"targetEntities":["category"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]},{"name":"Tag","displayName":"Content Tag","description":"Tags for flexible content labeling and discovery","propertyCount":3,"associationCount":1,"properties":[{"name":"id","displayName":"Tag ID","description":"Unique identifier for the tag","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"name","displayName":"Tag Name","description":"Display name of the tag","type":"string","semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the tag","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]}],"associations":[{"name":"publication","displayName":"Publication","description":"Publication this tag belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]},{"name":"Post","displayName":"Blog Post","description":"Individual blog posts and articles","propertyCount":16,"associationCount":4,"properties":[{"name":"id","displayName":"Post ID","description":"Unique identifier for the post","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"title","displayName":"Post Title","description":"Title of the blog post","type":"string","required":true,"semantics":["Semantic#Title"]},{"name":"slug","displayName":"URL Slug","description":"URL-friendly identifier for the post","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"excerpt","displayName":"Excerpt","description":"Brief summary or excerpt of the post","type":"string","semantics":["Semantic#Summary"]},{"name":"content","displayName":"Content","description":"Full content of the post in HTML or Markdown","type":"string","required":true,"semantics":["Semantic#HTML"]},{"name":"content_format","displayName":"Content Format","description":"Format of the content (HTML, Markdown, etc.)","type":"string","required":true,"semantics":[],"enumValues":["html","markdown","richtext"],"defaultValue":"markdown"},{"name":"featured_image_url","displayName":"Featured Image URL","description":"URL to featured image","type":"string","semantics":["Semantic#ImageURL"]},{"name":"status","displayName":"Post Status","description":"Current publishing status of the post","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["draft","pending_review","scheduled","published","archived"],"defaultValue":"draft"},{"name":"published_at","displayName":"Published At","description":"When the post was published","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]},{"name":"scheduled_at","displayName":"Scheduled At","description":"When the post is scheduled to be published","type":"datetime","semantics":[]},{"name":"view_count","displayName":"View Count","description":"Number of times the post has been viewed","type":"number","required":true,"semantics":[]},{"name":"reading_time","displayName":"Reading Time","description":"Estimated reading time in minutes","type":"number","readOnly":true,"semantics":["Semantic#Calculated"]},{"name":"word_count","displayName":"Word Count","description":"Number of words in the post content","type":"number","readOnly":true,"semantics":["Semantic#Calculated"]},{"name":"meta_title","displayName":"Meta Title","description":"SEO meta title","type":"string","semantics":[]},{"name":"meta_description","displayName":"Meta Description","description":"SEO meta description","type":"string","semantics":[]},{"name":"updated_at","displayName":"Updated At","description":"When the post was last updated","type":"datetime","readOnly":true,"semantics":["Semantic#UpdatedTimestamp"]}],"associations":[{"name":"publication","displayName":"Publication","description":"Publication this post belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"},{"name":"categories","displayName":"Post Categories","description":"Categories this post belongs to","required":false,"multiple":true,"targetEntities":["category"],"semantics":["Semantic#Categories"],"cardinality":"One-to-Many"},{"name":"tags","displayName":"Post Tags","description":"Tags associated with this post","required":false,"multiple":true,"targetEntities":["Tag"],"semantics":["Semantic#Tags"],"cardinality":"One-to-Many"},{"name":"author","displayName":"Post Author","description":"Author who wrote this post","required":true,"multiple":false,"targetEntities":["user"],"semantics":["Semantic#ResourceOwnerIdentifier"],"cardinality":"One-to-One"}],"semantics":[]}]}]},{"name":"UserManagement","displayName":"User Management","description":"Authors, editors, subscribers, and user roles management","modelCount":1,"entityCount":1,"models":[{"name":"Users","displayName":"User Management","description":"User accounts and authentication","entityCount":1,"entities":[{"name":"user","displayName":"User Account","description":"User account for authors, editors, and subscribers","propertyCount":14,"associationCount":0,"properties":[{"name":"id","displayName":"User ID","description":"Unique identifier for the user","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"email","displayName":"Email Address","description":"User email address for login and communication","type":"string","required":true,"semantics":["Semantic#Email"]},{"name":"password","displayName":"Password","description":"Encrypted password for authentication","type":"string","required":true,"semantics":["Semantic#Password"]},{"name":"username","displayName":"Username","description":"Unique username for the user","type":"string","required":true,"unique":true,"semantics":["Semantic#PublicUniqueName"]},{"name":"display_name","displayName":"Display Name","description":"Public display name for the user","type":"string","required":true,"semantics":[]},{"name":"first_name","displayName":"First Name","description":"User first name","type":"string","semantics":[]},{"name":"last_name","displayName":"Last Name","description":"User last name","type":"string","semantics":[]},{"name":"bio","displayName":"Biography","description":"User biography and description","type":"string","semantics":["Semantic#Description"]},{"name":"avatar_url","displayName":"Avatar URL","description":"URL to user profile picture","type":"string","semantics":["Semantic#ImageURL"]},{"name":"website","displayName":"Website","description":"User personal website URL","type":"string","semantics":["Semantic#URL"]},{"name":"role","displayName":"User Role","description":"User role for permission management","type":"string","required":true,"semantics":["Semantic#UserRole"],"enumValues":["subscriber","author","editor","admin","super_admin"],"defaultValue":"subscriber"},{"name":"status","displayName":"User Status","description":"Current status of the user account","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["active","inactive","suspended","pending_verification"],"defaultValue":"active"},{"name":"emailVerified","displayName":"Email Verified","description":"Whether the user has verified their email","type":"boolean","required":true,"semantics":[],"defaultValue":"false"},{"name":"created_at","displayName":"Created At","description":"When the user account was created","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[],"semantics":["Semantic#User"]}]}]},{"name":"SocialFeatures","displayName":"Social Features","description":"Comments, likes, shares, and social interactions","modelCount":1,"entityCount":1,"models":[{"name":"Comments","displayName":"Comment System","description":"Post comments and replies","entityCount":1,"entities":[{"name":"comment","displayName":"Comment","description":"User comments on posts","propertyCount":7,"associationCount":3,"properties":[{"name":"id","displayName":"Comment ID","description":"Unique identifier for the comment","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"content","displayName":"Comment Content","description":"Content of the comment","type":"string","required":true,"semantics":[]},{"name":"author_name","displayName":"Author Name","description":"Name of the comment author (for guest comments)","type":"string","semantics":[]},{"name":"author_email","displayName":"Author Email","description":"Email of the comment author (for guest comments)","type":"string","required":true,"semantics":["Semantic#Email"]},{"name":"status","displayName":"Comment Status","description":"Moderation status of the comment","type":"string","required":true,"semantics":["Semantic#Status"],"enumValues":["pending","approved","rejected","spam"],"defaultValue":"pending"},{"name":"user_agent","displayName":"User Agent","description":"Browser user agent string","type":"string","semantics":[]},{"name":"created_at","displayName":"Created At","description":"When the comment was created","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[{"name":"post","displayName":"Post","description":"Post this comment belongs to","required":true,"multiple":false,"targetEntities":["Post"],"semantics":[],"cardinality":"One-to-One"},{"name":"author","displayName":"Comment Author","description":"Registered user who wrote this comment","required":false,"multiple":false,"targetEntities":["user"],"semantics":["Semantic#ResourceOwnerIdentifier"],"cardinality":"One-to-One"},{"name":"parent_comment","displayName":"Parent Comment","description":"Parent comment for replies","required":false,"multiple":false,"targetEntities":["comment"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]}]}]},{"name":"Analytics","displayName":"Analytics & Tracking","description":"Analytics, metrics, and performance tracking","modelCount":1,"entityCount":1,"models":[{"name":"Analytics","displayName":"Content Analytics","description":"Content performance and user engagement metrics","entityCount":1,"entities":[{"name":"page_view","displayName":"Page View","description":"Individual page view tracking record","propertyCount":7,"associationCount":2,"properties":[{"name":"id","displayName":"Page View ID","description":"Unique identifier for the page view","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"path","displayName":"Page Path","description":"URL path of the viewed page","type":"string","required":true,"semantics":[]},{"name":"referrer","displayName":"Referrer","description":"Referring URL","type":"string","semantics":["Semantic#URL"]},{"name":"ip_address","displayName":"IP Address","description":"Visitor IP address","type":"string","semantics":["Semantic#ClientIPAddress"]},{"name":"user_agent","displayName":"User Agent","description":"Browser user agent string","type":"string","semantics":[]},{"name":"session_id","displayName":"Session ID","description":"Visitor session identifier","type":"string","semantics":[]},{"name":"viewed_at","displayName":"Viewed At","description":"When the page was viewed","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[{"name":"post","displayName":"Viewed Post","description":"Post that was viewed (if applicable)","required":false,"multiple":false,"targetEntities":["Post"],"semantics":[],"cardinality":"One-to-One"},{"name":"publication","displayName":"Publication","description":"Publication this page view belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]}]}]},{"name":"MediaManagement","displayName":"Media Management","description":"Image, video, and file upload management","modelCount":1,"entityCount":1,"models":[{"name":"Media","displayName":"Media Library","description":"Uploaded files, images, and media assets","entityCount":1,"entities":[{"name":"media_file","displayName":"Media File","description":"Uploaded media files (images, videos, documents)","propertyCount":10,"associationCount":2,"properties":[{"name":"id","displayName":"Media File ID","description":"Unique identifier for the media file","type":"string","primary":true,"readOnly":true,"semantics":[]},{"name":"filename","displayName":"File Name","description":"Original filename of the uploaded file","type":"string","required":true,"semantics":[]},{"name":"storage_key","displayName":"Storage Key","description":"Unique storage key for the file","type":"string","required":true,"unique":true,"semantics":[]},{"name":"url","displayName":"File URL","description":"Public URL to access the file","type":"string","required":true,"semantics":["Semantic#URL"]},{"name":"mime_type","displayName":"MIME Type","description":"MIME type of the file","type":"string","required":true,"semantics":[]},{"name":"file_size","displayName":"File Size","description":"File size in bytes","type":"number","required":true,"semantics":[]},{"name":"width","displayName":"Image Width","description":"Width in pixels (for images)","type":"number","required":true,"semantics":[]},{"name":"height","displayName":"Image Height","description":"Height in pixels (for images)","type":"number","required":true,"semantics":[]},{"name":"alt_text","displayName":"Alt Text","description":"Alternative text for accessibility","type":"string","semantics":[]},{"name":"uploaded_at","displayName":"Uploaded At","description":"When the file was uploaded","type":"datetime","readOnly":true,"semantics":["Semantic#CreatedTimestamp"]}],"associations":[{"name":"uploader","displayName":"File Uploader","description":"User who uploaded this file","required":true,"multiple":false,"targetEntities":["user"],"semantics":["Semantic#ResourceOwnerIdentifier"],"cardinality":"One-to-One"},{"name":"publication","displayName":"Publication","description":"Publication this media file belongs to","required":true,"multiple":false,"targetEntities":["publication"],"semantics":[],"cardinality":"One-to-One"}],"semantics":[]}]}]}]}}