Skip to content

Commit b435895

Browse files
committed
feat json api
1 parent 687f69e commit b435895

File tree

6 files changed

+239
-0
lines changed

6 files changed

+239
-0
lines changed

cmd/fti/main.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,69 @@
11
package main
22

3+
import (
4+
"context"
5+
"flag"
6+
"log"
7+
"path/filepath"
8+
"time"
9+
10+
"cloud.google.com/go/firestore"
11+
_ "github.com/BurntSushi/toml"
12+
"github.com/go-generalize/fti/pkg/config"
13+
"github.com/go-generalize/fti/pkg/files"
14+
"github.com/go-generalize/fti/pkg/inserter"
15+
"github.com/heetch/confita"
16+
"github.com/heetch/confita/backend/env"
17+
"github.com/heetch/confita/backend/file"
18+
"github.com/heetch/confita/backend/flags"
19+
"golang.org/x/xerrors"
20+
_ "gopkg.in/yaml.v2"
21+
)
22+
23+
var (
24+
configPath = flag.String("c", "config.yaml", "-c config.yaml")
25+
)
26+
327
func main() {
28+
flag.Parse()
29+
30+
if !files.Exists(*configPath) {
31+
configFullPath, _ := filepath.Abs(*configPath)
32+
log.Fatalf("not found configuration file: %s", configFullPath)
33+
}
34+
35+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
36+
defer cancel()
37+
38+
cfg := config.Config{}
39+
loader := confita.NewLoader(
40+
env.NewBackend(),
41+
file.NewBackend(*configPath),
42+
flags.NewBackend(),
43+
)
44+
err := loader.Load(ctx, &cfg)
45+
if err != nil {
46+
log.Fatalf("cannot load configuration: %+v", err)
47+
}
48+
49+
client, err := initFirestore(ctx, &cfg)
50+
if err != nil {
51+
log.Fatalf("failed to init firestore: %+v", err)
52+
}
53+
54+
is := inserter.NewInserter(client)
55+
err = is.Execute(context.Background(), &cfg)
56+
if err != nil {
57+
log.Fatalf("failed to execute insert: %+v", err)
58+
}
59+
}
60+
61+
func initFirestore(ctx context.Context, cfg *config.Config) (*firestore.Client, error) {
62+
projectID := config.GetProjectID(cfg)
63+
client, err := firestore.NewClient(ctx, projectID)
64+
if err != nil {
65+
return nil, xerrors.Errorf("failed to initialize firestore client: %w", err)
66+
}
467

68+
return client, nil
569
}

pkg/config/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package config
2+
3+
type Config struct {
4+
Targets []string `config:"targets,required" yaml:"targets"`
5+
FirestoreProjectOnEmulator string `config:"firestore_project_on_emulator,required" yaml:"firestore_project_on_emulator"`
6+
FirestoreEmulatorHost string `config:"firestore_emulator_host,required" yaml:"firestore_emulator_host"`
7+
}

pkg/config/project.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package config
2+
3+
import "os"
4+
5+
func GetProjectID(cfg *Config) string {
6+
if cfg.FirestoreProjectOnEmulator != "" {
7+
_ = os.Setenv("FIRESTORE_EMULATOR_HOST", cfg.FirestoreEmulatorHost)
8+
return cfg.FirestoreProjectOnEmulator
9+
}
10+
11+
id, ok := os.LookupEnv("GCP_PROJECT")
12+
if ok {
13+
return id
14+
}
15+
16+
id, ok = os.LookupEnv("GOOGLE_CLOUD_PROJECT")
17+
if ok {
18+
return id
19+
}
20+
21+
return ""
22+
}

pkg/files/exists.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package files
2+
3+
import "os"
4+
5+
func Exists(filename string) bool {
6+
_, err := os.Stat(filename)
7+
return err == nil
8+
}

