Skip to content

Commit 1ffb6db

Browse files
committed
feat(demo): add dummy data generation with startup notification
- Create demo package with comprehensive API collection examples - Add JSONPlaceholder, ReqRes, and HTTPBin sample collections - Generate realistic endpoints with proper headers, query params, and request bodies - Show green success notification when dummy data is created on first app launch - Auto-skip generation if collections already exist - Clear notification on any user keypress - Fix notification persistence across view recreations Collections created: - JSONPlaceholder API (5 endpoints: CRUD operations) - ReqRes API (4 endpoints: user management and auth) - HTTPBin Testing (4 endpoints: HTTP testing utilities) Perfect for hackathon demos - users get instant realistic data to explore\!
1 parent 6a58b29 commit 1ffb6db

6 files changed

Lines changed: 279 additions & 10 deletions

File tree

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package demo
2+
3+
import (
4+
"context"
5+
6+
"github.com/maniac-en/req/internal/backend/collections"
7+
"github.com/maniac-en/req/internal/backend/endpoints"
8+
"github.com/maniac-en/req/internal/log"
9+
)
10+
11+
type DemoGenerator struct {
12+
collectionsManager *collections.CollectionsManager
13+
endpointsManager *endpoints.EndpointsManager
14+
}
15+
16+
func NewDemoGenerator(collectionsManager *collections.CollectionsManager, endpointsManager *endpoints.EndpointsManager) *DemoGenerator {
17+
return &DemoGenerator{
18+
collectionsManager: collectionsManager,
19+
endpointsManager: endpointsManager,
20+
}
21+
}
22+
23+
func (d *DemoGenerator) PopulateDummyData(ctx context.Context) (bool, error) {
24+
log.Info("populating dummy data for demo")
25+
26+
// Check if we already have collections
27+
result, err := d.collectionsManager.ListPaginated(ctx, 1, 0)
28+
if err != nil {
29+
log.Error("failed to check existing collections", "error", err)
30+
return false, err
31+
}
32+
33+
if len(result.Collections) > 0 {
34+
log.Debug("dummy data already exists, skipping population", "collections_count", len(result.Collections))
35+
return false, nil
36+
}
37+
38+
// Create demo collections and endpoints
39+
if err := d.createJSONPlaceholderCollection(ctx); err != nil {
40+
return false, err
41+
}
42+
43+
if err := d.createReqresCollection(ctx); err != nil {
44+
return false, err
45+
}
46+
47+
if err := d.createHTTPBinCollection(ctx); err != nil {
48+
return false, err
49+
}
50+
51+
log.Info("dummy data populated successfully")
52+
return true, nil
53+
}
54+
55+
func (d *DemoGenerator) createJSONPlaceholderCollection(ctx context.Context) error {
56+
collection, err := d.collectionsManager.Create(ctx, "JSONPlaceholder API")
57+
if err != nil {
58+
log.Error("failed to create JSONPlaceholder collection", "error", err)
59+
return err
60+
}
61+
62+
endpoints := []endpoints.EndpointData{
63+
{
64+
CollectionID: collection.ID,
65+
Name: "Get All Posts",
66+
Method: "GET",
67+
URL: "https://jsonplaceholder.typicode.com/posts",
68+
Headers: `{"Content-Type": "application/json"}`,
69+
QueryParams: map[string]string{},
70+
RequestBody: "",
71+
},
72+
{
73+
CollectionID: collection.ID,
74+
Name: "Get Single Post",
75+
Method: "GET",
76+
URL: "https://jsonplaceholder.typicode.com/posts/1",
77+
Headers: `{"Content-Type": "application/json"}`,
78+
QueryParams: map[string]string{},
79+
RequestBody: "",
80+
},
81+
{
82+
CollectionID: collection.ID,
83+
Name: "Create Post",
84+
Method: "POST",
85+
URL: "https://jsonplaceholder.typicode.com/posts",
86+
Headers: `{"Content-Type": "application/json"}`,
87+
QueryParams: map[string]string{},
88+
RequestBody: `{"title": "My New Post", "body": "This is the content of my new post", "userId": 1}`,
89+
},
90+
{
91+
CollectionID: collection.ID,
92+
Name: "Update Post",
93+
Method: "PUT",
94+
URL: "https://jsonplaceholder.typicode.com/posts/1",
95+
Headers: `{"Content-Type": "application/json"}`,
96+
QueryParams: map[string]string{},
97+
RequestBody: `{"id": 1, "title": "Updated Post", "body": "This post has been updated", "userId": 1}`,
98+
},
99+
{
100+
CollectionID: collection.ID,
101+
Name: "Delete Post",
102+
Method: "DELETE",
103+
URL: "https://jsonplaceholder.typicode.com/posts/1",
104+
Headers: `{"Content-Type": "application/json"}`,
105+
QueryParams: map[string]string{},
106+
RequestBody: "",
107+
},
108+
}
109+
110+
return d.createEndpoints(ctx, endpoints)
111+
}
112+
113+
func (d *DemoGenerator) createReqresCollection(ctx context.Context) error {
114+
collection, err := d.collectionsManager.Create(ctx, "ReqRes API")
115+
if err != nil {
116+
log.Error("failed to create ReqRes collection", "error", err)
117+
return err
118+
}
119+
120+
endpoints := []endpoints.EndpointData{
121+
{
122+
CollectionID: collection.ID,
123+
Name: "List Users",
124+
Method: "GET",
125+
URL: "https://reqres.in/api/users",
126+
Headers: `{"Content-Type": "application/json"}`,
127+
QueryParams: map[string]string{"page": "2"},
128+
RequestBody: "",
129+
},
130+
{
131+
CollectionID: collection.ID,
132+
Name: "Single User",
133+
Method: "GET",
134+
URL: "https://reqres.in/api/users/2",
135+
Headers: `{"Content-Type": "application/json"}`,
136+
QueryParams: map[string]string{},
137+
RequestBody: "",
138+
},
139+
{
140+
CollectionID: collection.ID,
141+
Name: "Create User",
142+
Method: "POST",
143+
URL: "https://reqres.in/api/users",
144+
Headers: `{"Content-Type": "application/json"}`,
145+
QueryParams: map[string]string{},
146+
RequestBody: `{"name": "morpheus", "job": "leader"}`,
147+
},
148+
{
149+
CollectionID: collection.ID,
150+
Name: "Login",
151+
Method: "POST",
152+
URL: "https://reqres.in/api/login",
153+
Headers: `{"Content-Type": "application/json"}`,
154+
QueryParams: map[string]string{},
155+
RequestBody: `{"email": "eve.holt@reqres.in", "password": "cityslicka"}`,
156+
},
157+
}
158+
159+
return d.createEndpoints(ctx, endpoints)
160+
}
161+
162+
func (d *DemoGenerator) createHTTPBinCollection(ctx context.Context) error {
163+
collection, err := d.collectionsManager.Create(ctx, "HTTPBin Testing")
164+
if err != nil {
165+
log.Error("failed to create HTTPBin collection", "error", err)
166+
return err
167+
}
168+
169+
endpoints := []endpoints.EndpointData{
170+
{
171+
CollectionID: collection.ID,
172+
Name: "Test GET",
173+
Method: "GET",
174+
URL: "https://httpbin.org/get",
175+
Headers: `{"User-Agent": "Req-Terminal-Client/1.0"}`,
176+
QueryParams: map[string]string{"test": "value", "demo": "true"},
177+
RequestBody: "",
178+
},
179+
{
180+
CollectionID: collection.ID,
181+
Name: "Test POST JSON",
182+
Method: "POST",
183+
URL: "https://httpbin.org/post",
184+
Headers: `{"Content-Type": "application/json", "User-Agent": "Req-Terminal-Client/1.0"}`,
185+
QueryParams: map[string]string{},
186+
RequestBody: `{"message": "Hello from Req!", "timestamp": "2024-01-15T10:30:00Z", "data": {"key": "value"}}`,
187+
},
188+
{
189+
CollectionID: collection.ID,
190+
Name: "Test Headers",
191+
Method: "GET",
192+
URL: "https://httpbin.org/headers",
193+
Headers: `{"Authorization": "Bearer demo-token", "X-Custom-Header": "req-demo"}`,
194+
QueryParams: map[string]string{},
195+
RequestBody: "",
196+
},
197+
{
198+
CollectionID: collection.ID,
199+
Name: "Test Status Codes",
200+
Method: "GET",
201+
URL: "https://httpbin.org/status/200",
202+
Headers: `{"Content-Type": "application/json"}`,
203+
QueryParams: map[string]string{},
204+
RequestBody: "",
205+
},
206+
}
207+
208+
return d.createEndpoints(ctx, endpoints)
209+
}
210+
211+
func (d *DemoGenerator) createEndpoints(ctx context.Context, endpointData []endpoints.EndpointData) error {
212+
for _, data := range endpointData {
213+
_, err := d.endpointsManager.CreateEndpoint(ctx, data)
214+
if err != nil {
215+
log.Error("failed to create endpoint", "name", data.Name, "error", err)
216+
return err
217+
}
218+
log.Debug("created demo endpoint", "name", data.Name, "method", data.Method, "url", data.URL)
219+
}
220+
return nil
221+
}

