go-duck-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/generators/cache.js +107 -0
- package/generators/config.js +173 -0
- package/generators/devops.js +212 -0
- package/generators/docs.js +74 -0
- package/generators/graphql.js +38 -0
- package/generators/kratos.js +157 -0
- package/generators/logger.js +68 -0
- package/generators/metering.js +143 -0
- package/generators/migrations.js +240 -0
- package/generators/mqtt.js +87 -0
- package/generators/multitenancy.js +130 -0
- package/generators/postgrest.js +115 -0
- package/generators/repository.js +28 -0
- package/generators/resilience.js +69 -0
- package/generators/security.js +168 -0
- package/generators/swagger.js +145 -0
- package/generators/telemetry.js +121 -0
- package/generators/websocket.js +162 -0
- package/index.js +592 -0
- package/package.json +23 -0
- package/parser/gdl.js +162 -0
- package/templates/application.yml.hbs +18 -0
- package/templates/docs/gin_bottle.png +0 -0
- package/templates/docs/index.html.hbs +226 -0
- package/templates/docs/intro.mp4 +0 -0
- package/templates/docs/kratos_mark.png +0 -0
- package/templates/docs/layout.hbs +106 -0
- package/templates/docs/logo.png +0 -0
- package/templates/docs/pages/audit.hbs +39 -0
- package/templates/docs/pages/cli.hbs +83 -0
- package/templates/docs/pages/gdl.hbs +223 -0
- package/templates/docs/pages/graphql.hbs +51 -0
- package/templates/docs/pages/grpc.hbs +100 -0
- package/templates/docs/pages/index.hbs +181 -0
- package/templates/docs/pages/integrations.hbs +83 -0
- package/templates/docs/pages/observability.hbs +34 -0
- package/templates/docs/pages/realtime.hbs +43 -0
- package/templates/docs/pages/rest.hbs +149 -0
- package/templates/docs/pages/security.hbs +31 -0
- package/templates/go/controller.go.hbs +236 -0
- package/templates/go/entity.go.hbs +34 -0
- package/templates/go/enum.go.hbs +7 -0
- package/templates/go/main.go.hbs +186 -0
- package/templates/graphql/resolver.go.hbs +50 -0
- package/templates/graphql/schema.graphql.hbs +64 -0
- package/templates/kratos/service.go.hbs +104 -0
- package/templates/proto/entity.proto.hbs +95 -0
- package/test_parser.js +9 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export const generateSwaggerDocs = async (config, entities, outputDir) => {
|
|
6
|
+
const docsDir = path.join(outputDir, 'docs');
|
|
7
|
+
await fs.ensureDir(docsDir);
|
|
8
|
+
|
|
9
|
+
const swagger = {
|
|
10
|
+
openapi: '3.0.0',
|
|
11
|
+
info: {
|
|
12
|
+
title: `${config.name} API`,
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
description: `Generated documentation for ${config.name} microservice`
|
|
15
|
+
},
|
|
16
|
+
servers: [
|
|
17
|
+
{ url: 'http://localhost:8080/api', description: 'Local Development Server' }
|
|
18
|
+
],
|
|
19
|
+
paths: {},
|
|
20
|
+
components: {
|
|
21
|
+
schemas: {
|
|
22
|
+
Error: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
error: { type: 'string' }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 1. Add Entity Paths
|
|
33
|
+
for (const entity of entities) {
|
|
34
|
+
const name = entity.name.toLowerCase();
|
|
35
|
+
const capitalized = entity.name;
|
|
36
|
+
|
|
37
|
+
// Add Schema for Entity
|
|
38
|
+
swagger.components.schemas[capitalized] = {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
id: { type: 'integer' },
|
|
42
|
+
...entity.fields.reduce((acc, field) => {
|
|
43
|
+
acc[field.name] = { type: mapToSwaggerType(field.type) };
|
|
44
|
+
return acc;
|
|
45
|
+
}, {}),
|
|
46
|
+
createdAt: { type: 'string', format: 'date-time' },
|
|
47
|
+
updatedAt: { type: 'string', format: 'date-time' }
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// POST /entities
|
|
52
|
+
swagger.paths[`/${name}s`] = {
|
|
53
|
+
post: {
|
|
54
|
+
tags: [capitalized],
|
|
55
|
+
summary: `Create a new ${capitalized}`,
|
|
56
|
+
requestBody: {
|
|
57
|
+
required: true,
|
|
58
|
+
content: { 'application/json': { schema: { $ref: `#/components/schemas/${capitalized}` } } }
|
|
59
|
+
},
|
|
60
|
+
responses: {
|
|
61
|
+
201: { description: 'Created', content: { 'application/json': { schema: { $ref: `#/components/schemas/${capitalized}` } } } }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
get: {
|
|
65
|
+
tags: [capitalized],
|
|
66
|
+
summary: `Get all ${capitalized}s`,
|
|
67
|
+
parameters: [
|
|
68
|
+
{ name: 'page', in: 'query', schema: { type: 'integer' } },
|
|
69
|
+
{ name: 'size', in: 'query', schema: { type: 'integer' } },
|
|
70
|
+
{ name: 'eager', in: 'query', schema: { type: 'boolean' } }
|
|
71
|
+
],
|
|
72
|
+
responses: {
|
|
73
|
+
200: { description: 'OK', content: { 'application/json': { schema: { type: 'array', items: { $ref: `#/components/schemas/${capitalized}` } } } } }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// GET/PUT/PATCH/DELETE /entities/:id
|
|
79
|
+
swagger.paths[`/${name}s/{id}`] = {
|
|
80
|
+
get: {
|
|
81
|
+
tags: [capitalized],
|
|
82
|
+
summary: `Get ${capitalized} by ID`,
|
|
83
|
+
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }],
|
|
84
|
+
responses: {
|
|
85
|
+
200: { description: 'OK', content: { 'application/json': { schema: { $ref: `#/components/schemas/${capitalized}` } } } }
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
put: {
|
|
89
|
+
tags: [capitalized],
|
|
90
|
+
summary: `Update ${capitalized}`,
|
|
91
|
+
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }],
|
|
92
|
+
responses: {
|
|
93
|
+
200: { description: 'Updated', content: { 'application/json': { schema: { $ref: `#/components/schemas/${capitalized}` } } } }
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
delete: {
|
|
97
|
+
tags: [capitalized],
|
|
98
|
+
summary: `Delete ${capitalized}`,
|
|
99
|
+
parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }],
|
|
100
|
+
responses: {
|
|
101
|
+
204: { description: 'No Content' }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. Add System Paths
|
|
108
|
+
swagger.paths['/rpc/{table}'] = {
|
|
109
|
+
get: {
|
|
110
|
+
tags: ['Search'],
|
|
111
|
+
summary: 'Generic PostgREST-like Search',
|
|
112
|
+
parameters: [
|
|
113
|
+
{ name: 'table', in: 'path', required: true, schema: { type: 'string' } },
|
|
114
|
+
{ name: 'order', in: 'query', schema: { type: 'string' } },
|
|
115
|
+
{ name: 'limit', in: 'query', schema: { type: 'integer' } }
|
|
116
|
+
],
|
|
117
|
+
responses: { 200: { description: 'OK' } }
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
swagger.paths['/audit'] = {
|
|
122
|
+
get: {
|
|
123
|
+
tags: ['Audit'],
|
|
124
|
+
summary: 'View Audit Logs',
|
|
125
|
+
responses: { 200: { description: 'OK' } }
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
await fs.writeJson(path.join(docsDir, 'swagger.json'), swagger, { spaces: 2 });
|
|
130
|
+
console.log(chalk.gray(' Generated Swagger Documentation: swagger.json'));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const mapToSwaggerType = (type) => {
|
|
134
|
+
const types = {
|
|
135
|
+
'String': 'string',
|
|
136
|
+
'Integer': 'integer',
|
|
137
|
+
'Float': 'number',
|
|
138
|
+
'Boolean': 'boolean',
|
|
139
|
+
'Long': 'integer',
|
|
140
|
+
'BigDecimal': 'number',
|
|
141
|
+
'LocalDate': 'string',
|
|
142
|
+
'Instant': 'string'
|
|
143
|
+
};
|
|
144
|
+
return types[type] || 'string';
|
|
145
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export const generateTelemetryCode = async (config, outputDir) => {
|
|
6
|
+
const telemetryDir = path.join(outputDir, 'internal/telemetry');
|
|
7
|
+
const k8sDir = path.join(outputDir, 'k8s');
|
|
8
|
+
|
|
9
|
+
await fs.ensureDir(telemetryDir);
|
|
10
|
+
await fs.ensureDir(k8sDir);
|
|
11
|
+
|
|
12
|
+
const otelGo = `
|
|
13
|
+
package telemetry
|
|
14
|
+
|
|
15
|
+
import (
|
|
16
|
+
"context"
|
|
17
|
+
"fmt"
|
|
18
|
+
"log"
|
|
19
|
+
"time"
|
|
20
|
+
|
|
21
|
+
"{{app_name}}/config"
|
|
22
|
+
"go.opentelemetry.io/otel"
|
|
23
|
+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
|
24
|
+
"go.opentelemetry.io/otel/propagation"
|
|
25
|
+
"go.opentelemetry.io/otel/sdk/resource"
|
|
26
|
+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
27
|
+
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
|
|
28
|
+
"google.golang.org/grpc"
|
|
29
|
+
"google.golang.org/grpc/credentials/insecure"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// InitTelemetry initializes OpenTelemetry SDK
|
|
33
|
+
func InitTelemetry(cfg *config.Config) (func(context.Context) error, error) {
|
|
34
|
+
if !cfg.GoDuck.Telemetry.OTel.Enabled {
|
|
35
|
+
log.Println("OpenTelemetry is disabled.")
|
|
36
|
+
return func(context.Context) error { return nil }, nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ctx := context.Background()
|
|
40
|
+
|
|
41
|
+
// 1. Setup Resource
|
|
42
|
+
res, err := resource.New(ctx,
|
|
43
|
+
resource.WithAttributes(
|
|
44
|
+
semconv.ServiceNameKey.String(cfg.GoDuck.Name),
|
|
45
|
+
semconv.ServiceVersionKey.String(cfg.GoDuck.Version),
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
if err != nil {
|
|
49
|
+
return nil, fmt.Errorf("failed to create resource: %w", err)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Setup OTLP Exporter (gRPC)
|
|
53
|
+
conn, err := grpc.DialContext(ctx, cfg.GoDuck.Telemetry.OTel.Endpoint,
|
|
54
|
+
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
55
|
+
grpc.WithBlock(),
|
|
56
|
+
)
|
|
57
|
+
if err != nil {
|
|
58
|
+
return nil, fmt.Errorf("failed to create gRPC connection to OTel collector: %w", err)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
|
|
62
|
+
if err != nil {
|
|
63
|
+
return nil, fmt.Errorf("failed to create trace exporter: %w", err)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 3. Setup Tracer Provider
|
|
67
|
+
bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
|
|
68
|
+
tp := sdktrace.NewTracerProvider(
|
|
69
|
+
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(cfg.GoDuck.Telemetry.OTel.SamplerRatio)),
|
|
70
|
+
sdktrace.WithResource(res),
|
|
71
|
+
sdktrace.WithSpanProcessor(bsp),
|
|
72
|
+
)
|
|
73
|
+
otel.SetTracerProvider(tp)
|
|
74
|
+
|
|
75
|
+
// 4. Setup Text Map Propagator
|
|
76
|
+
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
|
77
|
+
|
|
78
|
+
log.Printf("OpenTelemetry initialized with endpoint: %s", cfg.GoDuck.Telemetry.OTel.Endpoint)
|
|
79
|
+
|
|
80
|
+
return tp.Shutdown, nil
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
const otelCollectorK8s = `
|
|
85
|
+
apiVersion: v1
|
|
86
|
+
kind: ConfigMap
|
|
87
|
+
metadata:
|
|
88
|
+
name: otel-collector-conf
|
|
89
|
+
labels:
|
|
90
|
+
app: {{app_name}}
|
|
91
|
+
data:
|
|
92
|
+
otel-collector-config.yaml: |
|
|
93
|
+
receivers:
|
|
94
|
+
otlp:
|
|
95
|
+
protocols:
|
|
96
|
+
grpc:
|
|
97
|
+
http:
|
|
98
|
+
processors:
|
|
99
|
+
batch:
|
|
100
|
+
resourcedetection:
|
|
101
|
+
detectors: [env, system]
|
|
102
|
+
exporters:
|
|
103
|
+
logging:
|
|
104
|
+
loglevel: debug
|
|
105
|
+
otlp:
|
|
106
|
+
endpoint: "jaeger-collector:4317"
|
|
107
|
+
tls:
|
|
108
|
+
insecure: true
|
|
109
|
+
service:
|
|
110
|
+
pipelines:
|
|
111
|
+
traces:
|
|
112
|
+
receivers: [otlp]
|
|
113
|
+
processors: [batch, resourcedetection]
|
|
114
|
+
exporters: [logging, otlp]
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
await fs.writeFile(path.join(telemetryDir, 'otel.go'), otelGo.replace(/{{app_name}}/g, config.name));
|
|
118
|
+
await fs.writeFile(path.join(k8sDir, 'otel-collector.yml'), otelCollectorK8s.replace(/{{app_name}}/g, config.name));
|
|
119
|
+
|
|
120
|
+
console.log(chalk.gray(' Generated OpenTelemetry Telemetry Package & K8s Config'));
|
|
121
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export const generateWebSocketCode = async (config, entities, outputDir) => {
|
|
6
|
+
const wsDir = path.join(outputDir, 'ws');
|
|
7
|
+
await fs.ensureDir(wsDir);
|
|
8
|
+
|
|
9
|
+
const wsHandler = `
|
|
10
|
+
package ws
|
|
11
|
+
|
|
12
|
+
import (
|
|
13
|
+
"context"
|
|
14
|
+
"crypto/hmac"
|
|
15
|
+
"crypto/sha256"
|
|
16
|
+
"encoding/hex"
|
|
17
|
+
"encoding/json"
|
|
18
|
+
"fmt"
|
|
19
|
+
"log"
|
|
20
|
+
"net/http"
|
|
21
|
+
"sync"
|
|
22
|
+
"{{app_name}}/controllers"
|
|
23
|
+
|
|
24
|
+
"github.com/gin-gonic/gin"
|
|
25
|
+
"github.com/gorilla/websocket"
|
|
26
|
+
"go.opentelemetry.io/otel"
|
|
27
|
+
"go.opentelemetry.io/otel/propagation"
|
|
28
|
+
"go.opentelemetry.io/otel/trace"
|
|
29
|
+
"gorm.io/gorm"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
var upgrader = websocket.Upgrader{
|
|
33
|
+
CheckOrigin: func(r *http.Request) bool { return true },
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type WSMessage struct {
|
|
37
|
+
Action string \`json:"action"\`
|
|
38
|
+
Payload json.RawMessage \`json:"payload"\`
|
|
39
|
+
Signature string \`json:"signature"\`
|
|
40
|
+
TraceParent string \`json:"traceparent,omitempty"\` // W3C TraceParent
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type WSResponse struct {
|
|
44
|
+
Action string \`json:"action"\`
|
|
45
|
+
Data interface{} \`json:"data"\`
|
|
46
|
+
Error string \`json:"error,omitempty"\`
|
|
47
|
+
TraceParent string \`json:"traceparent,omitempty"\`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// RESToverWSDispatcher handles the mapping from WS actions to Controller logic
|
|
51
|
+
type RESToverWSDispatcher struct {
|
|
52
|
+
DB *gorm.DB
|
|
53
|
+
SecretKey []byte
|
|
54
|
+
Tracer trace.Tracer
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func NewDispatcher(db *gorm.DB) *RESToverWSDispatcher {
|
|
58
|
+
return &RESToverWSDispatcher{
|
|
59
|
+
DB: db,
|
|
60
|
+
SecretKey: []byte("go-duck-super-secret-key"),
|
|
61
|
+
Tracer: otel.Tracer("ws-dispatcher"),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func (d *RESToverWSDispatcher) HandleConnection(c *gin.Context) {
|
|
66
|
+
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
67
|
+
if err != nil {
|
|
68
|
+
log.Printf("WS Upgrade error: %v", err)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
defer conn.Close()
|
|
72
|
+
|
|
73
|
+
for {
|
|
74
|
+
_, message, err := conn.ReadMessage()
|
|
75
|
+
if err != nil {
|
|
76
|
+
break
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var wsMsg WSMessage
|
|
80
|
+
if err := json.Unmarshal(message, &wsMsg); err != nil {
|
|
81
|
+
d.sendError(conn, "Invalid JSON format", wsMsg.Action, "")
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 1. Extract Parent Trace Context
|
|
86
|
+
ctx := context.Background()
|
|
87
|
+
if wsMsg.TraceParent != "" {
|
|
88
|
+
propagator := otel.GetTextMapPropagator()
|
|
89
|
+
ctx = propagator.Extract(ctx, propagation.HeaderCarrier{"traceparent": []string{wsMsg.TraceParent}})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 2. Start WS-Action Span
|
|
93
|
+
childCtx, span := d.Tracer.Start(ctx, fmt.Sprintf("WS Action: %s", wsMsg.Action))
|
|
94
|
+
|
|
95
|
+
// 3. Verify Message Signature (Payload Integrity)
|
|
96
|
+
if !d.verifySignature(wsMsg.Payload, wsMsg.Signature) {
|
|
97
|
+
d.sendError(conn, "Invalid signature: Payload compromised", wsMsg.Action, wsMsg.TraceParent)
|
|
98
|
+
span.End()
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. Dispatch to MVC Controllers
|
|
103
|
+
d.dispatch(childCtx, conn, wsMsg)
|
|
104
|
+
span.End()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func (d *RESToverWSDispatcher) verifySignature(payload []byte, signature string) bool {
|
|
109
|
+
h := hmac.New(sha256.New, d.SecretKey)
|
|
110
|
+
h.Write(payload)
|
|
111
|
+
expectedSignature := hex.EncodeToString(h.Sum(nil))
|
|
112
|
+
return hmac.Equal([]byte(expectedSignature), []byte(signature))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func (d *RESToverWSDispatcher) dispatch(ctx context.Context, conn *websocket.Conn, msg WSMessage) {
|
|
116
|
+
// Inject current span into response header for client to trace back
|
|
117
|
+
carrier := propagation.HeaderCarrier{}
|
|
118
|
+
otel.GetTextMapPropagator().Inject(ctx, carrier)
|
|
119
|
+
tp := carrier.Get("traceparent")
|
|
120
|
+
|
|
121
|
+
switch msg.Action {
|
|
122
|
+
default:
|
|
123
|
+
d.sendError(conn, "Unknown action", msg.Action, tp)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func (d *RESToverWSDispatcher) sendResponse(conn *websocket.Conn, action string, data interface{}, tp string) {
|
|
128
|
+
resp := WSResponse{Action: action, Data: data, TraceParent: tp}
|
|
129
|
+
conn.WriteJSON(resp)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func (d *RESToverWSDispatcher) sendError(conn *websocket.Conn, err string, action string, tp string) {
|
|
133
|
+
resp := WSResponse{Action: action, Error: err, TraceParent: tp}
|
|
134
|
+
conn.WriteJSON(resp)
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const wsContent = wsHandler.replace(/{{app_name}}/g, config.name);
|
|
139
|
+
let finalContent = wsContent;
|
|
140
|
+
let entitiesBlock = "";
|
|
141
|
+
for (const entity of entities) {
|
|
142
|
+
entitiesBlock += `
|
|
143
|
+
case "GET_${entity.name.toUpperCase()}S":
|
|
144
|
+
var list${entity.name} []map[string]interface{}
|
|
145
|
+
// Note: In a production app, we'd use gorm statement timeout and tracing hooks
|
|
146
|
+
d.DB.WithContext(ctx).Table("${entity.name.toLowerCase()}").Find(&list${entity.name})
|
|
147
|
+
d.sendResponse(conn, msg.Action, list${entity.name}, tp)
|
|
148
|
+
case "CREATE_${entity.name.toUpperCase()}":
|
|
149
|
+
d.sendResponse(conn, msg.Action, "${entity.name} creation processing...", tp)
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
finalContent = finalContent.replace("{{#each entities}}", "").replace("{{/each}}", "");
|
|
154
|
+
const startTag = "switch msg.Action {";
|
|
155
|
+
const endTag = "default:";
|
|
156
|
+
const parts = finalContent.split(startTag);
|
|
157
|
+
const endParts = parts[1].split(endTag);
|
|
158
|
+
finalContent = parts[0] + startTag + entitiesBlock + endTag + endParts[1];
|
|
159
|
+
|
|
160
|
+
await fs.writeFile(path.join(wsDir, 'handler.go'), finalContent);
|
|
161
|
+
console.log(chalk.gray(' Generated REST-over-WS Dispatcher with Traced-Envelope'));
|
|
162
|
+
};
|