Skip to content

Commit 740b31d

Browse files
committed
Nested Set Builder
0 parents  commit 740b31d

File tree

9 files changed

+578
-0
lines changed

9 files changed

+578
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
examples/dev_config.yml
2+
examples/ref.md

examples/config.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
db:
2+
adapter: "mysql" # this is can be set to other db adapter like mysql/postgres etc
3+
host: "[DB_HOST]"
4+
port: 3306
5+
name: "[DB_NAME]"
6+
user: "[DB_USER]"
7+
password: "[DB_PASSWORD]"

examples/config/config.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package config
2+
3+
import (
4+
"os"
5+
6+
"github.com/spf13/viper"
7+
"gitlab.com/sulthonzh/scraperpath-nested-set/examples/utils"
8+
)
9+
10+
// Configuration ::
11+
type Configuration struct {
12+
DB struct {
13+
Adapter string `json:"adapter"`
14+
Name string `json:"name"`
15+
Host string `json:"host"`
16+
Port string `json:"port"`
17+
Password string `json:"password"`
18+
User string `json:"user"`
19+
} `json:"db"`
20+
}
21+
22+
// Config ::
23+
var Config = Configuration{}
24+
25+
func init() {
26+
switch true {
27+
case os.Getenv("ENV") == "PRODUCTION":
28+
viper.SetConfigName("config") // Production Config
29+
case os.Getenv("ENV") == "LOCAL.DEV":
30+
viper.SetConfigName("local_dev_config") // Local Development Config
31+
default:
32+
viper.SetConfigName("dev_config") // Dev Config
33+
}
34+
35+
viper.AddConfigPath("/etc/nestedset/api")
36+
viper.AddConfigPath("$HOME/.nestedset/api")
37+
viper.AddConfigPath(".")
38+
39+
viper.SetConfigType("yaml")
40+
41+
viper.AutomaticEnv()
42+
43+
err := viper.ReadInConfig()
44+
if err != nil {
45+
utils.ExitOnFailure(err)
46+
}
47+
48+
err = viper.Unmarshal(&Config)
49+
if err != nil {
50+
utils.ExitOnFailure(err)
51+
}
52+
}

examples/db/db.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package db
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
8+
"gitlab.com/sulthonzh/scraperpath-nested-set/examples/config"
9+
10+
"github.com/jinzhu/gorm"
11+
// spesial import ::
12+
_ "github.com/jinzhu/gorm/dialects/mysql"
13+
// _ "github.com/jinzhu/gorm/dialects/postgres"
14+
// _ "github.com/jinzhu/gorm/dialects/sqlite"
15+
)
16+
17+
// InitDB ::
18+
func InitDB() (db *gorm.DB, err error) {
19+
dbConfig := config.Config.DB
20+
if dbConfig.Adapter == "mysql" {
21+
db, err = gorm.Open("mysql", fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?parseTime=True&loc=Local", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Name))
22+
// DB = DB.Set("gorm:table_options", "CHARSET=utf8")
23+
} else if dbConfig.Adapter == "postgres" {
24+
db, err = gorm.Open("postgres", fmt.Sprintf("postgres://%v:%v@%v/%v?sslmode=disable", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Name))
25+
} else if dbConfig.Adapter == "sqlite" {
26+
db, err = gorm.Open("sqlite3", fmt.Sprintf("%v/%v", os.TempDir(), dbConfig.Name))
27+
} else {
28+
err = errors.New("not supported database adapter")
29+
}
30+
31+
return
32+
}

examples/db/migrate/main.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
nestedset "gitlab.com/sulthonzh/scraperpath-nested-set"
7+
"gitlab.com/sulthonzh/scraperpath-nested-set/examples/db"
8+
"gitlab.com/sulthonzh/scraperpath-nested-set/examples/utils"
9+
)
10+
11+
func main() {
12+
migrate()
13+
}
14+
15+
func migrate() {
16+
db, err := db.InitDB()
17+
if err != nil {
18+
utils.ExitOnFailure(err)
19+
}
20+
21+
categories := nestedset.Node{}
22+
categories.SetTableName("scraper_paths")
23+
24+
fmt.Println(categories.TableName())
25+
err = db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(
26+
&categories,
27+
).Error
28+
if err != nil {
29+
utils.ExitOnFailure(err)
30+
}
31+
32+
err = db.Exec(`
33+
INSERT INTO scraper_paths (id,type,name,lft,rgt,data) VALUES
34+
('5439f1ff-cb6a-4793-acbf-4d80ede90187',1,'description',5,6,'{"field": "---", "target": "desc"}'),
35+
('628fbfb9-87b3-4d29-bda2-50671001fd51',1,'name',3,4,'{"field": "---", "target": "name"}'),
36+
('7f9960e8-1f9a-4ee3-a204-9e801535d96a',1,'title',10,11,NULL),
37+
('806dd400-0d44-4854-8448-34e9d3f35266',1,'listings',1,12,'{"field": "---", "target": "listings"}'),
38+
('8d5fceba-2ee3-46c3-8eaf-f06cdb4fd68e',1,'price',7,8,'{"field": "---", "target": "price"}'),
39+
('fddfd29f-c19f-4112-b556-29ca9d132140',1,'url',2,9,'{"field": "---", "target": "url"}');
40+
`).Error
41+
if err != nil {
42+
utils.ExitOnFailure(err)
43+
}
44+
fmt.Println("migrate: completed")
45+
}

