-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsystem_master.go
More file actions
371 lines (333 loc) · 11.9 KB
/
system_master.go
File metadata and controls
371 lines (333 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"github.com/pepabo/xpoint-cli/internal/xpoint"
"github.com/spf13/cobra"
)
var (
systemMasterListOutput string
systemMasterListJQ string
systemMasterShowOutput string
systemMasterShowJQ string
systemMasterDataType int
systemMasterDataRows int
systemMasterDataOffset int
systemMasterDataFormat string
systemMasterDataFileName string
systemMasterDataDelimiter string
systemMasterDataTitle bool
systemMasterDataNoTitle bool
systemMasterDataFields string
systemMasterDataOutput string
systemMasterDataJQ string
systemMasterImportOverwrite bool
systemMasterImportData string
systemMasterImportJQ string
systemMasterUploadFile string
systemMasterUploadOverwrite bool
)
var systemMasterCmd = &cobra.Command{
Use: "master",
Short: "Manage X-point masters via admin APIs",
}
var systemMasterListCmd = &cobra.Command{
Use: "list",
Short: "List masters (admin)",
Long: "List all masters via GET /api/v1/system/master. Requires an administrator account.",
RunE: runSystemMasterList,
}
var systemMasterShowCmd = &cobra.Command{
Use: "show <master_table_name>",
Short: "Show a user-specific master's property definition",
Long: `Fetch user-specific master property info via
GET /api/v1/system/master/{master_table_name}. Requires an administrator
account.`,
Args: cobra.ExactArgs(1),
RunE: runSystemMasterShow,
}
var systemMasterDataCmd = &cobra.Command{
Use: "data <master_code>",
Short: "Export master data (JSON or CSV)",
Long: `Export master rows via GET /api/v1/system/master/{master_code}/data.
--type (required) selects the master kind:
0 simple master
1 user-specific master (pass the table_name as <master_code>)
--format defaults to json. Use --format csv for CSV output; the CSV
payload is written to stdout (or --output FILE / DIR/).`,
Args: cobra.ExactArgs(1),
RunE: runSystemMasterData,
}
var systemMasterImportCmd = &cobra.Command{
Use: "import <master_code>",
Short: "Import rows into a simple master",
Long: `Import data rows into a simple master via
PUT /api/v1/system/master/{master_code}/data.
--data takes a JSON array of {"code","value"} objects, either inline,
as a file path, or - for stdin.
Pass --overwrite to replace existing data instead of appending.`,
Args: cobra.ExactArgs(1),
RunE: runSystemMasterImport,
}
var systemMasterUploadCmd = &cobra.Command{
Use: "upload <master_table_name>",
Short: "Upload a CSV for a user-specific master's import staging",
Long: `Upload a CSV via POST /multiapi/v1/system/master/{master_table_name}/data.
The upload only stages the file; the import itself is run later from
the admin site's task management (manually or by schedule).`,
Args: cobra.ExactArgs(1),
RunE: runSystemMasterUpload,
}
func init() {
systemCmd.AddCommand(systemMasterCmd)
systemMasterCmd.AddCommand(systemMasterListCmd)
systemMasterCmd.AddCommand(systemMasterShowCmd)
systemMasterCmd.AddCommand(systemMasterDataCmd)
systemMasterCmd.AddCommand(systemMasterImportCmd)
systemMasterCmd.AddCommand(systemMasterUploadCmd)
lf := systemMasterListCmd.Flags()
lf.StringVarP(&systemMasterListOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
lf.StringVar(&systemMasterListJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
sf := systemMasterShowCmd.Flags()
sf.StringVarP(&systemMasterShowOutput, "output", "o", "", "output format: table|json (default: table on TTY, json otherwise)")
sf.StringVar(&systemMasterShowJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
df := systemMasterDataCmd.Flags()
df.IntVar(&systemMasterDataType, "type", -1, "master_type: 0=simple master, 1=user-specific master (required)")
df.IntVar(&systemMasterDataRows, "rows", 0, "number of rows to fetch (0 = omit; server default 100; max 1000)")
df.IntVar(&systemMasterDataOffset, "offset", 0, "offset (0 = omit; server default 0)")
df.StringVar(&systemMasterDataFormat, "format", "json", "output format: json | csv")
df.StringVar(&systemMasterDataFileName, "file-name", "", "CSV file name hint (CSV only; default: {master_code}.csv)")
df.StringVar(&systemMasterDataDelimiter, "delimiter", "", "CSV delimiter: comma | tab (CSV only; default comma)")
df.BoolVar(&systemMasterDataTitle, "title", false, "CSV only (user-specific master): include field names on the first row (default: true)")
df.BoolVar(&systemMasterDataNoTitle, "no-title", false, "CSV only (user-specific master): omit field names from the first row")
df.StringVar(&systemMasterDataFields, "fields", "", "CSV only (simple master): comma-separated list of field names to include")
df.StringVarP(&systemMasterDataOutput, "output", "o", "", "output path: FILE, DIR/, - for stdout (default: stdout for JSON, server-provided filename for CSV)")
df.StringVar(&systemMasterDataJQ, "jq", "", "apply a gojq filter to the JSON response (JSON format only)")
_ = systemMasterDataCmd.MarkFlagRequired("type")
imf := systemMasterImportCmd.Flags()
imf.BoolVar(&systemMasterImportOverwrite, "overwrite", false, "replace existing simple master data instead of appending")
imf.StringVar(&systemMasterImportData, "data", "", "JSON array of {\"code\",\"value\"} rows: inline, file path, or - for stdin (required)")
imf.StringVar(&systemMasterImportJQ, "jq", "", "apply a gojq filter to the JSON response")
_ = systemMasterImportCmd.MarkFlagRequired("data")
uf := systemMasterUploadCmd.Flags()
uf.StringVar(&systemMasterUploadFile, "file", "", "path to the CSV file to upload, or - for stdin (required)")
uf.BoolVar(&systemMasterUploadOverwrite, "overwrite", false, "overwrite the existing staged CSV for this master")
_ = systemMasterUploadCmd.MarkFlagRequired("file")
}
func runSystemMasterList(cmd *cobra.Command, args []string) error {
client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
res, err := client.ListMasters(cmd.Context())
if err != nil {
return err
}
return render(res, resolveOutputFormat(systemMasterListOutput), systemMasterListJQ, func() error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer w.Flush()
fmt.Fprintln(w, "TYPE\tTYPE_NAME\tCODE\tTABLE_NAME\tITEMS\tNAME\tREMARKS")
for _, m := range res.Master {
code := m.Code
if code == "" {
code = "-"
}
tbl := m.TableName
if tbl == "" {
tbl = "-"
}
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d\t%s\t%s\n",
m.Type, m.TypeName, code, tbl, m.ItemCount, m.Name, m.Remarks,
)
}
return nil
})
}
func runSystemMasterShow(cmd *cobra.Command, args []string) error {
tableName := strings.TrimSpace(args[0])
if tableName == "" {
return fmt.Errorf("master_table_name is required")
}
client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
res, err := client.GetUserMasterInfo(cmd.Context(), tableName)
if err != nil {
return err
}
return render(res, resolveOutputFormat(systemMasterShowOutput), systemMasterShowJQ, func() error {
fmt.Fprintf(os.Stdout, "TABLE: %s\n", res.TableName)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer w.Flush()
fmt.Fprintln(w, "ID\tTYPE\tLENGTH\tPK\tINDEX")
for _, f := range res.Fields {
fmt.Fprintf(w, "%s\t%s\t%v\t%t\t%t\n", f.ID, f.Type, f.Length, f.PrimaryKey, f.Index)
}
return nil
})
}
func runSystemMasterData(cmd *cobra.Command, args []string) error {
masterCode := strings.TrimSpace(args[0])
if masterCode == "" {
return fmt.Errorf("master_code is required")
}
if systemMasterDataType != 0 && systemMasterDataType != 1 {
return fmt.Errorf("--type must be 0 (simple) or 1 (user-specific), got %d", systemMasterDataType)
}
format := strings.ToLower(strings.TrimSpace(systemMasterDataFormat))
switch format {
case "", "json":
format = "json"
case "csv":
default:
return fmt.Errorf("unknown --format %q (must be json or csv)", systemMasterDataFormat)
}
if systemMasterDataTitle && systemMasterDataNoTitle {
return fmt.Errorf("--title and --no-title are mutually exclusive")
}
p := xpoint.MasterDataParams{MasterType: systemMasterDataType}
if cmd.Flags().Changed("rows") {
v := systemMasterDataRows
p.Rows = &v
}
if cmd.Flags().Changed("offset") {
v := systemMasterDataOffset
p.Offset = &v
}
if format == "csv" {
p.FileName = systemMasterDataFileName
p.Delimiter = systemMasterDataDelimiter
p.Fields = systemMasterDataFields
if systemMasterDataNoTitle {
b := false
p.Title = &b
} else if cmd.Flags().Changed("title") {
v := systemMasterDataTitle
p.Title = &v
}
}
client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
filename, body, _, err := client.GetMasterData(cmd.Context(), masterCode, format, p)
if err != nil {
return err
}
if format == "json" {
if systemMasterDataJQ != "" {
return runJQ(json.RawMessage(body), systemMasterDataJQ)
}
switch systemMasterDataOutput {
case "", "-":
_, werr := os.Stdout.Write(body)
return werr
}
dst := resolveDownloadPath(systemMasterDataOutput, fallbackName(filename, masterCode+".json"), 0)
if err := os.WriteFile(dst, body, 0o600); err != nil {
return fmt.Errorf("write master data: %w", err)
}
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(body))
return nil
}
// CSV
if systemMasterDataOutput == "-" {
_, werr := os.Stdout.Write(body)
return werr
}
if systemMasterDataOutput == "" && !isTerminal(os.Stdout) {
_, werr := os.Stdout.Write(body)
return werr
}
dst := resolveDownloadPath(systemMasterDataOutput, fallbackName(filename, masterCode+".csv"), 0)
if err := os.WriteFile(dst, body, 0o600); err != nil {
return fmt.Errorf("write csv: %w", err)
}
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(body))
return nil
}
func runSystemMasterImport(cmd *cobra.Command, args []string) error {
masterCode := strings.TrimSpace(args[0])
if masterCode == "" {
return fmt.Errorf("master_code is required")
}
raw, err := loadStringInput(systemMasterImportData)
if err != nil {
return fmt.Errorf("load --data: %w", err)
}
var items []xpoint.SimpleMasterDataItem
if err := json.Unmarshal([]byte(raw), &items); err != nil {
return fmt.Errorf("--data must be a JSON array of {\"code\",\"value\"}: %w", err)
}
req := xpoint.ImportSimpleMasterRequest{Data: items}
if cmd.Flags().Changed("overwrite") {
v := systemMasterImportOverwrite
req.Overwrite = &v
}
client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
out, err := client.ImportSimpleMasterData(cmd.Context(), masterCode, req)
if err != nil {
return err
}
if systemMasterImportJQ != "" {
return runJQ(out, systemMasterImportJQ)
}
return writeJSON(os.Stdout, out)
}
func runSystemMasterUpload(cmd *cobra.Command, args []string) error {
tableName := strings.TrimSpace(args[0])
if tableName == "" {
return fmt.Errorf("master_table_name is required")
}
content, fileName, err := readUploadFile(systemMasterUploadFile)
if err != nil {
return fmt.Errorf("read --file: %w", err)
}
var overwrite *bool
if cmd.Flags().Changed("overwrite") {
v := systemMasterUploadOverwrite
overwrite = &v
}
client, err := newClientFromFlags(cmd.Context())
if err != nil {
return err
}
res, err := client.UploadUserMasterCSV(cmd.Context(), tableName, fileName, content, overwrite)
if err != nil {
return err
}
return writeJSON(os.Stdout, res)
}
// fallbackName returns name when non-empty, else alt.
func fallbackName(name, alt string) string {
if name != "" {
return name
}
return alt
}
// readUploadFile reads the CSV file contents and returns the bytes plus a
// suggested filename for the multipart form-data part. "-" reads from stdin
// and yields a synthetic "upload.csv" filename.
func readUploadFile(path string) ([]byte, string, error) {
if path == "-" {
b, err := io.ReadAll(os.Stdin)
if err != nil {
return nil, "", fmt.Errorf("read stdin: %w", err)
}
return b, "upload.csv", nil
}
b, err := os.ReadFile(path)
if err != nil {
return nil, "", err
}
return b, filepath.Base(path), nil
}