diff --git a/api/doc.go b/api/doc.go index bcc5468..705f7a3 100644 --- a/api/doc.go +++ b/api/doc.go @@ -69,7 +69,43 @@ func (s *HttpServer) update() gin.HandlerFunc { } } -func (s *HttpServer) search() gin.HandlerFunc { +func (s *HttpServer) get() gin.HandlerFunc { + return func(c *gin.Context) { + namespace := c.Param("namespace") + entryId := c.Param("entryId") + document, err := s.chain.GetDocument(c, namespace, entryId) + if err != nil { + c.String(500, fmt.Sprintf("get document error: %s", err)) + return + } + docWithAttr := &DocumentWithAttr{ + Document: document, + } + + attrs, err := s.chain.GetDocumentAttrs(c, namespace, entryId) + if err != nil { + c.String(500, fmt.Sprintf("get document attrs error: %s", err)) + return + } + for _, attr := range attrs { + if attr.Key == "parentId" { + docWithAttr.ParentID = attr.Value.(string) + } + if attr.Key == "mark" { + marked := attr.Value.(bool) + docWithAttr.Mark = &marked + } + if attr.Key == "unRead" { + unRead := attr.Value.(bool) + docWithAttr.UnRead = &unRead + } + } + + c.JSON(200, attrs) + } +} + +func (s *HttpServer) filter() gin.HandlerFunc { return func(c *gin.Context) { namespace := c.Param("namespace") page, err := strconv.Atoi(c.DefaultQuery("page", "0")) @@ -114,7 +150,45 @@ func (s *HttpServer) search() gin.HandlerFunc { c.String(500, fmt.Sprintf("search document error: %s", err)) return } - c.JSON(200, docs) + ids := []string{} + for _, doc := range docs { + ids = append(ids, doc.EntryId) + } + + allAttrs, err := s.chain.ListDocumentAttrs(c, namespace, ids) + if err != nil { + c.String(500, fmt.Sprintf("list document attrs error: %s", err)) + return + } + attrsMap := map[string][]doc.DocumentAttr{} + for _, attr := range allAttrs { + if attrsMap[attr.EntryId] == nil { + attrsMap[attr.EntryId] = []doc.DocumentAttr{} + } + attrsMap[attr.EntryId] = append(attrsMap[attr.EntryId], attr) + } + + var docWithAttrs []DocumentWithAttr + for _, document := range docs { + docWithAttr := DocumentWithAttr{Document: document} + attrs := attrsMap[document.EntryId] + for _, attr := range attrs { + if attr.Key == "parentId" { + docWithAttr.ParentID = attr.Value.(string) + } + if attr.Key == "mark" { + marked := attr.Value.(bool) + docWithAttr.Mark = &marked + } + if attr.Key == "unRead" { + unRead := attr.Value.(bool) + docWithAttr.UnRead = &unRead + } + } + docWithAttrs = append(docWithAttrs, docWithAttr) + } + + c.JSON(200, docWithAttrs) } } diff --git a/api/request.go b/api/request.go index 8ea756c..53bb035 100644 --- a/api/request.go +++ b/api/request.go @@ -17,22 +17,20 @@ package api import ( - "time" - "github.com/google/uuid" "github.com/basenana/friday/pkg/models/doc" ) type DocRequest struct { - EntryId string `json:"entryId,omitempty"` - Name string `json:"name"` - Namespace string `json:"namespace"` - Source string `json:"source,omitempty"` - WebUrl string `json:"webUrl,omitempty"` - Content string `json:"content"` - CreatedAt time.Time `json:"createdAt,omitempty"` - ChangedAt time.Time `json:"changedAt,omitempty"` + EntryId string `json:"entryId,omitempty"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Source string `json:"source,omitempty"` + WebUrl string `json:"webUrl,omitempty"` + Content string `json:"content"` + CreatedAt int64 `json:"createdAt,omitempty"` + ChangedAt int64 `json:"changedAt,omitempty"` } func (r *DocRequest) ToDocument() *doc.Document { @@ -91,13 +89,18 @@ func (r *DocAttrRequest) ToDocAttr() []*doc.DocumentAttr { } type DocQuery struct { - IDs []string `json:"ids"` - Namespace string `json:"namespace"` - Source string `json:"source,omitempty"` - WebUrl string `json:"webUrl,omitempty"` - ParentID string `json:"parentId,omitempty"` - UnRead *bool `json:"unRead,omitempty"` - Mark *bool `json:"mark,omitempty"` + IDs []string `json:"ids"` + Namespace string `json:"namespace"` + Source string `json:"source,omitempty"` + WebUrl string `json:"webUrl,omitempty"` + ParentID string `json:"parentId,omitempty"` + UnRead *bool `json:"unRead,omitempty"` + Mark *bool `json:"mark,omitempty"` + CreatedAtStart *int64 `json:"createdAtStart,omitempty"` + CreatedAtEnd *int64 `json:"createdAtEnd,omitempty"` + ChangedAtStart *int64 `json:"changedAtStart,omitempty"` + ChangedAtEnd *int64 `json:"changedAtEnd,omitempty"` + FuzzyName *string `json:"fuzzyName,omitempty"` Search string `json:"search"` @@ -137,6 +140,41 @@ func (q *DocQuery) ToQuery() *doc.DocumentQuery { Value: q.WebUrl, }) } + if q.CreatedAtStart != nil { + attrQueries = append(attrQueries, doc.AttrQuery{ + Attr: "createdAt", + Option: ">=", + Value: *q.CreatedAtStart, + }) + } + if q.ChangedAtStart != nil { + attrQueries = append(attrQueries, doc.AttrQuery{ + Attr: "updatedAt", + Option: ">=", + Value: *q.ChangedAtStart, + }) + } + if q.CreatedAtEnd != nil { + attrQueries = append(attrQueries, doc.AttrQuery{ + Attr: "createdAt", + Option: "<=", + Value: *q.CreatedAtEnd, + }) + } + if q.ChangedAtEnd != nil { + attrQueries = append(attrQueries, doc.AttrQuery{ + Attr: "updatedAt", + Option: "<=", + Value: *q.ChangedAtEnd, + }) + } + if q.FuzzyName != nil { + attrQueries = append(attrQueries, doc.AttrQuery{ + Attr: "name", + Option: "CONTAINS", + Value: *q.FuzzyName, + }) + } query.AttrQueries = attrQueries return query @@ -209,3 +247,11 @@ func (q *DocQuery) GetAttrQueries() []*doc.DocumentAttrQuery { } return attrQueries } + +type DocumentWithAttr struct { + doc.Document + + ParentID string `json:"parentId,omitempty"` + UnRead *bool `json:"unRead,omitempty"` + Mark *bool `json:"mark,omitempty"` +} diff --git a/api/server.go b/api/server.go index 613a17c..b6e9f2e 100644 --- a/api/server.go +++ b/api/server.go @@ -87,5 +87,6 @@ func (s *HttpServer) handle(group *gin.RouterGroup) { docGroup.POST("/entry/:entryId", s.store()) docGroup.DELETE("/entry/:entryId", s.delete()) docGroup.PUT("/entry/:entryId", s.update()) - docGroup.GET("/search", s.search()) + docGroup.GET("/entry/:entryId", s.get()) + docGroup.GET("/filter", s.filter()) } diff --git a/pkg/models/doc/document.go b/pkg/models/doc/document.go index 6022bcb..e56d82f 100644 --- a/pkg/models/doc/document.go +++ b/pkg/models/doc/document.go @@ -19,14 +19,13 @@ package doc import ( "encoding/json" "fmt" - "time" "github.com/meilisearch/meilisearch-go" ) var ( - DocFilterableAttrs = []string{"namespace", "id", "entryId", "name", "source", "webUrl", "createdAt", "updatedAt"} - DocAttrFilterableAttrs = []string{"namespace", "entryId", "key", "id", "value"} + DocFilterableAttrs = []string{"namespace", "id", "entryId", "kind", "name", "source", "webUrl", "createdAt", "updatedAt"} + DocAttrFilterableAttrs = []string{"namespace", "entryId", "key", "id", "kind", "value"} DocSortAttrs = []string{"createdAt", "updatedAt"} ) @@ -38,6 +37,7 @@ type DocPtrInterface interface { type Document struct { Id string `json:"id"` + Kind string `json:"kind"` Namespace string `json:"namespace"` EntryId string `json:"entryId"` Name string `json:"name"` @@ -49,8 +49,8 @@ type Document struct { HeaderImage string `json:"headerImage,omitempty"` SubContent string `json:"subContent,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty"` - UpdatedAt time.Time `json:"updatedAt,omitempty"` + CreatedAt int64 `json:"createdAt,omitempty"` + UpdatedAt int64 `json:"updatedAt,omitempty"` } func (d *Document) ID() string { @@ -67,6 +67,7 @@ func (d *Document) Type() string { type DocumentAttr struct { Id string `json:"id"` + Kind string `json:"kind"` Namespace string `json:"namespace"` EntryId string `json:"entryId"` Key string `json:"key"` diff --git a/pkg/service/chain.go b/pkg/service/chain.go index ec7b6f5..a12f385 100644 --- a/pkg/service/chain.go +++ b/pkg/service/chain.go @@ -85,9 +85,95 @@ func (c *Chain) StoreAttr(ctx context.Context, docAttr *doc.DocumentAttr) error }) } +func (c *Chain) GetDocument(ctx context.Context, namespace, entryId string) (doc.Document, error) { + docs, err := c.MeiliClient.Search(ctx, &doc.DocumentQuery{ + AttrQueries: []doc.AttrQuery{ + { + Attr: "namespace", + Option: "=", + Value: namespace, + }, + { + Attr: "entryId", + Option: "=", + Value: entryId, + }, + { + Attr: "kind", + Option: "=", + Value: "document", + }, + }, + }) + if err != nil { + return doc.Document{}, err + } + if len(docs) == 0 { + return doc.Document{}, nil + } + return docs[0], nil +} + +func (c *Chain) ListDocumentAttrs(ctx context.Context, namespace string, entryIds []string) ([]doc.DocumentAttr, error) { + attrs, err := c.MeiliClient.FilterAttr(ctx, &doc.DocumentAttrQuery{ + AttrQueries: []doc.AttrQuery{ + { + Attr: "namespace", + Option: "=", + Value: namespace, + }, + { + Attr: "entryId", + Option: "IN", + Value: entryIds, + }, + { + Attr: "kind", + Option: "=", + Value: "attr", + }, + }, + }) + if err != nil { + return nil, err + } + return attrs, nil +} + +func (c *Chain) GetDocumentAttrs(ctx context.Context, namespace, entryId string) ([]doc.DocumentAttr, error) { + attrs, err := c.MeiliClient.FilterAttr(ctx, &doc.DocumentAttrQuery{ + AttrQueries: []doc.AttrQuery{ + { + Attr: "namespace", + Option: "=", + Value: namespace, + }, + { + Attr: "entryId", + Option: "=", + Value: entryId, + }, + { + Attr: "kind", + Option: "=", + Value: "attr", + }, + }, + }) + if err != nil { + return nil, err + } + return attrs, nil +} + func (c *Chain) Search(ctx context.Context, query *doc.DocumentQuery, attrQueries []*doc.DocumentAttrQuery) ([]doc.Document, error) { attrs := []doc.DocumentAttr{} for _, attrQuery := range attrQueries { + attrQuery.AttrQueries = append(attrQuery.AttrQueries, doc.AttrQuery{ + Attr: "kind", + Option: "=", + Value: "attr", + }) attr, err := c.MeiliClient.FilterAttr(ctx, attrQuery) if err != nil { return nil, err @@ -101,6 +187,12 @@ func (c *Chain) Search(ctx context.Context, query *doc.DocumentQuery, attrQuerie if len(ids) == 0 && len(attrQueries) != 0 { return []doc.Document{}, nil } + + query.AttrQueries = append(query.AttrQueries, doc.AttrQuery{ + Attr: "kind", + Option: "=", + Value: "document", + }) if len(ids) != 0 { query.AttrQueries = append(query.AttrQueries, doc.AttrQuery{ Attr: "entryId", diff --git a/pkg/service/chain_test.go b/pkg/service/chain_test.go index efd33a7..c9726c9 100644 --- a/pkg/service/chain_test.go +++ b/pkg/service/chain_test.go @@ -18,7 +18,6 @@ package service_test import ( "context" - "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -61,31 +60,35 @@ var _ = Describe("Chain", func() { Namespace: "test-ns", EntryId: entryId11, Name: "test-name-11", + Kind: "document", Content: "

test

", - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: 1733584543, + UpdatedAt: 1733584543, } doc12 = &doc.Document{ Id: "2", Namespace: "test-ns", EntryId: entryId12, Name: "test-name-12", + Kind: "document", Content: "

test

", - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: 1733584543, + UpdatedAt: 1733584543, } doc21 = &doc.Document{ Id: "3", Namespace: "test-ns", EntryId: entryId21, Name: "test-name-21", + Kind: "document", Content: "

test

", - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: 1733584543, + UpdatedAt: 1733584543, } attr11 = &doc.DocumentAttr{ Id: "4", Namespace: "test-ns", + Kind: "attr", EntryId: entryId11, Key: "parentId", Value: parentId1, @@ -93,6 +96,7 @@ var _ = Describe("Chain", func() { attr12 = &doc.DocumentAttr{ Id: "5", Namespace: "test-ns", + Kind: "attr", EntryId: entryId12, Key: "parentId", Value: parentId1, @@ -100,6 +104,7 @@ var _ = Describe("Chain", func() { attr21 = &doc.DocumentAttr{ Id: "6", Namespace: "test-ns", + Kind: "attr", EntryId: entryId21, Key: "parentId", Value: parentId2, @@ -178,8 +183,8 @@ var _ = Describe("Chain", func() { EntryId: "100", Name: "test-name-100", Content: "

test

", - CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: 1733584543, + UpdatedAt: 1733584543, } err := Chain.Store(context.TODO(), doc3) Expect(err).Should(BeNil()) diff --git a/pkg/store/docstore/mock.go b/pkg/store/docstore/mock.go index 8e376ac..7cfb326 100644 --- a/pkg/store/docstore/mock.go +++ b/pkg/store/docstore/mock.go @@ -76,6 +76,13 @@ func (m *MockClient) FilterAttr(ctx context.Context, query *doc.DocumentAttrQuer matched = false } } + if q.Attr == "kind" { + all -= 1 + if !match(q, attr.Kind) { + matched = false + continue + } + } } if matched && all == 0 { result = append(result, attr) @@ -112,6 +119,13 @@ func (m *MockClient) Search(ctx context.Context, query *doc.DocumentQuery) ([]do continue } } + if q.Attr == "kind" { + all -= 1 + if !match(q, d.Kind) { + matched = false + continue + } + } } if matched && all == 0 && strings.Contains(d.Content, query.Search) { result = append(result, d) @@ -154,6 +168,13 @@ func (m *MockClient) DeleteByFilter(ctx context.Context, aqs []doc.AttrQuery) er continue } } + if q.Attr == "kind" { + all -= 1 + if !match(q, d.Kind) { + matched = false + continue + } + } } if matched && all == 0 { delete(docs, d.Id) @@ -169,6 +190,14 @@ func (m *MockClient) DeleteByFilter(ctx context.Context, aqs []doc.AttrQuery) er matched = false } } + if aq.Attr == "kind" { + all -= 1 + if !match(aq, attr.Kind) { + matched = false + continue + } + } + } if matched && all == 0 { delete(attrs, attr.Id)