Skip to content

Commit fa12cae

Browse files
ElecTwixTomaszal
authored andcommitted
feat(server/ritemapiexample): Refactor example breadcrumbs
Replaced the SQL query `GetExampleAllParentsNames` with Go logic to fetch and construct example breadcrumbs. Introduced the `mexamplebreadcrumb` model to represent structured breadcrumb items (collection, folder, endpoint, example). Updated the `sitemapiexample` service to recursively fetch parent folders and build the breadcrumb path using individual item models. Modified the API handler and translation layers to consume and serialize the new structured breadcrumb format.
1 parent abe7128 commit fa12cae

9 files changed

Lines changed: 157 additions & 92 deletions

File tree

packages/db/pkg/sqlc/query.sql

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -136,50 +136,6 @@ WHERE
136136
LIMIT
137137
1;
138138

139-
-- name: GetExampleAllParentsNames :one
140-
-- name: GetExampleAllParentsNames :one
141-
WITH RECURSIVE folder_path AS (
142-
SELECT
143-
f.id,
144-
f.name,
145-
f.parent_id,
146-
f.collection_id,
147-
1 AS level,
148-
f.name AS path
149-
FROM
150-
item_folder f
151-
WHERE
152-
f.id = (SELECT folder_id FROM item_api WHERE item_api.id = (SELECT item_api_id FROM item_api_example WHERE item_api_example.id = ?))
153-
AND f.id IS NOT NULL
154-
155-
UNION ALL
156-
157-
SELECT
158-
f.id,
159-
f.name,
160-
f.parent_id,
161-
f.collection_id,
162-
fp.level + 1,
163-
fp.path || '/' || f.name
164-
FROM
165-
item_folder f
166-
JOIN
167-
folder_path fp ON f.id = fp.parent_id
168-
)
169-
SELECT
170-
c.name AS collection_name,
171-
a.name AS api_name,
172-
e.name AS example_name,
173-
COALESCE((SELECT path FROM folder_path WHERE parent_id IS NULL ORDER BY level DESC LIMIT 1), '') AS folder_path
174-
FROM
175-
item_api_example e
176-
JOIN
177-
item_api a ON e.item_api_id = a.id
178-
JOIN
179-
collections c ON e.collection_id = c.id
180-
WHERE
181-
e.id = ?;
182-
183139
-- name: GetItemExampleByCollectionIDAndNextIDAndItemApiID :one
184140
SELECT
185141
id,