internal/tui/app/context.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import (
88
)
99

1010
type Context struct {
11-
Collections *collections.CollectionsManager
12-
Endpoints *endpoints.EndpointsManager
13-
HTTP *http.HTTPManager
14-
History *history.HistoryManager
11+
Collections *collections.CollectionsManager
12+
Endpoints *endpoints.EndpointsManager
13+
HTTP *http.HTTPManager
14+
History *history.HistoryManager
15+
DummyDataCreated bool
1516
}
1617

1718
func NewContext(
@@ -21,9 +22,14 @@ func NewContext(
2122
history *history.HistoryManager,
2223
) *Context {
2324
return &Context{
24-
Collections: collections,
25-
Endpoints: endpoints,
26-
HTTP: httpManager,
27-
History: history,
25+
Collections: collections,
26+
Endpoints: endpoints,
27+
HTTP: httpManager,
28+
History: history,
29+
DummyDataCreated: false,
2830
}
2931
}
32+
33+
func (c *Context) SetDummyDataCreated(created bool) {
34+
c.DummyDataCreated = created
35+
}

internal/tui/app/model.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,15 @@ type Model struct {
3030
}
3131

3232
func NewModel(ctx *Context) Model {
33+
collectionsView := views.NewCollectionsView(ctx.Collections)
34+
if ctx.DummyDataCreated {
35+
collectionsView.SetDummyDataNotification(true)
36+
}
37+
3338
m := Model{
3439
ctx: ctx,
3540
mode: CollectionsViewMode,
36-
collectionsView: views.NewCollectionsView(ctx.Collections),
41+
collectionsView: collectionsView,
3742
addCollectionView: views.NewAddCollectionView(ctx.Collections),
3843
}
3944
return m
@@ -117,6 +122,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
117122
m.height = msg.Height
118123
if m.mode == CollectionsViewMode && !m.collectionsView.IsInitialized() {
119124
m.collectionsView = views.NewCollectionsViewWithSize(m.ctx.Collections, m.width, m.height)
125+
if m.ctx.DummyDataCreated {
126+
m.collectionsView.SetDummyDataNotification(true)
127+
}
120128
return m, m.collectionsView.Init()
121129
}
122130
if m.mode == CollectionsViewMode {
@@ -126,6 +134,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
126134
m.mode = CollectionsViewMode
127135
if m.width > 0 && m.height > 0 {
128136
m.collectionsView = views.NewCollectionsViewWithSize(m.ctx.Collections, m.width, m.height)
137+
if m.ctx.DummyDataCreated {
138+
m.collectionsView.SetDummyDataNotification(true)
139+
}
129140
}
130141
m.collectionsView.SetSelectedIndex(m.selectedIndex)
131142
return m, m.collectionsView.Init()

internal/tui/views/collections.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import (
55
"fmt"
66

77
tea "github.com/charmbracelet/bubbletea"
8+
"github.com/charmbracelet/lipgloss"
89
"github.com/maniac-en/req/internal/backend/collections"
910
"github.com/maniac-en/req/internal/backend/crud"
11+
"github.com/maniac-en/req/internal/log"
1012
"github.com/maniac-en/req/internal/tui/components"
13+
"github.com/maniac-en/req/internal/tui/styles"
1114
)
1215

1316
type CollectionsView struct {
@@ -18,6 +21,7 @@ type CollectionsView struct {
1821
height int
1922
initialized bool
2023
selectedIndex int
24+
showDummyDataNotif bool
2125

2226
currentPage int
2327
pageSize int
@@ -42,6 +46,10 @@ func NewCollectionsViewWithSize(collectionsManager *collections.CollectionsManag
4246
}
4347
}
4448

49+
func (v *CollectionsView) SetDummyDataNotification(show bool) {
50+
v.showDummyDataNotif = show
51+
}
52+
4553
func (v CollectionsView) Init() tea.Cmd {
4654
return v.loadCollections
4755
}
@@ -123,6 +131,11 @@ func (v CollectionsView) Update(msg tea.Msg) (CollectionsView, tea.Cmd) {
123131
break
124132
}
125133

134+
// Clear dummy data notification on any keypress
135+
if v.showDummyDataNotif {
136+
v.showDummyDataNotif = false
137+
}
138+
126139
if !v.list.IsFiltering() {
127140
switch msg.String() {
128141
case "n", "right":
@@ -207,6 +220,14 @@ func (v CollectionsView) View() string {
207220
instructions += " • p/n: prev/next page"
208221
}
209222

223+
// Show dummy data notification if needed
224+
if v.showDummyDataNotif {
225+
instructions = lipgloss.NewStyle().
226+
Foreground(styles.Success).
227+
Bold(true).
228+
Render("✓ Demo data created! 3 collections with sample API endpoints ready to explore")
229+
}
230+
210231
return v.layout.FullView(
211232
"Collections",
212233
content,

main.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
tea "github.com/charmbracelet/bubbletea"
1414
"github.com/maniac-en/req/internal/backend/collections"
1515
"github.com/maniac-en/req/internal/backend/database"
16+
"github.com/maniac-en/req/internal/backend/demo"
1617
"github.com/maniac-en/req/internal/backend/endpoints"
1718
"github.com/maniac-en/req/internal/backend/history"
1819
"github.com/maniac-en/req/internal/backend/http"
@@ -140,7 +141,16 @@ func main() {
140141
historyManager,
141142
)
142143

143-
log.Info("application initialized", "components", []string{"database", "collections", "endpoints", "http", "history", "logging"})
144+
// populate dummy data for demo
145+
demoGenerator := demo.NewDemoGenerator(collectionsManager, endpointsManager)
146+
dummyDataCreated, err := demoGenerator.PopulateDummyData(context.Background())
147+
if err != nil {
148+
log.Error("failed to populate dummy data", "error", err)
149+
} else if dummyDataCreated {
150+
appContext.SetDummyDataCreated(true)
151+
}
152+
153+
log.Info("application initialized", "components", []string{"database", "collections", "endpoints", "http", "history", "logging", "demo"})
144154
log.Debug("configuration loaded", "collections_manager", collectionsManager != nil, "endpoints", endpointsManager != nil, "database", db != nil, "http_manager", httpManager != nil, "history_manager", historyManager != nil)
145155
log.Info("application started successfully")
146156

web/banner.png

1.52 MB
Loading

0 commit comments

Comments
 (0)