pkg/inserter/inserter.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package inserter
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
12+
"cloud.google.com/go/firestore"
13+
"github.com/go-generalize/fti/pkg/config"
14+
"github.com/go-generalize/fti/pkg/files"
15+
"golang.org/x/xerrors"
16+
)
17+
18+
type Inserter struct {
19+
client *firestore.Client
20+
refIDs map[string]string
21+
}
22+
23+
func NewInserter(client *firestore.Client) *Inserter {
24+
return &Inserter{
25+
client: client,
26+
}
27+
}
28+
29+
func (i *Inserter) Execute(ctx context.Context, cfg *config.Config) error {
30+
targetDir := cfg.Targets
31+
for _, t := range targetDir {
32+
if !files.Exists(t) {
33+
return xerrors.Errorf("cannot find target directory: %s", targetDir)
34+
}
35+
36+
err := filepath.Walk(t, i.executeFile(ctx))
37+
if err != nil {
38+
return xerrors.Errorf("failed to execute file: %w", err)
39+
}
40+
}
41+
42+
return nil
43+
}
44+
45+
func (i *Inserter) executeFile(ctx context.Context) func(path string, info os.FileInfo, _ error) error {
46+
return func(path string, info os.FileInfo, _ error) error {
47+
if info.IsDir() {
48+
return nil
49+
}
50+
51+
cn := filepath.Base(filepath.Dir(path))
52+
53+
var err error
54+
switch {
55+
case strings.HasSuffix(path, ".json"):
56+
err = i.executeJSON(ctx, cn, path)
57+
if err != nil {
58+
log.Printf("failed to insert json file: %s\n%+v", path, err)
59+
}
60+
case strings.HasSuffix(path, ".js"):
61+
// TODO: implements here for js api
62+
}
63+
64+
return nil
65+
}
66+
}
67+
68+
func (i *Inserter) executeJSON(ctx context.Context, cn, path string) error {
69+
jb, err := os.ReadFile(path)
70+
if err != nil {
71+
return xerrors.Errorf("failed to read json file: %+v", err)
72+
}
73+
74+
jm := new(JsonModel)
75+
err = json.Unmarshal(jb, jm)
76+
if err != nil {
77+
return xerrors.Errorf("failed to unmarshal json: %w", err)
78+
}
79+
80+
if payload, ok := jm.Payload.([]interface{}); ok {
81+
for idx, p := range payload {
82+
mp, ok := p.(map[string]interface{})
83+
if !ok {
84+
continue
85+
}
86+
err := i.createItem(ctx, cn, mp)
87+
if err != nil {
88+
return xerrors.Errorf("failed to create item in array (index=%d): %w", idx, err)
89+
}
90+
}
91+
} else if payload, ok := jm.Payload.(map[string]interface{}); ok {
92+
err := i.createItem(ctx, cn, payload)
93+
if err != nil {
94+
return xerrors.Errorf("failed to create item: %w", err)
95+
}
96+
} else {
97+
// print log or error?
98+
}
99+
100+
return nil
101+
}
102+
103+
func (i *Inserter) createItem(ctx context.Context, cn string, item map[string]interface{}) error {
104+
item = i.tryParseDate(item)
105+
106+
log.Printf("collection: %s, item: %+v", cn, item)
107+
_, err := i.client.Collection(cn).NewDoc().Create(ctx, item)
108+
if err != nil {
109+
return xerrors.Errorf("failed to create item: %w", err)
110+
}
111+
return nil
112+
}
113+
114+
func (i *Inserter) tryParseDate(item map[string]interface{}) map[string]interface{} {
115+
for k, v := range item {
116+
switch vt := v.(type) {
117+
case string:
118+
pt, err := time.Parse(time.RFC3339, vt)
119+
if err != nil {
120+
// print log?
121+
continue
122+
}
123+
item[k] = pt
124+
125+
case map[string]interface{}:
126+
item[k] = i.tryParseDate(vt)
127+
}
128+
}
129+
130+
return item
131+
}

pkg/inserter/json_model.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package inserter
2+
3+
type JsonModel struct {
4+
Version string `json:"version"`
5+
Ref string `json:"ref"`
6+
Payload interface{} `json:"payload"`
7+
}

0 commit comments

Comments
 (0)