packages/server/cmd/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func main() {
225225
newServiceManager.AddService(ritemfolder.CreateService(folderItemSrv, opitonsAll))
226226

227227
// Api Item Example
228-
itemApiExampleSrv := ritemapiexample.New(currentDB, exampleService, endpointService,
228+
itemApiExampleSrv := ritemapiexample.New(currentDB, exampleService, endpointService, folderService,
229229
workspaceService, collectionService, userService, exampleHeaderService, exampleQueryService, bodyFormService, bodyUrlService,
230230
bodyRawService, exampleResponseHeaderService, exampleResponseService, environmentService, variableService, assertService, assertResultService, logMap)
231231
newServiceManager.AddService(ritemapiexample.CreateService(itemApiExampleSrv, opitonsAll))

packages/server/internal/api/ritemapiexample/ritemapiexample.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ import (
4141
"the-dev-tools/server/pkg/service/sexamplerespheader"
4242
"the-dev-tools/server/pkg/service/sitemapi"
4343
"the-dev-tools/server/pkg/service/sitemapiexample"
44+
"the-dev-tools/server/pkg/service/sitemfolder"
4445
"the-dev-tools/server/pkg/service/suser"
4546
"the-dev-tools/server/pkg/service/svar"
4647
"the-dev-tools/server/pkg/service/sworkspace"
4748
"the-dev-tools/server/pkg/translate/tassert"
49+
"the-dev-tools/server/pkg/translate/tbreadcrumbs"
4850
"the-dev-tools/server/pkg/translate/texample"
51+
"the-dev-tools/server/pkg/translate/tgeneric"
4952
"the-dev-tools/server/pkg/varsystem"
5053
examplev1 "the-dev-tools/spec/dist/buf/go/collection/item/example/v1"
5154
"the-dev-tools/spec/dist/buf/go/collection/item/example/v1/examplev1connect"
@@ -60,6 +63,7 @@ type ItemAPIExampleRPC struct {
6063
DB *sql.DB
6164
iaes *sitemapiexample.ItemApiExampleService
6265
ias *sitemapi.ItemApiService
66+
ifs *sitemfolder.ItemFolderService
6367

6468
ws *sworkspace.WorkspaceService
6569
cs *scollection.CollectionService
@@ -84,7 +88,7 @@ type ItemAPIExampleRPC struct {
8488
logChanMap logconsole.LogChanMap
8589
}
8690

87-
func New(db *sql.DB, iaes sitemapiexample.ItemApiExampleService, ias sitemapi.ItemApiService,
91+
func New(db *sql.DB, iaes sitemapiexample.ItemApiExampleService, ias sitemapi.ItemApiService, ifs sitemfolder.ItemFolderService,
8892
ws sworkspace.WorkspaceService, cs scollection.CollectionService, us suser.UserService, hs sexampleheader.HeaderService, qs sexamplequery.ExampleQueryService,
8993
bfs sbodyform.BodyFormService, beus sbodyurl.BodyURLEncodedService, brs sbodyraw.BodyRawService, erhs sexamplerespheader.ExampleRespHeaderService,
9094
ers sexampleresp.ExampleRespService, es senv.EnvService, vs svar.VarService, as sassert.AssertService, ars sassertres.AssertResultService,
@@ -94,6 +98,7 @@ func New(db *sql.DB, iaes sitemapiexample.ItemApiExampleService, ias sitemapi.It
9498
DB: db,
9599
iaes: &iaes,
96100
ias: &ias,
101+
ifs: &ifs,
97102
ws: &ws,
98103
cs: &cs,
99104
us: &us,
@@ -172,7 +177,7 @@ func (c *ItemAPIExampleRPC) ExampleGet(ctx context.Context, req *connect.Request
172177
return nil, connect.NewError(connect.CodeInternal, err)
173178
}
174179

175-
exampleBreadcrumbs, err := c.iaes.GetExampleAllParentsNames(ctx, exampleIdWrap)
180+
exampleBreadcrumbs, err := c.iaes.GetExampleAllParents(ctx, exampleIdWrap, *c.cs, *c.ifs, *c.ias)
176181
if err != nil {
177182
return nil, connect.NewError(connect.CodeInternal, err)
178183
}
@@ -186,7 +191,10 @@ func (c *ItemAPIExampleRPC) ExampleGet(ctx context.Context, req *connect.Request
186191
if exampleResp != nil {
187192
respIdPtr = &exampleResp.ID
188193
}
189-
rpcExample := texample.SerializeModelToRPC(*example, respIdPtr, *exampleBreadcrumbs)
194+
195+
rpcBreadcrumbs := tgeneric.MassConvert(exampleBreadcrumbs, tbreadcrumbs.SerializeModelToRPC)
196+
197+
rpcExample := texample.SerializeModelToRPC(*example, respIdPtr, rpcBreadcrumbs)
190198
resp := &examplev1.ExampleGetResponse{
191199
ExampleId: rpcExample.ExampleId,
192200
LastResponseId: rpcExample.LastResponseId,

packages/server/internal/api/ritemapiexample/ritemapiexample_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"the-dev-tools/server/pkg/service/sexamplerespheader"
2727
"the-dev-tools/server/pkg/service/sitemapi"
2828
"the-dev-tools/server/pkg/service/sitemapiexample"
29+
"the-dev-tools/server/pkg/service/sitemfolder"
2930
"the-dev-tools/server/pkg/service/suser"
3031
"the-dev-tools/server/pkg/service/svar"
3132
"the-dev-tools/server/pkg/service/sworkspace"
@@ -46,6 +47,7 @@ func TestGetExampleApi(t *testing.T) {
4647

4748
ias := sitemapi.New(queries)
4849
iaes := sitemapiexample.New(queries)
50+
ifs := sitemfolder.New(queries)
4951
ws := sworkspace.New(queries)
5052
cs := scollection.New(queries, mockLogger)
5153
us := suser.New(queries)
@@ -113,7 +115,7 @@ func TestGetExampleApi(t *testing.T) {
113115

114116
logChanMap := logconsole.NewLogChanMapWith(10000)
115117

116-
rpcExample := ritemapiexample.New(db, iaes, ias,
118+
rpcExample := ritemapiexample.New(db, iaes, ias, ifs,
117119
ws, cs, us, hs, qs, bfs, bues, brs, erhs, ers, es, vs, as, ars, logChanMap)
118120
authedCtx := mwauth.CreateAuthedContext(ctx, UserID)
119121
resp, err := rpcExample.ExampleGet(authedCtx, req)
@@ -152,6 +154,7 @@ func TestCreateExampleApi(t *testing.T) {
152154

153155
ias := sitemapi.New(queries)
154156
iaes := sitemapiexample.New(queries)
157+
ifs := sitemfolder.New(queries)
155158
ws := sworkspace.New(queries)
156159
cs := scollection.New(queries, mockLogger)
157160
us := suser.New(queries)
@@ -201,7 +204,7 @@ func TestCreateExampleApi(t *testing.T) {
201204

202205
logChanMap := logconsole.NewLogChanMapWith(10000)
203206

204-
rpcExample := ritemapiexample.New(db, iaes, ias,
207+
rpcExample := ritemapiexample.New(db, iaes, ias, ifs,
205208
ws, cs, us, hs, qs, bfs, bues, brs, erhs, ers, es, vs, as, ars, logChanMap)
206209
authedCtx := mwauth.CreateAuthedContext(ctx, UserID)
207210
resp, err := rpcExample.ExampleCreate(authedCtx, req)
@@ -251,6 +254,7 @@ func TestUpdateExampleApi(t *testing.T) {
251254

252255
ias := sitemapi.New(queries)
253256
iaes := sitemapiexample.New(queries)
257+
ifs := sitemfolder.New(queries)
254258
ws := sworkspace.New(queries)
255259
cs := scollection.New(queries, mockLogger)
256260
us := suser.New(queries)
@@ -323,7 +327,7 @@ func TestUpdateExampleApi(t *testing.T) {
323327

324328
logChanMap := logconsole.NewLogChanMapWith(10000)
325329

326-
rpcExample := ritemapiexample.New(db, iaes, ias,
330+
rpcExample := ritemapiexample.New(db, iaes, ias, ifs,
327331
ws, cs, us, hs, qs, bfs, bues, brs, erhs, ers, es, vs, as, ars, logChanMap)
328332
authedCtx := mwauth.CreateAuthedContext(ctx, UserID)
329333
resp, err := rpcExample.ExampleUpdate(authedCtx, req)
@@ -360,6 +364,7 @@ func TestDeleteExampleApi(t *testing.T) {
360364

361365
ias := sitemapi.New(queries)
362366
iaes := sitemapiexample.New(queries)
367+
ifs := sitemfolder.New(queries)
363368
ws := sworkspace.New(queries)
364369
cs := scollection.New(queries, mockLogger)
365370
us := suser.New(queries)
@@ -427,7 +432,7 @@ func TestDeleteExampleApi(t *testing.T) {
427432

428433
logChanMap := logconsole.NewLogChanMapWith(10000)
429434

430-
rpcExample := ritemapiexample.New(db, iaes, ias,
435+
rpcExample := ritemapiexample.New(db, iaes, ias, ifs,
431436
ws, cs, us, hs, qs, bfs, bues, brs, erhs, ers, es, vs, as, ars, logChanMap)
432437
authedCtx := mwauth.CreateAuthedContext(ctx, UserID)
433438
resp, err := rpcExample.ExampleDelete(authedCtx, req)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package mexamplebreadcrumb
2+
3+
import (
4+
"the-dev-tools/server/pkg/model/mcollection"
5+
"the-dev-tools/server/pkg/model/mitemapi"
6+
"the-dev-tools/server/pkg/model/mitemapiexample"
7+
"the-dev-tools/server/pkg/model/mitemfolder"
8+
)
9+
10+
type ExampleBreadcrumbKind = uint8
11+
12+
const (
13+
EXAMPLE_BREADCRUMB_KIND_UNSPECIFIED ExampleBreadcrumbKind = iota
14+
EXAMPLE_BREADCRUMB_KIND_COLLECTION
15+
EXAMPLE_BREADCRUMB_KIND_FOLDER
16+
EXAMPLE_BREADCRUMB_KIND_ENDPOINT
17+
EXAMPLE_BREADCRUMB_KIND_EXAMPLE
18+
)
19+
20+
type ExampleBreadcrumb struct {
21+
Kind ExampleBreadcrumbKind
22+
Collection *mcollection.Collection
23+
Folder *mitemfolder.ItemFolder
24+
Endpoint *mitemapi.ItemApi
25+
Example *mitemapiexample.ItemApiExample
26+
}

packages/server/pkg/model/mitemapiexample/mitemapiexample.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,3 @@ type ItemApiExample struct {
3737
func (i ItemApiExample) GetCreatedTime() time.Time {
3838
return i.ID.Time()
3939
}
40-
41-
type ExampleBreadcrumbs struct {
42-
CollectionName string
43-
ApiName string
44-
ExampleName string
45-
FolderPath *string
46-
}

packages/server/pkg/service/sitemapiexample/sitemapiexample.go

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import (
88
"slices"
99
"the-dev-tools/db/pkg/sqlc/gen"
1010
"the-dev-tools/server/pkg/idwrap"
11+
"the-dev-tools/server/pkg/model/mexamplebreadcrumb"
1112
"the-dev-tools/server/pkg/model/mitemapiexample"
13+
"the-dev-tools/server/pkg/model/mitemfolder"
14+
"the-dev-tools/server/pkg/service/scollection"
15+
"the-dev-tools/server/pkg/service/sitemapi"
16+
"the-dev-tools/server/pkg/service/sitemfolder"
1217
)
1318

1419
type ItemApiExampleService struct {
@@ -119,32 +124,55 @@ func (iaes ItemApiExampleService) GetApiExample(ctx context.Context, id idwrap.I
119124
return ConvertToModelItem(itemApiExample), nil
120125
}
121126

122-
func (iaes ItemApiExampleService) GetExampleAllParentsNames(ctx context.Context, id idwrap.IDWrap) (*mitemapiexample.ExampleBreadcrumbs, error) {
123-
arg := gen.GetExampleAllParentsNamesParams{
124-
ID: id,
125-
ID_2: id,
127+
func (iaes ItemApiExampleService) GetExampleAllParents(ctx context.Context, id idwrap.IDWrap, collectionService scollection.CollectionService, folderService sitemfolder.ItemFolderService, endpointService sitemapi.ItemApiService) ([]mexamplebreadcrumb.ExampleBreadcrumb, error) {
128+
129+
example, err := iaes.GetApiExample(ctx, id)
130+
if err != nil {
131+
return nil, err
132+
}
133+
endpoint, err := endpointService.GetItemApi(ctx, example.ItemApiID)
134+
if err != nil {
135+
return nil, err
126136
}
127137

128-
names, err := iaes.Queries.GetExampleAllParentsNames(ctx, arg)
138+
collection, err := collectionService.GetCollection(ctx, example.CollectionID)
129139
if err != nil {
130140
return nil, err
131141
}
132-
var folderPathPtr *string
133-
if names.FolderPath != nil {
134-
path, ok := names.FolderPath.(string)
135-
if !ok {
136-
return nil, errors.New("folderPath type is not string")
142+
143+
folderID := endpoint.FolderID
144+
var folders []mitemfolder.ItemFolder
145+
for folderID != nil {
146+
folder, err := folderService.GetFolder(ctx, *folderID)
147+
if err != nil {
148+
return nil, err
137149
}
138-
folderPathPtr = &path
150+
folders = append(folders, *folder)
151+
folderID = folder.ParentID
139152
}
140153

141-
breadcrumbs := mitemapiexample.ExampleBreadcrumbs{
142-
CollectionName: names.CollectionName,
143-
ApiName: names.ApiName,
144-
ExampleName: names.ExampleName,
145-
FolderPath: folderPathPtr,
154+
var crumbs []mexamplebreadcrumb.ExampleBreadcrumb
155+
156+
crumbs = append(crumbs, mexamplebreadcrumb.ExampleBreadcrumb{
157+
Kind: mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_COLLECTION,
158+
Collection: collection,
159+
})
160+
for _, folder := range folders {
161+
crumbs = append(crumbs, mexamplebreadcrumb.ExampleBreadcrumb{
162+
Kind: mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_FOLDER,
163+
Folder: &folder,
164+
})
146165
}
147-
return &breadcrumbs, nil
166+
crumbs = append(crumbs, mexamplebreadcrumb.ExampleBreadcrumb{
167+
Kind: mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_ENDPOINT,
168+
Endpoint: endpoint,
169+
})
170+
crumbs = append(crumbs, mexamplebreadcrumb.ExampleBreadcrumb{
171+
Kind: mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_EXAMPLE,
172+
Example: example,
173+
})
174+
175+
return crumbs, nil
148176
}
149177

150178
func (iaes ItemApiExampleService) GetApiExampleByCollection(ctx context.Context, collectionID idwrap.IDWrap) ([]mitemapiexample.ItemApiExample, error) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package tbreadcrumbs
2+
3+
import (
4+
"the-dev-tools/server/pkg/idwrap"
5+
"the-dev-tools/server/pkg/model/mexamplebreadcrumb"
6+
"the-dev-tools/server/pkg/model/mitemapiexample"
7+
"the-dev-tools/server/pkg/translate/tcollection"
8+
"the-dev-tools/server/pkg/translate/texample"
9+
"the-dev-tools/server/pkg/translate/tfolder"
10+
"the-dev-tools/server/pkg/translate/titemapi"
11+
examplev1 "the-dev-tools/spec/dist/buf/go/collection/item/example/v1"
12+
)
13+
14+
func SerializeModelToRPC(breadCrumb mexamplebreadcrumb.ExampleBreadcrumb) *examplev1.ExampleBreadcrumb {
15+
// Split folder path into an array of folder names
16+
17+
rpcBreadcrumb := &examplev1.ExampleBreadcrumb{
18+
Kind: examplev1.ExampleBreadcrumbKind(breadCrumb.Kind),
19+
}
20+
switch breadCrumb.Kind {
21+
case mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_COLLECTION:
22+
rpcBreadcrumb.Collection = tcollection.SerializeCollectionModelToRPC(*breadCrumb.Collection)
23+
case mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_FOLDER:
24+
rpcBreadcrumb.Folder = tfolder.SeralizeModelToRPCItem(*breadCrumb.Folder)
25+
case mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_ENDPOINT:
26+
rpcBreadcrumb.Endpoint = titemapi.SeralizeModelToRPCItem(breadCrumb.Endpoint)
27+
case mexamplebreadcrumb.EXAMPLE_BREADCRUMB_KIND_EXAMPLE:
28+
example := breadCrumb.Example
29+
rpcBreadcrumb.Example = texample.SerializeModelToRPCItem(*example, nil)
30+
}
31+
32+
return rpcBreadcrumb
33+
34+
}
35+
36+
func SerializeModelToRPCItem(ex mitemapiexample.ItemApiExample, lastRespID *idwrap.IDWrap) *examplev1.ExampleListItem {
37+
var lastResp []byte = nil
38+
if lastRespID != nil {
39+
lastResp = lastRespID.Bytes()
40+
}
41+
42+
return &examplev1.ExampleListItem{
43+
ExampleId: ex.ID.Bytes(),
44+
Name: ex.Name,
45+
LastResponseId: lastResp,
46+
}
47+
}
48+
49+
func DeserializeRPCToModel(ex *examplev1.Example) (mitemapiexample.ItemApiExample, error) {
50+
if ex == nil {
51+
return mitemapiexample.ItemApiExample{}, nil
52+
}
53+
id, err := idwrap.NewFromBytes(ex.GetExampleId())
54+
if err != nil {
55+
return mitemapiexample.ItemApiExample{}, err
56+
}
57+
58+
return mitemapiexample.ItemApiExample{
59+
ID: id,
60+
BodyType: mitemapiexample.BodyType(ex.BodyKind),
61+
Name: ex.Name,
62+
}, nil
63+
}

0 commit comments

Comments
 (0)