Model generator

This commit is contained in:
2026-05-15 13:21:00 +02:00
parent d7a3547fbf
commit d11923c4f2
3 changed files with 228 additions and 0 deletions

View File

@@ -23,6 +23,19 @@
- **Model**: Shared domain models
- **Tools**: `peakup-maven` – Maven plugin with two mojos: `PackageInfoGeneratorMojo` (generates `package-info.java`
with `@NonNull` per package) and `TypeScriptClientGeneratorMojo` (generates TS client from REST endpoints)
- **Tools**: `peakup-tools` – standalone dev utilities:
- `ModelGeneratorTool` – generates a full domain slice (entity, DAO, service, resource) from a JSON definition file.
Schema: `peakup-tools/src/main/resources/model-definition-schema.json`
Example: `peakup-tools/src/main/resources/example-model-definition.json`
Run (from project root):
```
$MVN -f peakup-tools/pom.xml -Dmaven.repo.local=/home/ingo/.m2/peak-repo \
compile exec:java -Dexec.mainClass=com.peakup.tools.generator.ModelGeneratorTool \
"-Dexec.args=path/to/definition.json"
```
Output defaults to `peakup-backend/src/main/java/`. Pass a second arg to override.
Generated service only contains `getDAO()` + domain-specific methods — CRUD is inherited from
`AbstractService` as `public final @Transactional`.
**Backend Architecture**:

View File

@@ -0,0 +1,215 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "model-definition-schema.json",
"title": "ModelDefinition",
"description": "Input schema for ModelGeneratorTool. Describes a single JPA entity and generates model, DAO, service and resource.",
"type": "object",
"required": ["modelName", "packageBase", "tableName", "baseClass", "apiPath"],
"additionalProperties": true,
"properties": {
"modelName": {
"type": "string",
"description": "PascalCase class name, e.g. 'TrainingNote'."
},
"packageBase": {
"type": "string",
"description": "Root package for all generated classes, e.g. 'com.peakup.trainingnote'.",
"pattern": "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)*$"
},
"tableName": {
"type": "string",
"description": "Database table name, e.g. 'training_note'."
},
"baseClass": {
"type": "string",
"description": "Superclass of the generated entity.",
"enum": ["AbstractEntity", "AbstractTimestampedEntity"]
},
"apiPath": {
"type": "string",
"description": "JAX-RS @Path value on the resource, e.g. '/api/training-notes'.",
"pattern": "^/"
},
"jsonIncludeNonNull": {
"type": "boolean",
"default": false,
"description": "When true, adds @JsonInclude(NON_NULL) to the entity class."
},
"fields": {
"type": "array",
"default": [],
"description": "Scalar and enum fields mapped to @Column.",
"items": {
"type": "object",
"required": ["name", "javaType", "nullable", "columnName"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "camelCase field name, e.g. 'athleteId'."
},
"javaType": {
"type": "string",
"description": "Java type. Primitives: int, long, double, float, boolean. Wrappers: Integer, Long, Double, Float, Boolean. String, Instant, LocalDate. Arrays: int[], long[], double[], String[] (use jdbcTypeCode=ARRAY). Any other simple name is resolved to packageBase.model, or provide a fully-qualified name."
},
"nullable": {
"type": "boolean",
"description": "When true, adds @Nullable and omits nullable=false on @Column."
},
"columnName": {
"type": "string",
"description": "Database column name, snake_case, e.g. 'athlete_id'."
},
"columnDefinition": {
"type": "string",
"description": "Optional JPA columnDefinition override, e.g. 'TEXT', 'integer[]', 'text[]'."
},
"jdbcTypeCode": {
"type": "string",
"description": "Optional Hibernate SqlTypes constant name. Currently only 'ARRAY' is supported.",
"enum": ["ARRAY"]
}
}
}
},
"elementCollections": {
"type": "array",
"default": [],
"description": "Fields mapped via @ElementCollection(fetch=LAZY). Supports both scalar (String) and embeddable collections.",
"items": {
"type": "object",
"required": ["fieldName", "collectionTable", "joinColumn", "valueType"],
"additionalProperties": false,
"properties": {
"fieldName": {
"type": "string",
"description": "camelCase field name on the entity, e.g. 'tags'."
},
"collectionTable": {
"type": "string",
"description": "Join table name, e.g. 'training_note_tag'."
},
"joinColumn": {
"type": "string",
"description": "FK column in the join table pointing to the owning entity, e.g. 'training_note_id'."
},
"valueType": {
"type": "string",
"description": "Element type. Use 'String' for scalar collections; use a class name for @Embeddable collections. Simple names are resolved to packageBase.model."
},
"valueColumn": {
"type": ["string", "null"],
"description": "Column name for scalar (String) collections, e.g. 'tag'. Omit or set null for embeddable collections."
},
"orderColumn": {
"type": ["string", "null"],
"description": "Column name for @OrderColumn, e.g. 'sort_index'. Null omits the annotation."
},
"embeddable": {
"description": "Inline @Embeddable class definition. When present, a separate embeddable source file is generated.",
"oneOf": [
{ "type": "null" },
{
"type": "object",
"required": ["className", "fields"],
"additionalProperties": false,
"properties": {
"className": {
"type": "string",
"description": "PascalCase class name for the embeddable, e.g. 'NoteAttachment'."
},
"fields": {
"type": "array",
"description": "Fields of the embeddable. Same structure as top-level fields.",
"items": {
"type": "object",
"required": ["name", "javaType", "nullable", "columnName"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"javaType": { "type": "string" },
"nullable": { "type": "boolean" },
"columnName": { "type": "string" },
"columnDefinition": { "type": "string" },
"jdbcTypeCode": { "type": "string", "enum": ["ARRAY"] }
}
}
}
}
}
]
}
}
}
},
"oneToManyRelations": {
"type": "array",
"default": [],
"description": "Fields mapped via @OneToMany(fetch=EAGER). Target entity must exist independently.",
"items": {
"type": "object",
"required": ["fieldName", "entityClass", "joinColumn", "orderBy"],
"additionalProperties": false,
"properties": {
"fieldName": {
"type": "string",
"description": "camelCase field name on the entity, e.g. 'steps'."
},
"entityClass": {
"type": "string",
"description": "Simple or fully-qualified target entity class name. Simple names are resolved to packageBase.model; FQNs (containing a dot) are used as-is."
},
"joinColumn": {
"type": "string",
"description": "FK column on the target entity side, e.g. 'training_note_id'."
},
"orderBy": {
"type": "string",
"description": "JPQL @OrderBy clause, e.g. 'sortIndex ASC'."
},
"fetchMode": {
"type": ["string", "null"],
"description": "Hibernate @Fetch mode constant, e.g. 'SUBSELECT'. Null omits the annotation.",
"enum": ["SUBSELECT", "SELECT", "JOIN", null]
}
}
}
},
"enums": {
"type": "array",
"default": [],
"description": "Enums to generate alongside the entity. Each implements PersistableEnum and includes an inner Converter with autoApply=true.",
"items": {
"type": "object",
"required": ["className", "values"],
"additionalProperties": false,
"properties": {
"className": {
"type": "string",
"description": "PascalCase enum name, e.g. 'NoteTypeEnum'."
},
"values": {
"type": "array",
"minItems": 1,
"description": "Ordered list of enum constants.",
"items": {
"type": "object",
"required": ["name", "key"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "UPPER_SNAKE_CASE constant name, e.g. 'GENERAL'."
},
"key": {
"type": "integer",
"description": "Stable integer persisted to the database. Must be unique within the enum."
}
}
}
}
}
}
}
}
}