eva4j 1.0.14 → 1.0.16
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.
- package/.agents/skills/skill-creator/LICENSE.txt +202 -0
- package/.agents/skills/skill-creator/SKILL.md +485 -0
- package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
- package/.agents/skills/skill-creator/agents/comparator.md +202 -0
- package/.agents/skills/skill-creator/agents/grader.md +223 -0
- package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
- package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.agents/skills/skill-creator/references/schemas.md +430 -0
- package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.agents/skills/skill-creator/scripts/utils.py +47 -0
- package/AGENTS.md +268 -6
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +430 -14
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +461 -13
- package/bin/eva4j.js +32 -14
- package/config/defaults.json +1 -0
- package/docs/commands/EVALUATE_SYSTEM.md +746 -261
- package/docs/commands/EXPORT_DIAGRAM.md +153 -0
- package/docs/commands/GENERATE_ENTITIES.md +599 -6
- package/docs/commands/INDEX.md +7 -0
- package/examples/domain-events.yaml +166 -20
- package/examples/domain-listeners.yaml +212 -0
- package/examples/domain-one-to-many.yaml +1 -0
- package/examples/domain-one-to-one.yaml +1 -0
- package/examples/domain-ports.yaml +414 -0
- package/examples/domain-soft-delete.yaml +47 -44
- package/examples/system/notification.yaml +147 -0
- package/examples/system/product.yaml +185 -0
- package/examples/system/system.yaml +112 -0
- package/examples/system-report.html +971 -0
- package/examples/system.yaml +46 -3
- package/package.json +2 -1
- package/src/agents/design-reviewer.agent.md +163 -0
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +1 -0
- package/src/commands/detach.js +149 -108
- package/src/commands/evaluate-system.js +234 -8
- package/src/commands/export-diagram.js +522 -0
- package/src/commands/generate-entities.js +685 -66
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +43 -10
- package/src/generators/base-generator.js +18 -6
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +12 -2
- package/src/skills/build-system-yaml/SKILL.md +323 -0
- package/src/skills/build-system-yaml/references/domain-yaml-spec.md +410 -0
- package/src/skills/build-system-yaml/references/module-spec.md +367 -0
- package/src/skills/build-system-yaml/references/system-yaml-spec.md +178 -0
- package/src/utils/config-manager.js +54 -0
- package/src/utils/context-builder.js +1 -0
- package/src/utils/domain-diagram.js +192 -0
- package/src/utils/domain-validator.js +1020 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/system-validator.js +319 -199
- package/src/utils/yaml-to-entity.js +272 -7
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +3 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +6 -5
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +4 -1
- package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
- package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
- package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
- package/templates/base/docker/Dockerfile.ejs +21 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/crud/ApplicationMapper.java.ejs +4 -0
- package/templates/crud/Controller.java.ejs +4 -4
- package/templates/crud/CreateCommand.java.ejs +4 -0
- package/templates/crud/CreateCommandHandler.java.ejs +3 -6
- package/templates/crud/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +12 -6
- package/templates/crud/EndpointsController.java.ejs +6 -6
- package/templates/crud/FindByQuery.java.ejs +1 -1
- package/templates/crud/FindByQueryHandler.java.ejs +3 -9
- package/templates/crud/GetQueryHandler.java.ejs +2 -5
- package/templates/crud/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -14
- package/templates/crud/ScaffoldCommandHandler.java.ejs +2 -4
- package/templates/crud/ScaffoldQuery.java.ejs +3 -2
- package/templates/crud/ScaffoldQueryHandler.java.ejs +5 -6
- package/templates/crud/SubEntityAddCommand.java.ejs +4 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +2 -6
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +2 -7
- package/templates/crud/TransitionCommandHandler.java.ejs +2 -6
- package/templates/crud/UpdateCommand.java.ejs +4 -0
- package/templates/crud/UpdateCommandHandler.java.ejs +2 -5
- package/templates/evaluate/report.html.ejs +394 -2
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +9 -0
- package/templates/kafka-listener/KafkaController.java.ejs +1 -1
- package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
- package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
- package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
- package/templates/kafka-listener/ListenerCommandHandler.java.ejs +25 -0
- package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
- package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
- package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
- package/templates/mock/MockEvent.java.ejs +10 -0
- package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
- package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
- package/templates/mock/SpringEventListener.java.ejs +61 -0
- package/templates/ports/PortDomainModel.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +67 -0
- package/templates/ports/PortFeignClient.java.ejs +45 -0
- package/templates/ports/PortFeignConfig.java.ejs +24 -0
- package/templates/ports/PortInterface.java.ejs +45 -0
- package/templates/ports/PortNestedType.java.ejs +28 -0
- package/templates/ports/PortRequestDto.java.ejs +30 -0
- package/templates/ports/PortResponseDto.java.ejs +28 -0
- package/templates/postman/Collection.json.ejs +1 -1
- package/templates/postman/UnifiedCollection.json.ejs +185 -0
- package/templates/shared/annotations/LogAfter.java.ejs +2 -0
- package/templates/shared/annotations/LogBefore.java.ejs +2 -0
- package/templates/shared/annotations/LogExceptions.java.ejs +2 -0
- package/templates/shared/annotations/LogLevel.java.ejs +8 -0
- package/templates/shared/annotations/LogTimer.java.ejs +1 -0
- package/templates/shared/annotations/Loggable.java.ejs +17 -0
- package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
- package/templates/shared/configurations/loggerConfig/HandlerLogs.java.ejs +120 -32
- package/templates/shared/errorMessage/ErrorResponse.java.ejs +23 -0
- package/templates/shared/handlerException/HandlerExceptions.java.ejs +99 -79
- package/src/commands/generate-system.js +0 -243
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +0 -1103
- package/templates/base/root/skill-build-domain-yaml.ejs +0 -292
- package/templates/base/root/skill-build-system-yaml.ejs +0 -252
- package/templates/shared/errorMessage/ErrorMessage.java.ejs +0 -5
- package/templates/shared/errorMessage/FullErrorMessage.java.ejs +0 -9
- package/templates/shared/errorMessage/ShortErrorMessage.java.ejs +0 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const yaml = require('js-yaml');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const pluralize = require('pluralize');
|
|
4
|
-
const { toPascalCase, toCamelCase, toSnakeCase } = require('./naming');
|
|
4
|
+
const { toPascalCase, toCamelCase, toSnakeCase, toKebabCase } = require('./naming');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Parse domain.yaml and extract aggregates with entities and value objects
|
|
@@ -23,11 +23,38 @@ async function parseDomainYaml(yamlPath, packageName = '', moduleName = '') {
|
|
|
23
23
|
packageName,
|
|
24
24
|
moduleName
|
|
25
25
|
}));
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
const endpoints = parseEndpoints(domainData);
|
|
28
|
+
const listeners = parseListeners(domainData);
|
|
29
|
+
|
|
30
|
+
// ── C2-006: useCase name collision between endpoints and listeners ─────────
|
|
31
|
+
// Both sections generate a "{UseCase}Command.java". The generator processes
|
|
32
|
+
// listeners first, then endpoints — the endpoint run silently overwrites the
|
|
33
|
+
// listener command, leaving the KafkaListener dispatching a constructor that
|
|
34
|
+
// no longer exists → compile error.
|
|
35
|
+
const endpointUseCases = new Set(
|
|
36
|
+
endpoints ? endpoints.versions.flatMap(v => v.operations.map(op => op.useCase)) : []
|
|
37
|
+
);
|
|
38
|
+
const collisions = listeners
|
|
39
|
+
.map(l => l.useCase)
|
|
40
|
+
.filter(uc => endpointUseCases.has(uc));
|
|
41
|
+
if (collisions.length > 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`[C2-006] useCase name collision in domain.yaml:\n` +
|
|
44
|
+
collisions.map(uc =>
|
|
45
|
+
` - "${uc}" appears in both endpoints: (operation) and listeners: (useCase).\n` +
|
|
46
|
+
` Both would generate "${uc}Command.java" — the endpoint version overwrites the listener version.\n` +
|
|
47
|
+
` Fix: rename the listener useCase, e.g. "${uc.replace(/^Create/, 'Initialize')}".`
|
|
48
|
+
).join('\n')
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
return {
|
|
28
53
|
aggregates,
|
|
29
54
|
allEnums: extractAllEnums(domainData.aggregates),
|
|
30
|
-
endpoints
|
|
55
|
+
endpoints,
|
|
56
|
+
listeners,
|
|
57
|
+
ports: parsePorts(domainData, moduleName)
|
|
31
58
|
};
|
|
32
59
|
}
|
|
33
60
|
|
|
@@ -80,10 +107,21 @@ function parseAggregate(aggregateData) {
|
|
|
80
107
|
return {
|
|
81
108
|
name: eventName,
|
|
82
109
|
fieldName: toCamelCase(eventName),
|
|
83
|
-
fields: eventFields
|
|
110
|
+
fields: eventFields,
|
|
111
|
+
triggers: event.triggers || []
|
|
84
112
|
};
|
|
85
113
|
});
|
|
86
114
|
|
|
115
|
+
// Build inverse map: { methodName → [event, ...] }
|
|
116
|
+
// Used by the AggregateRoot template to emit raise() calls inside transition methods.
|
|
117
|
+
const triggeredEventsMap = {};
|
|
118
|
+
domainEvents.forEach(event => {
|
|
119
|
+
(event.triggers || []).forEach(method => {
|
|
120
|
+
if (!triggeredEventsMap[method]) triggeredEventsMap[method] = [];
|
|
121
|
+
triggeredEventsMap[method].push(event);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
87
125
|
return {
|
|
88
126
|
name: toPascalCase(name),
|
|
89
127
|
packageName: aggregateData.package || '',
|
|
@@ -93,6 +131,7 @@ function parseAggregate(aggregateData) {
|
|
|
93
131
|
aggregateMethods,
|
|
94
132
|
allEntities: parsedEntities,
|
|
95
133
|
domainEvents,
|
|
134
|
+
triggeredEventsMap,
|
|
96
135
|
enums: aggregateEnums
|
|
97
136
|
};
|
|
98
137
|
}
|
|
@@ -109,8 +148,16 @@ function parseAggregate(aggregateData) {
|
|
|
109
148
|
* @returns {Object} Parsed entity
|
|
110
149
|
*/
|
|
111
150
|
function parseEntity(entityData, aggregateName, packageName = '', moduleName = '', aggregateEnums = [], valueObjectNames = [], inverseRelationships = {}) {
|
|
112
|
-
const { name, isRoot = false, tableName, properties, fields: fieldsYaml, relationships = [], auditable = false, audit } = entityData;
|
|
151
|
+
const { name, isRoot = false, tableName, properties, fields: fieldsYaml, relationships = [], auditable = false, audit, hasSoftDelete = false } = entityData;
|
|
113
152
|
|
|
153
|
+
// Validate hasSoftDelete
|
|
154
|
+
if (hasSoftDelete !== undefined && typeof hasSoftDelete !== 'boolean') {
|
|
155
|
+
throw new Error(`Entity "${name}": hasSoftDelete must be a boolean (true/false)`);
|
|
156
|
+
}
|
|
157
|
+
if (hasSoftDelete === true && isRoot === false) {
|
|
158
|
+
console.warn(`⚠️ Entity "${name}": hasSoftDelete is only supported on the aggregate root (isRoot: true). It will be ignored for secondary entities.`);
|
|
159
|
+
}
|
|
160
|
+
|
|
114
161
|
// Accept both 'properties' and 'fields' field names
|
|
115
162
|
let entityFields = properties || fieldsYaml || [];
|
|
116
163
|
|
|
@@ -174,6 +221,15 @@ function parseEntity(entityData, aggregateName, packageName = '', moduleName = '
|
|
|
174
221
|
];
|
|
175
222
|
}
|
|
176
223
|
}
|
|
224
|
+
|
|
225
|
+
// Inject deletedAt field for soft-delete root entities
|
|
226
|
+
const effectiveSoftDelete = hasSoftDelete === true && isRoot === true;
|
|
227
|
+
if (effectiveSoftDelete) {
|
|
228
|
+
entityFields = [
|
|
229
|
+
...entityFields,
|
|
230
|
+
{ name: 'deletedAt', type: 'LocalDateTime' }
|
|
231
|
+
];
|
|
232
|
+
}
|
|
177
233
|
|
|
178
234
|
const className = toPascalCase(name);
|
|
179
235
|
const fieldName = toCamelCase(name);
|
|
@@ -206,6 +262,7 @@ function parseEntity(entityData, aggregateName, packageName = '', moduleName = '
|
|
|
206
262
|
fieldName,
|
|
207
263
|
tableName: table,
|
|
208
264
|
isRoot,
|
|
265
|
+
hasSoftDelete: effectiveSoftDelete,
|
|
209
266
|
auditable: auditable === true, // Legacy support
|
|
210
267
|
audit: auditConfig, // New audit configuration
|
|
211
268
|
fields,
|
|
@@ -235,7 +292,7 @@ function buildAnnotationString(validation) {
|
|
|
235
292
|
if (value !== undefined) params.push(`value = ${value}`);
|
|
236
293
|
if (min !== undefined) params.push(`min = ${min}`);
|
|
237
294
|
if (max !== undefined) params.push(`max = ${max}`);
|
|
238
|
-
if (regexp !== undefined) params.push(`regexp = "${regexp}"`);
|
|
295
|
+
if (regexp !== undefined) params.push(`regexp = "${regexp.replace(/\\/g, '\\\\')}"`);
|
|
239
296
|
if (integer !== undefined) params.push(`integer = ${integer}`);
|
|
240
297
|
if (fraction !== undefined) params.push(`fraction = ${fraction}`);
|
|
241
298
|
if (inclusive !== undefined) params.push(`inclusive = ${inclusive}`);
|
|
@@ -990,6 +1047,212 @@ function parseEndpoints(domainData) {
|
|
|
990
1047
|
};
|
|
991
1048
|
}
|
|
992
1049
|
|
|
1050
|
+
/**
|
|
1051
|
+
* Parse the optional listeners section from domain.yaml.
|
|
1052
|
+
* Declares integration events this module CONSUMES from external producers.
|
|
1053
|
+
* @param {Object} domainData - Raw parsed YAML data
|
|
1054
|
+
* @returns {Array} Parsed listeners array (empty if not declared)
|
|
1055
|
+
*/
|
|
1056
|
+
function parseListeners(domainData) {
|
|
1057
|
+
if (!domainData.listeners || !Array.isArray(domainData.listeners)) return [];
|
|
1058
|
+
return domainData.listeners.map(listener => {
|
|
1059
|
+
const eventName = toPascalCase(listener.event);
|
|
1060
|
+
// Normalise: strip trailing 'Event' suffix for class naming, re-add it consistently
|
|
1061
|
+
const baseName = eventName.endsWith('Event') ? eventName.slice(0, -5) : eventName;
|
|
1062
|
+
const integrationEventClassName = `${baseName}IntegrationEvent`;
|
|
1063
|
+
// e.g. PaymentApprovedKafkaListener
|
|
1064
|
+
const listenerClassName = `${baseName}KafkaListener`;
|
|
1065
|
+
const useCaseName = toPascalCase(listener.useCase);
|
|
1066
|
+
const commandClassName = `${useCaseName}Command`;
|
|
1067
|
+
const topic = listener.topic || null;
|
|
1068
|
+
const fields = (listener.fields || []).map(f => ({
|
|
1069
|
+
name: toCamelCase(f.name),
|
|
1070
|
+
javaType: f.type
|
|
1071
|
+
}));
|
|
1072
|
+
const nestedTypes = (listener.nestedTypes || []).map(nt => ({
|
|
1073
|
+
name: toPascalCase(nt.name),
|
|
1074
|
+
fields: (nt.fields || []).map(f => ({
|
|
1075
|
+
name: toCamelCase(f.name),
|
|
1076
|
+
javaType: f.type
|
|
1077
|
+
}))
|
|
1078
|
+
}));
|
|
1079
|
+
return {
|
|
1080
|
+
event: eventName,
|
|
1081
|
+
baseName,
|
|
1082
|
+
producer: listener.producer || null,
|
|
1083
|
+
topic,
|
|
1084
|
+
useCase: useCaseName,
|
|
1085
|
+
commandClassName,
|
|
1086
|
+
integrationEventClassName,
|
|
1087
|
+
listenerClassName,
|
|
1088
|
+
fields,
|
|
1089
|
+
nestedTypes
|
|
1090
|
+
};
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Derive a domain model type name from a method name.
|
|
1096
|
+
* Strips common verb prefixes and 'ById/ByName/...' suffixes so that
|
|
1097
|
+
* 'findCustomerById' → 'Customer', 'processPayment' → 'Payment'.
|
|
1098
|
+
* @param {string} methodName camelCase method name
|
|
1099
|
+
* @returns {string} PascalCase domain model name
|
|
1100
|
+
*/
|
|
1101
|
+
function deriveDomainType(methodName) {
|
|
1102
|
+
let name = toPascalCase(methodName);
|
|
1103
|
+
|
|
1104
|
+
// Strip verb prefix
|
|
1105
|
+
name = name.replace(
|
|
1106
|
+
/^(Find|Get|Fetch|Search|Retrieve|List|Check|Process|Create|Update|Delete|Cancel|Submit|Execute)/,
|
|
1107
|
+
''
|
|
1108
|
+
);
|
|
1109
|
+
|
|
1110
|
+
// Strip trailing 'By{Something}' (e.g. ById, ByName, ByCode)
|
|
1111
|
+
name = name.replace(/By[A-Z][a-zA-Z0-9]*$/, '');
|
|
1112
|
+
|
|
1113
|
+
// Strip common informational suffixes
|
|
1114
|
+
name = name.replace(/(?:Status|Availability|All)$/, '');
|
|
1115
|
+
|
|
1116
|
+
// Fall back to full PascalCase method name if we stripped everything
|
|
1117
|
+
return name || toPascalCase(methodName);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Parse the optional ports section from domain.yaml.
|
|
1122
|
+
* Declares HTTP services this module CALLS synchronously (Feign clients).
|
|
1123
|
+
* Entries sharing the same service: are grouped into a single FeignClient.
|
|
1124
|
+
* @param {Object} domainData - Raw parsed YAML data
|
|
1125
|
+
* @param {string} moduleName - Module name (used for property key naming)
|
|
1126
|
+
* @returns {Array} Parsed port service groups (empty if not declared)
|
|
1127
|
+
*/
|
|
1128
|
+
function parsePorts(domainData, moduleName = '') {
|
|
1129
|
+
if (!domainData.ports || !Array.isArray(domainData.ports)) return [];
|
|
1130
|
+
|
|
1131
|
+
const serviceMap = new Map();
|
|
1132
|
+
|
|
1133
|
+
for (const entry of domainData.ports) {
|
|
1134
|
+
if (!entry.service || !entry.name) continue;
|
|
1135
|
+
|
|
1136
|
+
const serviceName = toPascalCase(entry.service);
|
|
1137
|
+
const methodName = toCamelCase(entry.name);
|
|
1138
|
+
const methodPascal = toPascalCase(entry.name);
|
|
1139
|
+
|
|
1140
|
+
// Parse HTTP verb + path
|
|
1141
|
+
const httpParts = (entry.http || 'GET /').trim().split(/\s+/);
|
|
1142
|
+
const httpVerb = (httpParts[0] || 'GET').toUpperCase();
|
|
1143
|
+
const httpPath = httpParts[1] || '/';
|
|
1144
|
+
|
|
1145
|
+
// Extract path variables: /screenings/{id}/seats → ['id']
|
|
1146
|
+
const pathVarMatches = httpPath.match(/\{(\w+)\}/g) || [];
|
|
1147
|
+
const pathVariables = pathVarMatches.map(pv => pv.slice(1, -1));
|
|
1148
|
+
|
|
1149
|
+
// Response fields
|
|
1150
|
+
const responseFields = (entry.fields || []).map(f => ({
|
|
1151
|
+
name: toCamelCase(f.name),
|
|
1152
|
+
javaType: f.type
|
|
1153
|
+
}));
|
|
1154
|
+
|
|
1155
|
+
// Body fields (POST/PUT/PATCH only)
|
|
1156
|
+
const bodyAllowed = httpVerb !== 'GET' && httpVerb !== 'DELETE';
|
|
1157
|
+
const bodyFields = bodyAllowed
|
|
1158
|
+
? (entry.body || []).map(f => ({ name: toCamelCase(f.name), javaType: f.type }))
|
|
1159
|
+
: [];
|
|
1160
|
+
|
|
1161
|
+
// nestedTypes per method
|
|
1162
|
+
const nestedTypes = (entry.nestedTypes || []).map(nt => ({
|
|
1163
|
+
name: toPascalCase(nt.name),
|
|
1164
|
+
fields: (nt.fields || []).map(f => ({
|
|
1165
|
+
name: toCamelCase(f.name),
|
|
1166
|
+
javaType: f.type
|
|
1167
|
+
}))
|
|
1168
|
+
}));
|
|
1169
|
+
|
|
1170
|
+
const returnList = entry.returnList === true;
|
|
1171
|
+
const hasResponse = responseFields.length > 0;
|
|
1172
|
+
const hasBody = bodyFields.length > 0;
|
|
1173
|
+
// ACL: infra DTO name (lives in infrastructure/adapters/{service}/)
|
|
1174
|
+
const infraDtoName = hasResponse ? `${methodPascal}Dto` : null;
|
|
1175
|
+
// Domain model type (lives in domain/models/)
|
|
1176
|
+
const domainType = hasResponse
|
|
1177
|
+
? (entry.domainType ? toPascalCase(entry.domainType) : deriveDomainType(entry.name))
|
|
1178
|
+
: null;
|
|
1179
|
+
// Keep responseDtoName as alias to infraDtoName for backward-compat
|
|
1180
|
+
const responseDtoName = infraDtoName;
|
|
1181
|
+
const requestDtoName = hasBody ? `${methodPascal}RequestDto` : null;
|
|
1182
|
+
|
|
1183
|
+
const method = {
|
|
1184
|
+
name: methodName,
|
|
1185
|
+
namePascal: methodPascal,
|
|
1186
|
+
httpVerb,
|
|
1187
|
+
httpPath,
|
|
1188
|
+
pathVariables,
|
|
1189
|
+
fields: responseFields,
|
|
1190
|
+
bodyFields,
|
|
1191
|
+
nestedTypes,
|
|
1192
|
+
returnList,
|
|
1193
|
+
hasResponse,
|
|
1194
|
+
hasBody,
|
|
1195
|
+
infraDtoName,
|
|
1196
|
+
domainType,
|
|
1197
|
+
responseDtoName,
|
|
1198
|
+
requestDtoName
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
if (!serviceMap.has(serviceName)) {
|
|
1202
|
+
serviceMap.set(serviceName, {
|
|
1203
|
+
serviceName,
|
|
1204
|
+
serviceNameCamelCase: toCamelCase(serviceName),
|
|
1205
|
+
target: entry.target || null,
|
|
1206
|
+
baseUrl: entry.baseUrl || null,
|
|
1207
|
+
methods: [],
|
|
1208
|
+
nestedTypes: [],
|
|
1209
|
+
domainModels: [] // ACL: unique domain models per service group
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const group = serviceMap.get(serviceName);
|
|
1214
|
+
group.methods.push(method);
|
|
1215
|
+
|
|
1216
|
+
// Keep baseUrl from the first entry that declares it
|
|
1217
|
+
if (entry.baseUrl && !group.baseUrl) {
|
|
1218
|
+
group.baseUrl = entry.baseUrl;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Deduplicate nestedTypes within the service group
|
|
1222
|
+
for (const nt of nestedTypes) {
|
|
1223
|
+
if (!group.nestedTypes.some(existing => existing.name === nt.name)) {
|
|
1224
|
+
group.nestedTypes.push(nt);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// ACL: collect unique domain models (by name) across all methods in this service
|
|
1229
|
+
if (method.domainType && method.hasResponse) {
|
|
1230
|
+
if (!group.domainModels.some(dm => dm.name === method.domainType)) {
|
|
1231
|
+
group.domainModels.push({
|
|
1232
|
+
name: method.domainType,
|
|
1233
|
+
fields: method.fields
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const moduleKebab = toKebabCase(moduleName);
|
|
1240
|
+
|
|
1241
|
+
return Array.from(serviceMap.values()).map(group => {
|
|
1242
|
+
const serviceKebab = toKebabCase(group.serviceName);
|
|
1243
|
+
return {
|
|
1244
|
+
...group,
|
|
1245
|
+
baseUrl: group.baseUrl || 'http://localhost:8080',
|
|
1246
|
+
baseUrlProperty: `${moduleKebab}.${serviceKebab}.base-url`,
|
|
1247
|
+
feignClientName: `${moduleKebab}-${serviceKebab}`,
|
|
1248
|
+
feignClientClassName: `${group.serviceName}FeignClient`,
|
|
1249
|
+
feignAdapterClassName: `${group.serviceName}FeignAdapter`,
|
|
1250
|
+
feignConfigClassName: `${group.serviceName}FeignConfig`,
|
|
1251
|
+
adapterPackage: group.serviceNameCamelCase
|
|
1252
|
+
};
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
|
|
993
1256
|
module.exports = {
|
|
994
1257
|
parseDomainYaml,
|
|
995
1258
|
parseAggregate,
|
|
@@ -998,5 +1261,7 @@ module.exports = {
|
|
|
998
1261
|
generateAggregateMethods,
|
|
999
1262
|
generateEntityImports,
|
|
1000
1263
|
generateValidationImports,
|
|
1001
|
-
generateAggregateMethodImports
|
|
1264
|
+
generateAggregateMethodImports,
|
|
1265
|
+
parseListeners,
|
|
1266
|
+
parsePorts
|
|
1002
1267
|
};
|
|
@@ -82,8 +82,9 @@ public class <%= aggregateName %>Mapper {
|
|
|
82
82
|
<% } else if (!rel.isCollection && rel.type === 'OneToOne') { %>
|
|
83
83
|
// Map OneToOne relationship <%= rel.fieldName %>
|
|
84
84
|
if (domain.get<%= rel.fieldName.charAt(0).toUpperCase() + rel.fieldName.slice(1) %>() != null) {
|
|
85
|
-
<%= rel.target %>Jpa <%= rel.fieldName %>Jpa = toJpa<%= rel.target %>(domain.get<%= rel.fieldName.charAt(0).toUpperCase() + rel.fieldName.slice(1) %>()
|
|
86
|
-
|
|
85
|
+
<%= rel.target %>Jpa <%= rel.fieldName %>Jpa = toJpa<%= rel.target %>(domain.get<%= rel.fieldName.charAt(0).toUpperCase() + rel.fieldName.slice(1) %>());
|
|
86
|
+
<% if (rel.mappedBy) { %> <%= rel.fieldName %>Jpa.set<%= rel.mappedBy.charAt(0).toUpperCase() + rel.mappedBy.slice(1) %>(jpa);
|
|
87
|
+
<% } %> jpa.set<%= rel.fieldName.charAt(0).toUpperCase() + rel.fieldName.slice(1) %>(<%= rel.fieldName %>Jpa);
|
|
87
88
|
}
|
|
88
89
|
<% } else if (!rel.isCollection) { %>
|
|
89
90
|
jpa.set<%= rel.fieldName.charAt(0).toUpperCase() + rel.fieldName.slice(1) %>(toJpa<%= rel.target %>(domain.get<%= rel.fieldName.charAt(0).toUpperCase() + rel.fieldName.slice(1) %>()));
|
|
@@ -17,9 +17,10 @@ public interface <%= rootEntity.name %>Repository {
|
|
|
17
17
|
|
|
18
18
|
Page<<%= rootEntity.name %>> findAll(Pageable pageable);
|
|
19
19
|
|
|
20
|
-
void deleteById(<%= rootEntity.fields[0].javaType %> id);
|
|
21
|
-
|
|
22
20
|
boolean existsById(<%= rootEntity.fields[0].javaType %> id);
|
|
21
|
+
<% if (!hasSoftDelete) { %>
|
|
22
|
+
void deleteById(<%= rootEntity.fields[0].javaType %> id);
|
|
23
|
+
<% } %>
|
|
23
24
|
<% if (findByOps && findByOps.length > 0) { %>
|
|
24
25
|
<% findByOps.forEach(function(op) { %>
|
|
25
26
|
Page<<%= rootEntity.name %>> <%= op.jpaMethodName %>(<%= op.fieldJavaType %> <%= op.fieldName %>, Pageable pageable);
|
|
@@ -49,15 +49,16 @@ public class <%= rootEntity.name %>RepositoryImpl implements <%= rootEntity.name
|
|
|
49
49
|
.map(mapper::toDomain);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
@Override
|
|
53
|
-
public void deleteById(<%= rootEntity.fields[0].javaType %> id) {
|
|
54
|
-
jpaRepository.deleteById(id);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
52
|
@Override
|
|
58
53
|
public boolean existsById(<%= rootEntity.fields[0].javaType %> id) {
|
|
59
54
|
return jpaRepository.existsById(id);
|
|
60
55
|
}
|
|
56
|
+
<% if (!hasSoftDelete) { %>
|
|
57
|
+
@Override
|
|
58
|
+
public void deleteById(<%= rootEntity.fields[0].javaType %> id) {
|
|
59
|
+
jpaRepository.deleteById(id);
|
|
60
|
+
}
|
|
61
|
+
<% } %>
|
|
61
62
|
<% if (findByOps && findByOps.length > 0) { %>
|
|
62
63
|
<% findByOps.forEach(function(op) { %>
|
|
63
64
|
@Override
|
|
@@ -18,6 +18,14 @@ import <%= packageName %>.shared.domain.DomainEvent;
|
|
|
18
18
|
import <%= packageName %>.<%= moduleName %>.domain.models.events.<%= event.name %>;
|
|
19
19
|
<% }); %>
|
|
20
20
|
<% } %>
|
|
21
|
+
<%
|
|
22
|
+
const _needsLocalDateTimeImport = Object.values(triggeredEventsMap || {}).flat().some(function(ev) {
|
|
23
|
+
return (ev.fields || []).some(function(ef) { return ef.name.endsWith('At') && ef.javaType === 'LocalDateTime'; });
|
|
24
|
+
}) && !imports.some(function(imp) { return imp.includes('LocalDateTime'); });
|
|
25
|
+
%>
|
|
26
|
+
<% if (_needsLocalDateTimeImport) { %>
|
|
27
|
+
import java.time.LocalDateTime;
|
|
28
|
+
<% } %>
|
|
21
29
|
import java.util.ArrayList;
|
|
22
30
|
import java.util.Collections;
|
|
23
31
|
import java.util.List;
|
|
@@ -82,7 +90,7 @@ public class <%= name %> {
|
|
|
82
90
|
<% }); %>
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
<% const creationFields = fields.filter(f => f.name !== 'id' && f.name !== 'createdAt' && f.name !== 'updatedAt' && f.name !== 'createdBy' && f.name !== 'updatedBy' && !f.readOnly && !f.autoInit); %>
|
|
93
|
+
<% const creationFields = fields.filter(f => f.name !== 'id' && f.name !== 'createdAt' && f.name !== 'updatedAt' && f.name !== 'createdBy' && f.name !== 'updatedBy' && f.name !== 'deletedAt' && !f.readOnly && !f.autoInit); %>
|
|
86
94
|
<% const autoInitFields = fields.filter(f => f.autoInit); %>
|
|
87
95
|
<% const defaultValueFields = fields.filter(f => f.readOnly && !f.autoInit && f.javaDefaultValue); %>
|
|
88
96
|
<% if (creationFields.length > 0 || autoInitFields.length > 0 || defaultValueFields.length > 0) { %>
|
|
@@ -184,8 +192,40 @@ public class <%= name %> {
|
|
|
184
192
|
}
|
|
185
193
|
<% } %>
|
|
186
194
|
this.<%= field.name %> = this.<%= field.name %>.transitionTo(<%- field.javaType %>.<%= transition.to %>);
|
|
195
|
+
<%
|
|
196
|
+
const _triggeredEvents = (triggeredEventsMap || {})[methodName] || [];
|
|
197
|
+
_triggeredEvents.forEach(function(evt) {
|
|
198
|
+
const _entityBase = name.charAt(0).toLowerCase() + name.slice(1);
|
|
199
|
+
const _args = ['this.getId()'];
|
|
200
|
+
(evt.fields || []).forEach(function(ef) {
|
|
201
|
+
// Skip {entityName}Id — already provided as aggregateId in the DomainEvent constructor
|
|
202
|
+
if (ef.name === _entityBase + 'Id') { return; }
|
|
203
|
+
const _matched = fields.find(function(f) { return f.name === ef.name; });
|
|
204
|
+
if (_matched) {
|
|
205
|
+
if (_matched.javaType === ef.javaType) {
|
|
206
|
+
_args.push('this.get' + ef.name.charAt(0).toUpperCase() + ef.name.slice(1) + '()'); return;
|
|
207
|
+
}
|
|
208
|
+
// Type mismatch: entity field may be a VO wrapping the expected primitive/type
|
|
209
|
+
var _vo = (valueObjects || []).find(function(vo) { return vo.name === _matched.javaType; });
|
|
210
|
+
if (_vo) {
|
|
211
|
+
var _voSub = _vo.fields.find(function(voF) { return voF.name === ef.name && voF.javaType === ef.javaType; });
|
|
212
|
+
if (!_voSub) { _voSub = _vo.fields.find(function(voF) { return voF.javaType === ef.javaType; }); }
|
|
213
|
+
if (_voSub) {
|
|
214
|
+
var _oCap = ef.name.charAt(0).toUpperCase() + ef.name.slice(1);
|
|
215
|
+
var _sCap = _voSub.name.charAt(0).toUpperCase() + _voSub.name.slice(1);
|
|
216
|
+
_args.push('this.get' + _oCap + '().get' + _sCap + '()'); return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
_args.push('null /* TODO: provide ' + ef.name + ' (entity returns ' + _matched.javaType + ', expected ' + ef.javaType + ') */'); return;
|
|
220
|
+
}
|
|
221
|
+
if (ef.name.endsWith('At') && ef.javaType === 'LocalDateTime') { _args.push('LocalDateTime.now()'); return; }
|
|
222
|
+
_args.push('null /* TODO: provide ' + ef.name + ' */');
|
|
223
|
+
});
|
|
224
|
+
%>
|
|
225
|
+
raise(new <%= evt.name %>(<%= _args.join(', ') %>));
|
|
226
|
+
<% }); %>
|
|
187
227
|
}
|
|
188
|
-
<% })
|
|
228
|
+
<% });%>
|
|
189
229
|
|
|
190
230
|
// ─── State Query Helpers ─────────────────────────────────────────────────
|
|
191
231
|
<% meta.enumValues.forEach(value => { %>
|
|
@@ -202,4 +242,22 @@ public class <%= name %> {
|
|
|
202
242
|
<% }); %>
|
|
203
243
|
<% }); %>
|
|
204
244
|
<% } %>
|
|
245
|
+
<% if (hasSoftDelete) { %>
|
|
246
|
+
|
|
247
|
+
// ─── Soft Delete ─────────────────────────────────────────────────────────
|
|
248
|
+
/**
|
|
249
|
+
* Marks this entity as deleted by setting deletedAt timestamp.
|
|
250
|
+
* Use this instead of physical deletion to preserve records.
|
|
251
|
+
*/
|
|
252
|
+
public void softDelete() {
|
|
253
|
+
if (this.deletedAt != null) {
|
|
254
|
+
throw new IllegalStateException("<%= name %> is already deleted");
|
|
255
|
+
}
|
|
256
|
+
this.deletedAt = java.time.LocalDateTime.now();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public boolean isDeleted() {
|
|
260
|
+
return this.deletedAt != null;
|
|
261
|
+
}
|
|
262
|
+
<% } %>
|
|
205
263
|
}
|
|
@@ -55,7 +55,10 @@ public class <%= aggregateName %>DomainEventHandler {
|
|
|
55
55
|
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
|
56
56
|
public void on<%= event.name %>(<%= event.name %> event) {
|
|
57
57
|
<% if (broker) { %>
|
|
58
|
-
|
|
58
|
+
<%
|
|
59
|
+
const _aggrIdField = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1) + 'Id';
|
|
60
|
+
%>
|
|
61
|
+
messageBroker.publish<%= event.integrationEventClassName %>(new <%= event.integrationEventClassName %>(<% if (event.fields && event.fields.length > 0) { event.fields.forEach(function(field, idx) { %><%= field.name === _aggrIdField ? 'event.getAggregateId()' : 'event.get' + field.name.charAt(0).toUpperCase() + field.name.slice(1) + '()' %><%= idx < event.fields.length - 1 ? ', ' : '' %><% }); } %>));
|
|
59
62
|
<% } else { %>
|
|
60
63
|
// TODO: handle <%= event.name %> — add your side-effect logic here
|
|
61
64
|
// e.g.: notificationService.notify(event);
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
package <%= packageName %>.<%= moduleName %>.domain.models.events;
|
|
2
2
|
|
|
3
3
|
import <%= packageName %>.shared.domain.DomainEvent;
|
|
4
|
-
<%
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
<%
|
|
5
|
+
// Fields named {aggregateName}Id are excluded — the aggregate id is already
|
|
6
|
+
// available via getAggregateId() inherited from DomainEvent.
|
|
7
|
+
const _aggregateBase = aggregateName.charAt(0).toLowerCase() + aggregateName.slice(1);
|
|
8
|
+
const _ownFields = fields.filter(f => f.name !== _aggregateBase + 'Id');
|
|
9
|
+
%>
|
|
10
|
+
<% const needsBigDecimal = _ownFields.some(f => f.javaType === 'BigDecimal'); %>
|
|
11
|
+
<% const needsLocalDate = _ownFields.some(f => f.javaType === 'LocalDate' || f.javaType === 'LocalDateTime' || f.javaType === 'LocalTime'); %>
|
|
12
|
+
<% const needsUUID = _ownFields.some(f => f.javaType === 'UUID'); %>
|
|
13
|
+
<% const needsList = _ownFields.some(f => f.isCollection); %>
|
|
8
14
|
<% if (needsBigDecimal) { %>
|
|
9
15
|
import java.math.BigDecimal;
|
|
10
16
|
<% } %>
|
|
@@ -18,6 +24,15 @@ import java.util.UUID;
|
|
|
18
24
|
<% if (needsList) { %>
|
|
19
25
|
import java.util.List;
|
|
20
26
|
<% } %>
|
|
27
|
+
<%
|
|
28
|
+
const _STANDARD_DOMAIN_TYPES = new Set(['String','Integer','Long','Double','Float','Boolean','BigDecimal','LocalDate','LocalDateTime','LocalTime','Instant','UUID']);
|
|
29
|
+
const _customDomainElementTypes = _ownFields
|
|
30
|
+
? [...new Set(_ownFields.filter(f => f.isCollection && f.collectionElementType && !_STANDARD_DOMAIN_TYPES.has(f.collectionElementType)).map(f => f.collectionElementType))]
|
|
31
|
+
: [];
|
|
32
|
+
%>
|
|
33
|
+
<% _customDomainElementTypes.forEach(typeName => { %>
|
|
34
|
+
import <%= packageName %>.<%= moduleName %>.domain.models.events.<%- typeName %>;
|
|
35
|
+
<% }); %>
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
38
|
* <%= name %> - Domain Event
|
|
@@ -25,23 +40,24 @@ import java.util.List;
|
|
|
25
40
|
* Raised when: TODO — describe the business fact this event represents.
|
|
26
41
|
* Aggregate: <%= aggregateName %>
|
|
27
42
|
*
|
|
43
|
+
* The aggregate id is available via {@link #getAggregateId()} (inherited from DomainEvent).
|
|
28
44
|
* This is a pure domain class. It carries no Spring or infrastructure dependencies.
|
|
29
45
|
* Publish it externally via <%= aggregateName %>DomainEventHandler (application layer).
|
|
30
46
|
*/
|
|
31
47
|
public final class <%= name %> extends DomainEvent {
|
|
32
48
|
|
|
33
|
-
<%
|
|
49
|
+
<% _ownFields.forEach(field => { %>
|
|
34
50
|
private final <%- field.javaType %> <%= field.name %>;
|
|
35
51
|
<% }); %>
|
|
36
52
|
|
|
37
|
-
public <%= name %>(String aggregateId<%
|
|
53
|
+
public <%= name %>(String aggregateId<% _ownFields.forEach(field => { %>, <%- field.javaType %> <%= field.name %><% }); %>) {
|
|
38
54
|
super(aggregateId);
|
|
39
|
-
<%
|
|
55
|
+
<% _ownFields.forEach(field => { %>
|
|
40
56
|
this.<%= field.name %> = <%= field.name %>;
|
|
41
57
|
<% }); %>
|
|
42
58
|
}
|
|
43
59
|
|
|
44
|
-
<%
|
|
60
|
+
<% _ownFields.forEach(field => { %>
|
|
45
61
|
public <%- field.javaType %> get<%= field.name.charAt(0).toUpperCase() + field.name.slice(1) %>() {
|
|
46
62
|
return <%= field.name %>;
|
|
47
63
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package <%= packageName %>.<%= moduleName %>.domain.models.events;
|
|
2
|
+
<%
|
|
3
|
+
const needsBigDecimal = fields && fields.some(f => f.javaType === 'BigDecimal');
|
|
4
|
+
const needsLocalDate = fields && fields.some(f => ['LocalDate','LocalDateTime','LocalTime'].includes(f.javaType));
|
|
5
|
+
const needsInstant = fields && fields.some(f => f.javaType === 'Instant');
|
|
6
|
+
const needsUUID = fields && fields.some(f => f.javaType === 'UUID');
|
|
7
|
+
const needsList = fields && fields.some(f => f.javaType && f.javaType.startsWith('List'));
|
|
8
|
+
%>
|
|
9
|
+
<% if (needsBigDecimal) { %>
|
|
10
|
+
import java.math.BigDecimal;
|
|
11
|
+
<% } %>
|
|
12
|
+
<% if (needsLocalDate) { %>
|
|
13
|
+
import java.time.LocalDate;
|
|
14
|
+
import java.time.LocalDateTime;
|
|
15
|
+
<% } %>
|
|
16
|
+
<% if (needsInstant) { %>
|
|
17
|
+
import java.time.Instant;
|
|
18
|
+
<% } %>
|
|
19
|
+
<% if (needsUUID) { %>
|
|
20
|
+
import java.util.UUID;
|
|
21
|
+
<% } %>
|
|
22
|
+
<% if (needsList) { %>
|
|
23
|
+
import java.util.List;
|
|
24
|
+
<% } %>
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* <%= name %> — snapshot value type carried in domain events.
|
|
28
|
+
*
|
|
29
|
+
* This is a pure domain record with no infrastructure dependencies.
|
|
30
|
+
* TODO: Define the fields this snapshot should carry.
|
|
31
|
+
* Example for an order item:
|
|
32
|
+
* String productId,
|
|
33
|
+
* String productName,
|
|
34
|
+
* Integer quantity,
|
|
35
|
+
* BigDecimal unitPrice
|
|
36
|
+
*/
|
|
37
|
+
public record <%= name %>(
|
|
38
|
+
<% if (fields && fields.length > 0) { %>
|
|
39
|
+
<% fields.forEach((field, idx) => { %>
|
|
40
|
+
<%- field.javaType %> <%= field.name %><%= idx < fields.length - 1 ? ',' : '' %>
|
|
41
|
+
<% }); %>
|
|
42
|
+
<% } else { %>
|
|
43
|
+
// TODO: Add the fields this snapshot should carry
|
|
44
|
+
<% } %>
|
|
45
|
+
) {
|
|
46
|
+
}
|
|
@@ -22,11 +22,17 @@ import <%= packageName %>.shared.domain.AuditableEntity;
|
|
|
22
22
|
<% } else if (auditable) { %>
|
|
23
23
|
import <%= packageName %>.shared.domain.AuditableEntity;
|
|
24
24
|
<% } %>
|
|
25
|
+
<% if (hasSoftDelete) { %>
|
|
26
|
+
import org.hibernate.annotations.SQLRestriction;
|
|
27
|
+
<% } %>
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* <%= name %>Jpa - JPA Entity
|
|
28
31
|
* Persistence entity with JPA annotations
|
|
29
32
|
*/
|
|
33
|
+
<% if (hasSoftDelete) { %>
|
|
34
|
+
@SQLRestriction("deleted_at IS NULL")
|
|
35
|
+
<% } %>
|
|
30
36
|
@Entity
|
|
31
37
|
@Table(name = "<%= tableName %>")
|
|
32
38
|
@Getter
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
FROM eclipse-temurin:<%= javaVersion %>-jdk-alpine AS builder
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY build.gradle settings.gradle ./
|
|
6
|
+
COPY gradle ./gradle
|
|
7
|
+
COPY gradlew ./
|
|
8
|
+
RUN chmod +x gradlew && ./gradlew dependencies --no-daemon || true
|
|
9
|
+
|
|
10
|
+
COPY src ./src
|
|
11
|
+
RUN ./gradlew bootJar --no-daemon -x test
|
|
12
|
+
|
|
13
|
+
FROM eclipse-temurin:<%= javaVersion %>-jre-alpine
|
|
14
|
+
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
|
|
17
|
+
COPY --from=builder /app/build/libs/*.jar app.jar
|
|
18
|
+
|
|
19
|
+
EXPOSE <%= serverPort %>
|
|
20
|
+
|
|
21
|
+
ENTRYPOINT ["java", "-jar", "app.jar"]
|