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
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<%
|
|
2
|
+
/**
|
|
3
|
+
* UnifiedCollection.json.ejs
|
|
4
|
+
*
|
|
5
|
+
* Generates a single Postman 2.1.0 collection covering ALL modules in the
|
|
6
|
+
* system. The hierarchy is:
|
|
7
|
+
*
|
|
8
|
+
* {SystemName} API
|
|
9
|
+
* └── {ModuleName}
|
|
10
|
+
* └── {AggregateName}
|
|
11
|
+
* ├── POST Create {Agg}
|
|
12
|
+
* ├── GET Get {Agg} by ID
|
|
13
|
+
* ├── GET Get All {Agg}s
|
|
14
|
+
* ├── PUT Update {Agg}
|
|
15
|
+
* ├── DELETE Delete {Agg}
|
|
16
|
+
* └── PUT {Custom} {Agg} (confirm, cancel, …)
|
|
17
|
+
*
|
|
18
|
+
* Context expected:
|
|
19
|
+
* - systemName : string
|
|
20
|
+
* - collectionId : string (UUID)
|
|
21
|
+
* - port : number
|
|
22
|
+
* - modules : Array<ModuleCtx>
|
|
23
|
+
*
|
|
24
|
+
* ModuleCtx:
|
|
25
|
+
* - name : string (module name, e.g. "product")
|
|
26
|
+
* - aggregates : Array<AggregateCtx>
|
|
27
|
+
*
|
|
28
|
+
* AggregateCtx:
|
|
29
|
+
* - name : string (PascalCase, e.g. "Product")
|
|
30
|
+
* - trackUser : boolean
|
|
31
|
+
* - idType : string ("String" | "UUID" | "Long")
|
|
32
|
+
* - exampleId : string
|
|
33
|
+
* - operations : Array<OperationCtx> (when endpoints: section exists)
|
|
34
|
+
* - defaultCrud : boolean (true when no endpoints: section)
|
|
35
|
+
* - bodies : { [useCase]: object } (pre-generated JSON bodies keyed by useCase)
|
|
36
|
+
*
|
|
37
|
+
* OperationCtx:
|
|
38
|
+
* - useCase : string
|
|
39
|
+
* - method : string (GET, POST, PUT, PATCH, DELETE)
|
|
40
|
+
* - path : string (e.g. "/{id}" or "/publish")
|
|
41
|
+
* - basePath : string (e.g. "/products")
|
|
42
|
+
* - version : string (e.g. "v1")
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
function buildUrl(port, version, basePath, opPath) {
|
|
46
|
+
// Normalise: remove leading slash from basePath if present, and from opPath
|
|
47
|
+
let bPath = basePath.replace(/^\//, '');
|
|
48
|
+
let oPath = opPath.replace(/^\//, '');
|
|
49
|
+
|
|
50
|
+
// Build full path segments
|
|
51
|
+
const segments = ['api', version];
|
|
52
|
+
if (bPath) segments.push(...bPath.split('/').filter(Boolean));
|
|
53
|
+
if (oPath) segments.push(...oPath.split('/').filter(Boolean));
|
|
54
|
+
|
|
55
|
+
const raw = 'http://localhost:' + port + '/' + segments.join('/');
|
|
56
|
+
return { raw, segments, port: String(port) };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hasBody(method) {
|
|
60
|
+
return method === 'POST' || method === 'PUT' || method === 'PATCH';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ----- Build the items tree ------------------------------------------------
|
|
64
|
+
const moduleItems = [];
|
|
65
|
+
|
|
66
|
+
for (const mod of modules) {
|
|
67
|
+
const aggregateItems = [];
|
|
68
|
+
|
|
69
|
+
for (const agg of mod.aggregates) {
|
|
70
|
+
const requestItems = [];
|
|
71
|
+
|
|
72
|
+
if (agg.operations && agg.operations.length > 0) {
|
|
73
|
+
// Endpoint-driven: use declared operations
|
|
74
|
+
for (const op of agg.operations) {
|
|
75
|
+
const url = buildUrl(port, op.version, op.basePath, op.path);
|
|
76
|
+
// Replace {id} with example ID
|
|
77
|
+
const rawUrl = url.raw.replace(/\{id\}/g, agg.exampleId);
|
|
78
|
+
const pathSegments = url.segments.map(s => s === '{id}' ? agg.exampleId : s);
|
|
79
|
+
|
|
80
|
+
const headers = [{ key: 'Content-Type', value: 'application/json', type: 'text' }];
|
|
81
|
+
if (agg.trackUser) {
|
|
82
|
+
headers.push({ key: 'X-User', value: 'system', type: 'text' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const item = {
|
|
86
|
+
name: op.useCase,
|
|
87
|
+
request: {
|
|
88
|
+
method: op.method,
|
|
89
|
+
header: headers,
|
|
90
|
+
url: {
|
|
91
|
+
raw: rawUrl,
|
|
92
|
+
protocol: 'http',
|
|
93
|
+
host: ['localhost'],
|
|
94
|
+
port: String(port),
|
|
95
|
+
path: pathSegments
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
response: []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (hasBody(op.method) && agg.bodies[op.useCase]) {
|
|
102
|
+
item.request.body = {
|
|
103
|
+
mode: 'raw',
|
|
104
|
+
raw: JSON.stringify(agg.bodies[op.useCase], null, 2),
|
|
105
|
+
options: { raw: { language: 'json' } }
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
requestItems.push(item);
|
|
110
|
+
}
|
|
111
|
+
} else if (agg.defaultCrud) {
|
|
112
|
+
// No endpoints section → generate 5 standard CRUD requests
|
|
113
|
+
const resource = agg.resourceNameKebab;
|
|
114
|
+
const crudOps = [
|
|
115
|
+
{ useCase: 'Create ' + agg.name, method: 'POST', path: '' },
|
|
116
|
+
{ useCase: 'Get ' + agg.name + ' by ID', method: 'GET', path: '/' + agg.exampleId },
|
|
117
|
+
{ useCase: 'Get All ' + agg.name + 's', method: 'GET', path: '' },
|
|
118
|
+
{ useCase: 'Update ' + agg.name, method: 'PUT', path: '/' + agg.exampleId },
|
|
119
|
+
{ useCase: 'Delete ' + agg.name, method: 'DELETE', path: '/' + agg.exampleId },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const op of crudOps) {
|
|
123
|
+
const url = buildUrl(port, 'v1', resource, op.path);
|
|
124
|
+
|
|
125
|
+
const headers = [{ key: 'Content-Type', value: 'application/json', type: 'text' }];
|
|
126
|
+
if (agg.trackUser) {
|
|
127
|
+
headers.push({ key: 'X-User', value: 'system', type: 'text' });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const item = {
|
|
131
|
+
name: op.useCase,
|
|
132
|
+
request: {
|
|
133
|
+
method: op.method,
|
|
134
|
+
header: headers,
|
|
135
|
+
url: {
|
|
136
|
+
raw: url.raw,
|
|
137
|
+
protocol: 'http',
|
|
138
|
+
host: ['localhost'],
|
|
139
|
+
port: String(port),
|
|
140
|
+
path: url.segments
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
response: []
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (hasBody(op.method) && agg.bodies['default']) {
|
|
147
|
+
item.request.body = {
|
|
148
|
+
mode: 'raw',
|
|
149
|
+
raw: JSON.stringify(agg.bodies['default'], null, 2),
|
|
150
|
+
options: { raw: { language: 'json' } }
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
requestItems.push(item);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (requestItems.length > 0) {
|
|
159
|
+
aggregateItems.push({
|
|
160
|
+
name: agg.name,
|
|
161
|
+
item: requestItems
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (aggregateItems.length > 0) {
|
|
167
|
+
moduleItems.push({
|
|
168
|
+
name: mod.name,
|
|
169
|
+
item: aggregateItems
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const collection = {
|
|
175
|
+
info: {
|
|
176
|
+
_postman_id: collectionId,
|
|
177
|
+
name: systemName + ' API',
|
|
178
|
+
description: 'Auto-generated unified API collection for all modules in ' + systemName + '.\nGenerated by eva4j with realistic fake data.',
|
|
179
|
+
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json',
|
|
180
|
+
version: '1.0.0'
|
|
181
|
+
},
|
|
182
|
+
item: moduleItems
|
|
183
|
+
};
|
|
184
|
+
%>
|
|
185
|
+
<%- JSON.stringify(collection, null, 2) %>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package <%= packageName %>.shared.domain.annotations;
|
|
2
|
+
|
|
3
|
+
import java.lang.annotation.ElementType;
|
|
4
|
+
import java.lang.annotation.Retention;
|
|
5
|
+
import java.lang.annotation.RetentionPolicy;
|
|
6
|
+
import java.lang.annotation.Target;
|
|
7
|
+
|
|
8
|
+
@Target(ElementType.METHOD)
|
|
9
|
+
@Retention(RetentionPolicy.RUNTIME)
|
|
10
|
+
public @interface Loggable {
|
|
11
|
+
boolean before() default true;
|
|
12
|
+
boolean after() default true;
|
|
13
|
+
boolean exceptions() default true;
|
|
14
|
+
boolean timer() default false;
|
|
15
|
+
LogLevel level() default LogLevel.INFO;
|
|
16
|
+
String[] excludeArgs() default {};
|
|
17
|
+
}
|
package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package <%= packageName %>.shared.infrastructure.configurations.eventPublicationConfig;
|
|
2
|
+
|
|
3
|
+
import java.util.Map;
|
|
4
|
+
import javax.sql.DataSource;
|
|
5
|
+
|
|
6
|
+
import org.hibernate.boot.model.relational.Namespace;
|
|
7
|
+
import org.hibernate.boot.model.relational.Sequence;
|
|
8
|
+
import org.hibernate.mapping.Table;
|
|
9
|
+
import org.hibernate.tool.schema.spi.SchemaFilter;
|
|
10
|
+
import org.hibernate.tool.schema.spi.SchemaFilterProvider;
|
|
11
|
+
import org.slf4j.Logger;
|
|
12
|
+
import org.slf4j.LoggerFactory;
|
|
13
|
+
import org.springframework.boot.ApplicationRunner;
|
|
14
|
+
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
|
|
15
|
+
import org.springframework.context.annotation.Bean;
|
|
16
|
+
import org.springframework.context.annotation.Configuration;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fixes Spring Modulith JPA event_publication table columns.
|
|
20
|
+
* <p>
|
|
21
|
+
* Hibernate maps the internal {@code JpaEventPublication} String fields
|
|
22
|
+
* as {@code varchar(255)} by default, which is too short for serialised
|
|
23
|
+
* domain event payloads.
|
|
24
|
+
* <p>
|
|
25
|
+
* This config does two things:
|
|
26
|
+
* <ol>
|
|
27
|
+
* <li>Excludes the {@code event_publication} table from Hibernate DDL auto
|
|
28
|
+
* so it never tries to ALTER columns back to {@code varchar(255)}.</li>
|
|
29
|
+
* <li>Runs an {@link ApplicationRunner} that ensures the columns are {@code TEXT}.</li>
|
|
30
|
+
* </ol>
|
|
31
|
+
*/
|
|
32
|
+
@Configuration
|
|
33
|
+
public class EventPublicationSchemaConfig implements HibernatePropertiesCustomizer {
|
|
34
|
+
|
|
35
|
+
private static final Logger log = LoggerFactory.getLogger(EventPublicationSchemaConfig.class);
|
|
36
|
+
|
|
37
|
+
@Override
|
|
38
|
+
public void customize(Map<String, Object> hibernateProperties) {
|
|
39
|
+
hibernateProperties.put(
|
|
40
|
+
"hibernate.hbm2ddl.schema_filter_provider",
|
|
41
|
+
new EventPublicationFilterProvider()
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Bean
|
|
46
|
+
ApplicationRunner eventPublicationSchemaFix(DataSource dataSource) {
|
|
47
|
+
return args -> {
|
|
48
|
+
try (var conn = dataSource.getConnection()) {
|
|
49
|
+
var db = conn.getMetaData().getDatabaseProductName().toLowerCase();
|
|
50
|
+
String[] statements;
|
|
51
|
+
|
|
52
|
+
if (db.contains("postgre")) {
|
|
53
|
+
statements = new String[]{
|
|
54
|
+
"CREATE TABLE IF NOT EXISTS event_publication (id UUID NOT NULL PRIMARY KEY, completion_date TIMESTAMP, event_type TEXT NOT NULL, listener_id TEXT NOT NULL, publication_date TIMESTAMP, serialized_event TEXT NOT NULL)",
|
|
55
|
+
"ALTER TABLE event_publication ALTER COLUMN serialized_event TYPE TEXT",
|
|
56
|
+
"ALTER TABLE event_publication ALTER COLUMN event_type TYPE TEXT",
|
|
57
|
+
"ALTER TABLE event_publication ALTER COLUMN listener_id TYPE TEXT"
|
|
58
|
+
};
|
|
59
|
+
} else if (db.contains("mysql") || db.contains("maria")) {
|
|
60
|
+
statements = new String[]{
|
|
61
|
+
"CREATE TABLE IF NOT EXISTS event_publication (id VARCHAR(36) NOT NULL PRIMARY KEY, completion_date DATETIME, event_type TEXT NOT NULL, listener_id TEXT NOT NULL, publication_date DATETIME, serialized_event TEXT NOT NULL)",
|
|
62
|
+
"ALTER TABLE event_publication MODIFY COLUMN serialized_event TEXT",
|
|
63
|
+
"ALTER TABLE event_publication MODIFY COLUMN event_type TEXT",
|
|
64
|
+
"ALTER TABLE event_publication MODIFY COLUMN listener_id TEXT"
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
// H2 and other databases
|
|
68
|
+
statements = new String[]{
|
|
69
|
+
"CREATE TABLE IF NOT EXISTS event_publication (id UUID NOT NULL PRIMARY KEY, completion_date TIMESTAMP, event_type TEXT NOT NULL, listener_id TEXT NOT NULL, publication_date TIMESTAMP, serialized_event TEXT NOT NULL)",
|
|
70
|
+
"ALTER TABLE IF EXISTS event_publication ALTER COLUMN serialized_event TEXT",
|
|
71
|
+
"ALTER TABLE IF EXISTS event_publication ALTER COLUMN event_type TEXT",
|
|
72
|
+
"ALTER TABLE IF EXISTS event_publication ALTER COLUMN listener_id TEXT"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (var sql : statements) {
|
|
77
|
+
try (var stmt = conn.createStatement()) {
|
|
78
|
+
stmt.execute(sql);
|
|
79
|
+
} catch (Exception e) {
|
|
80
|
+
// Column already TEXT or statement not applicable — safe to ignore
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
log.debug("event_publication columns verified (TEXT)");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Hibernate Schema Filter ────────────────────────────────────────────
|
|
89
|
+
// Excludes event_publication from ddl-auto so Hibernate never tries
|
|
90
|
+
// to ALTER its columns back to varchar(255).
|
|
91
|
+
|
|
92
|
+
static class EventPublicationFilterProvider implements SchemaFilterProvider {
|
|
93
|
+
private static final SchemaFilter FILTER = new EventPublicationFilter();
|
|
94
|
+
|
|
95
|
+
@Override public SchemaFilter getCreateFilter() { return FILTER; }
|
|
96
|
+
@Override public SchemaFilter getDropFilter() { return FILTER; }
|
|
97
|
+
@Override public SchemaFilter getMigrateFilter() { return FILTER; }
|
|
98
|
+
@Override public SchemaFilter getValidateFilter() { return FILTER; }
|
|
99
|
+
@Override public SchemaFilter getTruncatorFilter() { return FILTER; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static class EventPublicationFilter implements SchemaFilter {
|
|
103
|
+
@Override public boolean includeNamespace(Namespace namespace) { return true; }
|
|
104
|
+
@Override public boolean includeSequence(Sequence sequence) { return true; }
|
|
105
|
+
@Override public boolean includeTable(Table table) {
|
|
106
|
+
return !"event_publication".equalsIgnoreCase(table.getName());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,56 +1,144 @@
|
|
|
1
1
|
package <%= packageName %>.shared.infrastructure.configurations.loggerConfig;
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import <%= packageName %>.shared.domain.annotations.*;
|
|
4
4
|
import org.aspectj.lang.JoinPoint;
|
|
5
5
|
import org.aspectj.lang.ProceedingJoinPoint;
|
|
6
6
|
import org.aspectj.lang.annotation.*;
|
|
7
|
+
import org.aspectj.lang.reflect.MethodSignature;
|
|
8
|
+
import org.slf4j.Logger;
|
|
9
|
+
import org.slf4j.LoggerFactory;
|
|
7
10
|
import org.springframework.stereotype.Component;
|
|
8
11
|
|
|
12
|
+
import java.util.Set;
|
|
13
|
+
import java.util.StringJoiner;
|
|
9
14
|
|
|
10
15
|
@Component
|
|
11
16
|
@Aspect
|
|
12
17
|
public class HandlerLogs {
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// ── Individual annotations ──────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
@Before("@annotation(logBefore)")
|
|
22
|
+
public void executionBeforeMethod(JoinPoint joinPoint, LogBefore logBefore) {
|
|
23
|
+
Logger logger = getLogger(joinPoint);
|
|
24
|
+
log(logger, logBefore.level(),
|
|
25
|
+
"▶ Entering {} with args: [{}]",
|
|
26
|
+
joinPoint.getSignature().getName(),
|
|
27
|
+
formatArgs(joinPoint, Set.of(logBefore.excludeArgs())));
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
@AfterReturning(pointcut = "@annotation(
|
|
24
|
-
public void executionSuccessMethod(JoinPoint joinPoint){
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
@AfterReturning(pointcut = "@annotation(logAfter)", returning = "result")
|
|
31
|
+
public void executionSuccessMethod(JoinPoint joinPoint, LogAfter logAfter, Object result) {
|
|
32
|
+
Logger logger = getLogger(joinPoint);
|
|
33
|
+
log(logger, logAfter.level(),
|
|
34
|
+
"◀ Completed {} with args: [{}] | return: {}",
|
|
35
|
+
joinPoint.getSignature().getName(),
|
|
36
|
+
formatArgs(joinPoint, Set.of(logAfter.excludeArgs())),
|
|
37
|
+
result);
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
@AfterThrowing(pointcut = "@annotation(
|
|
33
|
-
public void executionFailMethod(JoinPoint joinPoint, Exception exception){
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
@AfterThrowing(pointcut = "@annotation(logExceptions)", throwing = "exception")
|
|
41
|
+
public void executionFailMethod(JoinPoint joinPoint, LogExceptions logExceptions, Exception exception) {
|
|
42
|
+
Logger logger = getLogger(joinPoint);
|
|
43
|
+
log(logger, logExceptions.level(),
|
|
44
|
+
"✖ Method {} failed with args: [{}] | exception: {} | message: {}",
|
|
45
|
+
joinPoint.getSignature().getName(),
|
|
46
|
+
formatArgs(joinPoint, Set.of(logExceptions.excludeArgs())),
|
|
47
|
+
exception.getClass().getSimpleName(),
|
|
48
|
+
exception.getMessage());
|
|
40
49
|
}
|
|
41
50
|
|
|
42
|
-
@Around("@annotation(
|
|
43
|
-
public Object executionTimeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
51
|
+
@Around("@annotation(logTimer)")
|
|
52
|
+
public Object executionTimeMethod(ProceedingJoinPoint joinPoint, LogTimer logTimer) throws Throwable {
|
|
44
53
|
long start = System.currentTimeMillis();
|
|
45
|
-
Object
|
|
54
|
+
Object result = joinPoint.proceed();
|
|
46
55
|
long executionTime = System.currentTimeMillis() - start;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return
|
|
56
|
+
Logger logger = getLogger(joinPoint);
|
|
57
|
+
log(logger, logTimer.level(),
|
|
58
|
+
"⏱ Method {} executed in {} ms",
|
|
59
|
+
joinPoint.getSignature().getName(),
|
|
60
|
+
executionTime);
|
|
61
|
+
return result;
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
// ── Unified @Loggable annotation ───────────────────────────────
|
|
65
|
+
|
|
66
|
+
@Around("@annotation(loggable)")
|
|
67
|
+
public Object executionLoggableMethod(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
|
|
68
|
+
Logger logger = getLogger(joinPoint);
|
|
69
|
+
String methodName = joinPoint.getSignature().getName();
|
|
70
|
+
Set<String> excluded = Set.of(loggable.excludeArgs());
|
|
71
|
+
|
|
72
|
+
if (loggable.before()) {
|
|
73
|
+
log(logger, loggable.level(),
|
|
74
|
+
"▶ Entering {} with args: [{}]",
|
|
75
|
+
methodName,
|
|
76
|
+
formatArgs(joinPoint, excluded));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
long start = loggable.timer() ? System.currentTimeMillis() : 0;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
Object result = joinPoint.proceed();
|
|
83
|
+
|
|
84
|
+
if (loggable.after()) {
|
|
85
|
+
log(logger, loggable.level(),
|
|
86
|
+
"◀ Completed {} with args: [{}] | return: {}",
|
|
87
|
+
methodName,
|
|
88
|
+
formatArgs(joinPoint, excluded),
|
|
89
|
+
result);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (loggable.timer()) {
|
|
93
|
+
long executionTime = System.currentTimeMillis() - start;
|
|
94
|
+
log(logger, loggable.level(),
|
|
95
|
+
"⏱ Method {} executed in {} ms",
|
|
96
|
+
methodName,
|
|
97
|
+
executionTime);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
} catch (Exception exception) {
|
|
102
|
+
if (loggable.exceptions()) {
|
|
103
|
+
logger.warn("✖ Method {} failed with args: [{}] | exception: {} | message: {}",
|
|
104
|
+
methodName,
|
|
105
|
+
formatArgs(joinPoint, excluded),
|
|
106
|
+
exception.getClass().getSimpleName(),
|
|
107
|
+
exception.getMessage());
|
|
108
|
+
}
|
|
109
|
+
throw exception;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
55
112
|
|
|
113
|
+
// ── Private helpers ────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
private Logger getLogger(JoinPoint joinPoint) {
|
|
116
|
+
return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private String formatArgs(JoinPoint joinPoint, Set<String> excludedArgs) {
|
|
120
|
+
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
|
121
|
+
String[] paramNames = signature.getParameterNames();
|
|
122
|
+
Object[] args = joinPoint.getArgs();
|
|
123
|
+
|
|
124
|
+
if (paramNames == null || paramNames.length == 0) {
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
StringJoiner joiner = new StringJoiner(", ");
|
|
129
|
+
for (int i = 0; i < paramNames.length; i++) {
|
|
130
|
+
joiner.add(paramNames[i] + "=" +
|
|
131
|
+
(excludedArgs.contains(paramNames[i]) ? "[PROTECTED]" : args[i]));
|
|
132
|
+
}
|
|
133
|
+
return joiner.toString();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private void log(Logger logger, LogLevel level, String message, Object... args) {
|
|
137
|
+
switch (level) {
|
|
138
|
+
case TRACE -> logger.trace(message, args);
|
|
139
|
+
case DEBUG -> logger.debug(message, args);
|
|
140
|
+
case INFO -> logger.info(message, args);
|
|
141
|
+
case WARN -> logger.warn(message, args);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
56
144
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package <%= packageName %>.shared.domain.errorMessage;
|
|
2
|
+
|
|
3
|
+
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
4
|
+
|
|
5
|
+
import java.time.LocalDateTime;
|
|
6
|
+
import java.util.List;
|
|
7
|
+
|
|
8
|
+
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
9
|
+
public record ErrorResponse(
|
|
10
|
+
LocalDateTime timestamp,
|
|
11
|
+
int status,
|
|
12
|
+
String error,
|
|
13
|
+
String message,
|
|
14
|
+
List<String> details
|
|
15
|
+
) {
|
|
16
|
+
public ErrorResponse(int status, String error, String message) {
|
|
17
|
+
this(LocalDateTime.now(), status, error, message, null);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public ErrorResponse(int status, String error, String message, List<String> details) {
|
|
21
|
+
this(LocalDateTime.now(), status, error, message, details);
|
|
22
|
+
}
|
|
23
|
+
}
|