examples/main.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
nestedset "gitlab.com/sulthonzh/scraperpath-nested-set"
8+
"gitlab.com/sulthonzh/scraperpath-nested-set/examples/db"
9+
"gitlab.com/sulthonzh/scraperpath-nested-set/examples/utils"
10+
)
11+
12+
func main() {
13+
db, err := db.InitDB()
14+
if err != nil {
15+
utils.ExitOnFailure(err)
16+
}
17+
18+
n := nestedset.NewNodeStorageCustomTableName(db, "scraper_paths")
19+
// root := nestedset.Node{
20+
// ID: uuid.Must(uuid.NewV4()),
21+
// Type: 1,
22+
// Name: "listings",
23+
// Left: 1,
24+
// Right: 2,
25+
// }
26+
parent, err := n.GetNodeByValue(1, "listings")
27+
// n.Plant(&root)
28+
// node.GetAllNodes(1)
29+
if err != nil {
30+
utils.ExitOnFailure(err)
31+
}
32+
33+
// n.InsertChild(parent, &nestedset.Node{
34+
// ID: uuid.Must(uuid.NewV4()),
35+
// Type: 1,
36+
// Name: "price",
37+
// })
38+
39+
nodes, err := n.GetAllNodes(parent)
40+
if err != nil {
41+
utils.ExitOnFailure(err)
42+
}
43+
child := struct {
44+
Target interface{} `json:"target"`
45+
Field string `json:"field"`
46+
Info string `json:"info"`
47+
}{}
48+
49+
treeNodes := nodes.GenerateTree(child)
50+
b, _ := json.Marshal(treeNodes)
51+
fmt.Println(string(b))
52+
53+
err = json.Unmarshal(b, &nodes)
54+
if err != nil {
55+
utils.ExitOnFailure(err)
56+
}
57+
58+
}

examples/utils/utils.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
// ExitOnFailure prints a fatal error message and exits the process with status 1.
9+
func ExitOnFailure(err error) {
10+
if err == nil {
11+
return
12+
}
13+
fmt.Fprintf(os.Stderr, "[CRIT] %s. ", err.Error())
14+
os.Exit(1)
15+
}

nestedset.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package nestedset
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/satori/go.uuid"
7+
)
8+
9+
type (
10+
// Node represents a node of nested set tree
11+
Node struct {
12+
_tableName string `sql:"-"`
13+
ID uuid.UUID `sql:"type:varbinary(36);NOT NULL" gorm:"primary_key;column:id" json:"-"` //
14+
Left int32 `sql:"type:int;NOT NULL" gorm:"column:lft" json:"-"` //
15+
Right int32 `sql:"type:int;NOT NULL" gorm:"column:rgt" json:"-"` //
16+
Name string `sql:"type:varchar(50);NOT NULL" gorm:"column:name" json:"name,omitempty"` //
17+
Type int32 `sql:"type:int;NOT NULL" gorm:"column:type" json:"type,omitempty"` //
18+
JSONData string `sql:"type:JSON;default:NULL" gorm:"column:data" json:"-"` //
19+
Data interface{} `sql:"-" json:"data,omitempty"` //
20+
Depth int32 `sql:"-" json:"depth,omitempty"` //
21+
Children NodeCollection `sql:"-" json:"children,omitempty"` //
22+
23+
}
24+
// NodeCollection ::
25+
NodeCollection []*Node
26+
)
27+
28+
// NeWNode returns pointer to newly initialized Node
29+
func NeWNode(left, right int32, name string) *Node {
30+
n := &Node{
31+
ID: uuid.Must(uuid.NewV4()),
32+
Left: left,
33+
Right: right,
34+
Name: name,
35+
}
36+
return n
37+
}
38+
39+
// TableName ::
40+
func (n *Node) TableName() string {
41+
if n._tableName != "" {
42+
return n._tableName
43+
}
44+
return "nodes"
45+
}
46+
47+
// SetTableName ::
48+
func (n *Node) SetTableName(name string) {
49+
n._tableName = name
50+
}
51+
52+
// GenerateTree ::
53+
func (nodes NodeCollection) GenerateTree(childStruct interface{}) NodeCollection {
54+
query := NodeCollection{}
55+
for _, n := range nodes {
56+
if n.Depth == 0 {
57+
query = append(query, n)
58+
}
59+
}
60+
return nodes.runQueryRecursive(query, childStruct)
61+
}
62+
63+
func (nodes NodeCollection) runQueryRecursive(query NodeCollection, v interface{}) (result NodeCollection) {
64+
for _, q := range query {
65+
var vTemp interface{}
66+
if q.JSONData != "" {
67+
vTemp = v
68+
b := []byte(q.JSONData)
69+
json.Unmarshal(b, &vTemp)
70+
}
71+
result = append(result, &Node{
72+
ID: q.ID,
73+
Name: q.Name,
74+
Data: vTemp,
75+
Children: nodes.getChildrens(q, v),
76+
})
77+
}
78+
return result
79+
}
80+
81+
func (nodes NodeCollection) getChildrens(parent *Node, v interface{}) NodeCollection {
82+
query := NodeCollection{}
83+
for _, n := range nodes {
84+
if n.Depth == parent.Depth+1 && n.Left > parent.Left && n.Right < parent.Right {
85+
query = append(query, n)
86+
}
87+
}
88+
return nodes.runQueryRecursive(query, v)
89+
}

0 commit comments

Comments
 (0)