Skip to content

Commit 1fedeeb

Browse files
committed
feat: update record when record is existed
1 parent d8c1e05 commit 1fedeeb

File tree

4 files changed

+198
-8
lines changed

4 files changed

+198
-8
lines changed

cmd/sync/main.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ func Run(lc *leetcode.Leetcode, isNotion bool) {
2121
nc := notion.NewNotion(lc.Config.Notion.Token).
2222
WithConfig("", lc.Config.Notion.DatabaseID)
2323

24+
err := nc.Init()
25+
if err != nil {
26+
log.Fatalf("notion init failed: %v", err)
27+
}
28+
2429
g, _ := errgroup.WithContext(context.TODO())
2530
nThread := 5
2631

@@ -37,7 +42,7 @@ func Run(lc *leetcode.Leetcode, isNotion bool) {
3742
for i := 0; i < nThread; i++ {
3843
g.Go(func() error {
3944
for record := range in {
40-
err := nc.Insert(record)
45+
err := nc.InsertOrUpdate(record)
4146
if err != nil {
4247
return err
4348
}
@@ -72,8 +77,9 @@ func MetaToRecord(e *meta.Meta) *notion.Record {
7277
}
7378

7479
fields := []*notion.Field{
75-
{Type: "title", Name: "ID", Content: e.Index},
76-
{Type: "text", Name: "Name", Content: e.Title},
80+
{Type: "text", Name: "_id", Content: e.ID},
81+
{Type: "text", Name: "ID", Content: e.Index},
82+
{Type: "title", Name: "Name", Content: e.Title},
7783
{Type: "url", Name: "Link", Content: e.Link},
7884
{Type: "select", Name: "Difficulty", Content: e.Difficulty},
7985
{Type: "multi_select", Name: "Tags", Content: e.Tags},

internal/meta/meta.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package meta
22

33
import (
4+
"crypto/sha256"
5+
"encoding/hex"
46
"github.com/bmatcuk/doublestar/v2"
57
"github.com/ppsteven/leetcode-tool/internal/helper"
68
"io/ioutil"
@@ -24,6 +26,7 @@ var (
2426
)
2527

2628
type Meta struct {
29+
ID string
2730
Index string
2831
Title string
2932
Difficulty string
@@ -120,6 +123,7 @@ func findMeta(content []byte, fp string) *Meta {
120123
}
121124

122125
return &Meta{
126+
ID: GetMetaID(fp),
123127
Index: findTag(content, indexRegex),
124128
Title: findTag(content, titleRegex),
125129
Difficulty: findTag(content, difficultyRegex),
@@ -140,3 +144,9 @@ func genCompleted(isCompleted bool, ext string) string {
140144
}
141145
return ext[1:] + " ❌"
142146
}
147+
148+
func GetMetaID(fp string) string {
149+
hash := sha256.New()
150+
hash.Write([]byte(fp))
151+
return hex.EncodeToString(hash.Sum(nil))
152+
}

internal/notion/notion.go

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ package notion
22

33
import (
44
"context"
5+
"fmt"
56
"github.com/jomei/notionapi"
7+
"log"
8+
"strings"
69
)
710

11+
type PageUID string
12+
13+
type SigAndID struct {
14+
Signature string
15+
PageID notionapi.PageID
16+
}
17+
818
type Notion struct {
919
DatabaseID notionapi.DatabaseID
1020
PageID notionapi.PageID
1121
client *notionapi.Client
22+
PageSig map[PageUID]*SigAndID
1223
}
1324

1425
type Field struct {
@@ -69,6 +80,77 @@ func (r *Record) MakeProperties() notionapi.Properties {
6980
return properties
7081
}
7182

83+
type PageData struct {
84+
PageID notionapi.PageID
85+
Data map[string]string
86+
}
87+
88+
func ParsePage(page *notionapi.Page) (PageUID, *PageData) {
89+
var pageUID PageUID
90+
pageID := GetPageID(page.ID)
91+
data := make(map[string]string)
92+
for name, property := range page.Properties {
93+
data[name] = ParseProperty(property)
94+
}
95+
if v, ok := data["_id"]; ok {
96+
pageUID = PageUID(v)
97+
} else {
98+
log.Fatalf("_id not found: %v", data)
99+
}
100+
return pageUID, &PageData{PageID: notionapi.PageID(pageID), Data: data}
101+
}
102+
103+
func ParseProperty(property notionapi.Property) string {
104+
switch property.GetType() {
105+
case "title":
106+
p, ok := property.(*notionapi.TitleProperty)
107+
if !ok {
108+
log.Fatalf("title parsed failed: %v", property)
109+
}
110+
if len(p.Title) == 0 {
111+
return ""
112+
}
113+
return p.Title[0].Text.Content
114+
case "rich_text":
115+
p, ok := property.(*notionapi.RichTextProperty)
116+
if !ok {
117+
log.Fatalf("rich_text parsed failed: %v", property)
118+
}
119+
if len(p.RichText) == 0 {
120+
return ""
121+
}
122+
return p.RichText[0].Text.Content
123+
case "url":
124+
p, ok := property.(*notionapi.URLProperty)
125+
if !ok {
126+
log.Fatalf("url parsed failed: %v", property)
127+
}
128+
return p.URL
129+
case "select":
130+
p, ok := property.(*notionapi.SelectProperty)
131+
if !ok {
132+
log.Fatalf("select parsed failed: %v", property)
133+
}
134+
return p.Select.Name
135+
case "multi_select":
136+
p, ok := property.(*notionapi.MultiSelectProperty)
137+
if !ok {
138+
log.Fatalf("multi_select parsed failed: %v", property)
139+
}
140+
optionStr := ""
141+
for _, option := range p.MultiSelect {
142+
optionStr += option.Name + ","
143+
}
144+
if len(optionStr) > 0 {
145+
optionStr = optionStr[:len(optionStr)-1]
146+
}
147+
return optionStr
148+
default:
149+
log.Fatalf("%s not supported current", property.GetType())
150+
}
151+
return ""
152+
}
153+
72154
func NewNotion(token string) *Notion {
73155
client := notionapi.NewClient(notionapi.Token(token))
74156
return &Notion{
@@ -77,11 +159,25 @@ func NewNotion(token string) *Notion {
77159
}
78160

79161
func (n *Notion) WithConfig(pageID, databaseID string) *Notion {
80-
n.DatabaseID = notionapi.DatabaseID(databaseID)
81162
n.PageID = notionapi.PageID(pageID)
163+
n.DatabaseID = notionapi.DatabaseID(databaseID)
82164
return n
83165
}
84166

167+
func (n *Notion) Init() error {
168+
if n.DatabaseID != "" {
169+
records, err := n.Query()
170+
if err != nil {
171+
return fmt.Errorf("query failed: %v", err)
172+
}
173+
n.PageSig = make(map[PageUID]*SigAndID)
174+
for pageUID, pageData := range records {
175+
n.PageSig[pageUID] = &SigAndID{PageID: pageData.PageID, Signature: GetPageSig(pageData.Data)}
176+
}
177+
}
178+
return nil
179+
}
180+
85181
func (n *Notion) Insert(record *Record) error {
86182
ctx := context.Background()
87183

@@ -95,3 +191,56 @@ func (n *Notion) Insert(record *Record) error {
95191
_, err := n.client.Page.Create(ctx, pageReq)
96192
return err
97193
}
194+
195+
func (n *Notion) Update(pageID notionapi.PageID, record *Record) error {
196+
ctx := context.Background()
197+
198+
updateReq := &notionapi.PageUpdateRequest{
199+
Properties: record.MakeProperties(),
200+
}
201+
_, err := n.client.Page.Update(ctx, pageID, updateReq)
202+
203+
return err
204+
}
205+
206+
func (n *Notion) Query() (records map[PageUID]*PageData, err error) {
207+
ctx := context.Background()
208+
209+
dbQueryReq := &notionapi.DatabaseQueryRequest{}
210+
211+
queryResp, err := n.client.Database.Query(ctx, n.DatabaseID, dbQueryReq)
212+
213+
if err != nil {
214+
return nil, err
215+
}
216+
217+
records = make(map[PageUID]*PageData)
218+
for _, result := range queryResp.Results {
219+
pageUID, pageData := ParsePage(&result)
220+
records[pageUID] = pageData
221+
}
222+
return records, nil
223+
}
224+
225+
func (n *Notion) InsertOrUpdate(record *Record) error {
226+
pageUID, pageData := ParsePage(&notionapi.Page{Properties: record.MakeProperties()})
227+
228+
if pageSig, ok := n.PageSig[pageUID]; ok {
229+
// 签名不一致,更新行信息
230+
if pageSig.Signature != GetPageSig(pageData.Data) {
231+
return n.Update(n.PageSig[pageUID].PageID, record)
232+
} else {
233+
return nil
234+
}
235+
}
236+
237+
return n.Insert(record)
238+
}
239+
240+
func GetPageSig(v map[string]string) string {
241+
return fmt.Sprintf("%s_%s_%s_%s_%s_%s", v["Difficulty"], v["ID"], v["Link"], v["Name"], v["Solved"], v["Tags"])
242+
}
243+
244+
func GetPageID(objectID notionapi.ObjectID) string {
245+
return strings.ReplaceAll(objectID.String(), "-", "")
246+
}

internal/notion/notion_test.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88

99
func TestNotion(t *testing.T) {
1010
notion := NewNotion("secret_xxxxxx")
11-
notion.WithConfig("", "xxxx")
11+
notion.WithConfig("xxx", "xxxx")
1212

1313
m := &meta.Meta{
14+
"465b111ca3e74113a0fc382aa1d3dfa6",
1415
"1",
1516
"Binary Search",
1617
"Medium",
@@ -24,10 +25,33 @@ func TestNotion(t *testing.T) {
2425
""}
2526
record := MetaToRecord(m)
2627

27-
t.Run("", func(t *testing.T) {
28+
t.Run("Insert", func(t *testing.T) {
2829
err := notion.Insert(record)
2930
assert.NoError(t, err)
3031
})
32+
33+
t.Run("Query", func(t *testing.T) {
34+
ret, err := notion.Query()
35+
assert.NoError(t, err)
36+
t.Log(ret)
37+
})
38+
39+
t.Run("Init", func(t *testing.T) {
40+
err := notion.Init()
41+
assert.NoError(t, err)
42+
})
43+
44+
t.Run("Update", func(t *testing.T) {
45+
err := notion.Update("465b111ca3e74113a0fc382aa1d3dfa6", MetaToRecord(m))
46+
assert.NoError(t, err)
47+
})
48+
t.Run("InsertOrUpdate", func(t *testing.T) {
49+
_ = notion.Init()
50+
51+
m.Title = "test"
52+
err := notion.InsertOrUpdate(MetaToRecord(m))
53+
assert.NoError(t, err)
54+
})
3155
}
3256

3357
func MetaToRecord(e *meta.Meta) *Record {
@@ -39,8 +63,9 @@ func MetaToRecord(e *meta.Meta) *Record {
3963
}
4064

4165
fields := []*Field{
42-
{Type: "title", Name: "ID", Content: e.Index},
43-
{Type: "text", Name: "Name", Content: e.Title},
66+
{Type: "text", Name: "_id", Content: e.ID},
67+
{Type: "text", Name: "ID", Content: e.Index},
68+
{Type: "title", Name: "Name", Content: e.Title},
4469
{Type: "url", Name: "Link", Content: e.Link},
4570
{Type: "select", Name: "Difficulty", Content: e.Difficulty},
4671
{Type: "multi_select", Name: "Tags", Content: e.Tags},

0 commit comments

Comments
 (0)