Skip to content

Commit 77551db

Browse files
authored
Merge pull request #106 from raghavyuva/feat/develop
v0.1.0-alpha.6
2 parents 4c54084 + 52f2755 commit 77551db

File tree

70 files changed

+355
-80
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+355
-80
lines changed

api/Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ run: build
66
@./bin/$(APP_NAME)
77

88
test:
9-
@go test -p 1 ./... -v -count=1
9+
@go test -p 1 ./internal/features/... -v -count=1
10+
11+
test-all:
12+
@go test -p 1 ./... -v -count=1
13+
14+
test-routes:
15+
@go test -p 1 ./internal/tests/routes/... -v -count=1

api/api/versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"version": "v1",
55
"status": "active",
6-
"release_date": "2025-05-20T06:54:40.525772+05:30",
6+
"release_date": "2025-05-21T12:13:32.366669+05:30",
77
"end_of_life": "0001-01-01T00:00:00Z",
88
"changes": [
99
"Initial API version"

api/internal/features/deploy/proxy/caddy.go

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -72,29 +72,31 @@ func (c *Caddy) Serve() error {
7272
Handle: []interface{}{handle},
7373
}
7474

75-
jsonData, err := json.Marshal([]Route{routeConfig})
75+
// Get current config
76+
config, err := c.GetConfig()
7677
if err != nil {
77-
return fmt.Errorf("failed to marshal route config: %w", err)
78+
return fmt.Errorf("failed to get current config: %w", err)
7879
}
7980

80-
req, err := http.NewRequest("POST", c.Endpoint+"/config/apps/http/servers/nixopus/routes/...", bytes.NewBuffer(jsonData))
81-
if err != nil {
82-
return fmt.Errorf("failed to create request: %w", err)
81+
// Replace the route for our domain
82+
server := config.Apps.HTTP.Servers["nixopus"]
83+
var newRoutes []Route
84+
for _, route := range server.Routes {
85+
if len(route.Match) > 0 && len(route.Match[0].Host) > 0 && route.Match[0].Host[0] != c.Domain {
86+
newRoutes = append(newRoutes, route)
87+
}
8388
}
84-
req.Header.Set("Content-Type", "application/json")
8589

86-
resp, err := c.client.Do(req)
87-
if err != nil {
88-
return fmt.Errorf("failed to send request: %w", err)
89-
}
90-
defer resp.Body.Close()
90+
newRoutes = append(newRoutes, routeConfig)
91+
server.Routes = newRoutes
92+
config.Apps.HTTP.Servers["nixopus"] = server
9193

92-
if resp.StatusCode != http.StatusOK {
93-
body, _ := io.ReadAll(resp.Body)
94-
return fmt.Errorf("failed to append route: %s - %s", resp.Status, string(body))
94+
// Update the configuration
95+
if err := c.UpdateConfig(config); err != nil {
96+
return fmt.Errorf("failed to update caddy configuration: %w", err)
9597
}
9698

97-
c.Logger.Log(logger.Info, "Caddy server started successfully", "")
99+
c.Logger.Log(logger.Info, "Caddy server configuration updated successfully", "")
98100
return nil
99101
}
100102

@@ -112,33 +114,6 @@ func (c *Caddy) checkCaddyRunning() error {
112114
return nil
113115
}
114116

115-
func (c *Caddy) loadConfig(config CaddyConfig) error {
116-
jsonData, err := json.Marshal(config)
117-
if err != nil {
118-
return fmt.Errorf("failed to marshal config: %w", err)
119-
}
120-
121-
req, err := http.NewRequest("POST", c.Endpoint+"/load", bytes.NewBuffer(jsonData))
122-
if err != nil {
123-
return fmt.Errorf("failed to create request: %w", err)
124-
}
125-
req.Header.Set("Content-Type", "application/json")
126-
req.Header.Set("Cache-Control", "must-revalidate")
127-
128-
resp, err := c.client.Do(req)
129-
if err != nil {
130-
return fmt.Errorf("failed to send request: %w", err)
131-
}
132-
defer resp.Body.Close()
133-
134-
if resp.StatusCode != http.StatusOK {
135-
body, _ := io.ReadAll(resp.Body)
136-
return fmt.Errorf("failed to load config: %s - %s", resp.Status, string(body))
137-
}
138-
139-
return nil
140-
}
141-
142117
func (c *Caddy) Stop() error {
143118
req, err := http.NewRequest("POST", c.Endpoint+"/stop", nil)
144119
if err != nil {

api/internal/features/feature-flags/storage/init.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@ func NewFeatureFlagStorage(db *bun.DB, ctx context.Context) *FeatureFlagStorage
3232
}
3333
}
3434

35+
func (s *FeatureFlagStorage) getDB() bun.IDB {
36+
if s.tx != nil {
37+
return s.tx
38+
}
39+
return s.DB
40+
}
41+
3542
func (s *FeatureFlagStorage) GetFeatureFlags(organizationID uuid.UUID) ([]types.FeatureFlag, error) {
3643
var flags []types.FeatureFlag
37-
err := s.DB.NewSelect().
44+
err := s.getDB().NewSelect().
3845
Model(&flags).
3946
Where("organization_id = ?", organizationID).
4047
Where("deleted_at IS NULL").
@@ -49,7 +56,7 @@ func (s *FeatureFlagStorage) GetFeatureFlags(organizationID uuid.UUID) ([]types.
4956

5057
func (s *FeatureFlagStorage) UpdateFeatureFlag(organizationID uuid.UUID, featureName string, isEnabled bool) error {
5158
flag := &types.FeatureFlag{}
52-
err := s.DB.NewSelect().
59+
err := s.getDB().NewSelect().
5360
Model(flag).
5461
Where("organization_id = ?", organizationID).
5562
Where("feature_name = ?", featureName).
@@ -66,15 +73,15 @@ func (s *FeatureFlagStorage) UpdateFeatureFlag(organizationID uuid.UUID, feature
6673
CreatedAt: time.Now(),
6774
UpdatedAt: time.Now(),
6875
}
69-
_, err = s.DB.NewInsert().Model(flag).Exec(s.Ctx)
76+
_, err = s.getDB().NewInsert().Model(flag).Exec(s.Ctx)
7077
return err
7178
}
7279
return fmt.Errorf("failed to get feature flag: %w", err)
7380
}
7481

7582
flag.IsEnabled = isEnabled
7683
flag.UpdatedAt = time.Now()
77-
_, err = s.DB.NewUpdate().
84+
_, err = s.getDB().NewUpdate().
7885
Model(flag).
7986
Where("id = ?", flag.ID).
8087
Exec(s.Ctx)
@@ -84,7 +91,7 @@ func (s *FeatureFlagStorage) UpdateFeatureFlag(organizationID uuid.UUID, feature
8491

8592
func (s *FeatureFlagStorage) IsFeatureEnabled(organizationID uuid.UUID, featureName string) (bool, error) {
8693
var isEnabled bool
87-
err := s.DB.NewSelect().
94+
err := s.getDB().NewSelect().
8895
TableExpr("feature_flags").
8996
Column("is_enabled").
9097
Where("organization_id = ?", organizationID).
@@ -103,7 +110,7 @@ func (s *FeatureFlagStorage) IsFeatureEnabled(organizationID uuid.UUID, featureN
103110
}
104111

105112
func (s *FeatureFlagStorage) CreateFeatureFlag(featureFlag types.FeatureFlag) error {
106-
_, err := s.DB.NewInsert().Model(&featureFlag).Exec(s.Ctx)
113+
_, err := s.getDB().NewInsert().Model(&featureFlag).Exec(s.Ctx)
107114
return err
108115
}
109116

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package tests
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/google/uuid"
8+
"github.com/raghavyuva/nixopus-api/internal/features/feature-flags/storage"
9+
"github.com/raghavyuva/nixopus-api/internal/testutils"
10+
"github.com/raghavyuva/nixopus-api/internal/types"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestFeatureFlagStorage(t *testing.T) {
15+
t.Run("should create and get feature flag", func(t *testing.T) {
16+
setup := testutils.NewTestSetup()
17+
featureStorage := &storage.FeatureFlagStorage{DB: setup.DB, Ctx: setup.Ctx}
18+
19+
_, org, err := setup.CreateTestUserAndOrg()
20+
assert.NoError(t, err)
21+
22+
featureFlag := types.FeatureFlag{
23+
ID: uuid.New(),
24+
OrganizationID: org.ID,
25+
FeatureName: string(types.FeatureDomain),
26+
IsEnabled: true,
27+
CreatedAt: time.Now(),
28+
UpdatedAt: time.Now(),
29+
}
30+
31+
err = featureStorage.CreateFeatureFlag(featureFlag)
32+
assert.NoError(t, err)
33+
34+
flags, err := featureStorage.GetFeatureFlags(org.ID)
35+
assert.NoError(t, err)
36+
assert.Len(t, flags, 1)
37+
assert.Equal(t, featureFlag.FeatureName, flags[0].FeatureName)
38+
assert.Equal(t, featureFlag.IsEnabled, flags[0].IsEnabled)
39+
})
40+
41+
t.Run("should update existing feature flag", func(t *testing.T) {
42+
setup := testutils.NewTestSetup()
43+
featureStorage := &storage.FeatureFlagStorage{DB: setup.DB, Ctx: setup.Ctx}
44+
45+
_, org, err := setup.CreateTestUserAndOrg()
46+
assert.NoError(t, err)
47+
48+
featureFlag := types.FeatureFlag{
49+
ID: uuid.New(),
50+
OrganizationID: org.ID,
51+
FeatureName: string(types.FeatureDomain),
52+
IsEnabled: true,
53+
CreatedAt: time.Now(),
54+
UpdatedAt: time.Now(),
55+
}
56+
57+
err = featureStorage.CreateFeatureFlag(featureFlag)
58+
assert.NoError(t, err)
59+
60+
err = featureStorage.UpdateFeatureFlag(org.ID, string(types.FeatureDomain), false)
61+
assert.NoError(t, err)
62+
63+
isEnabled, err := featureStorage.IsFeatureEnabled(org.ID, string(types.FeatureDomain))
64+
assert.NoError(t, err)
65+
assert.False(t, isEnabled)
66+
})
67+
68+
t.Run("should create new feature flag on update if not exists", func(t *testing.T) {
69+
setup := testutils.NewTestSetup()
70+
featureStorage := &storage.FeatureFlagStorage{DB: setup.DB, Ctx: setup.Ctx}
71+
72+
_, org, err := setup.CreateTestUserAndOrg()
73+
assert.NoError(t, err)
74+
75+
err = featureStorage.UpdateFeatureFlag(org.ID, string(types.FeatureDomain), true)
76+
assert.NoError(t, err)
77+
78+
flags, err := featureStorage.GetFeatureFlags(org.ID)
79+
assert.NoError(t, err)
80+
assert.Len(t, flags, 1)
81+
assert.Equal(t, string(types.FeatureDomain), flags[0].FeatureName)
82+
assert.True(t, flags[0].IsEnabled)
83+
})
84+
85+
t.Run("should handle transaction operations", func(t *testing.T) {
86+
setup := testutils.NewTestSetup()
87+
featureStorage := &storage.FeatureFlagStorage{DB: setup.DB, Ctx: setup.Ctx}
88+
89+
_, org, err := setup.CreateTestUserAndOrg()
90+
assert.NoError(t, err)
91+
92+
tx, err := featureStorage.BeginTx()
93+
assert.NoError(t, err)
94+
defer tx.Rollback()
95+
96+
txStorage := featureStorage.WithTx(tx)
97+
98+
err = txStorage.UpdateFeatureFlag(org.ID, string(types.FeatureDomain), true)
99+
assert.NoError(t, err)
100+
101+
err = txStorage.UpdateFeatureFlag(org.ID, string(types.FeatureTerminal), false)
102+
assert.NoError(t, err)
103+
104+
err = tx.Commit()
105+
assert.NoError(t, err)
106+
107+
flags, err := featureStorage.GetFeatureFlags(org.ID)
108+
assert.NoError(t, err)
109+
assert.Len(t, flags, 2)
110+
111+
domainEnabled, err := featureStorage.IsFeatureEnabled(org.ID, string(types.FeatureDomain))
112+
assert.NoError(t, err)
113+
assert.True(t, domainEnabled)
114+
115+
terminalEnabled, err := featureStorage.IsFeatureEnabled(org.ID, string(types.FeatureTerminal))
116+
assert.NoError(t, err)
117+
assert.True(t, terminalEnabled)
118+
})
119+
120+
t.Run("should rollback transaction on error", func(t *testing.T) {
121+
setup := testutils.NewTestSetup()
122+
featureStorage := &storage.FeatureFlagStorage{DB: setup.DB, Ctx: setup.Ctx}
123+
124+
_, org, err := setup.CreateTestUserAndOrg()
125+
assert.NoError(t, err)
126+
127+
tx, err := featureStorage.BeginTx()
128+
assert.NoError(t, err)
129+
defer tx.Rollback()
130+
131+
txStorage := featureStorage.WithTx(tx)
132+
133+
err = txStorage.UpdateFeatureFlag(org.ID, string(types.FeatureDomain), true)
134+
assert.NoError(t, err)
135+
136+
err = txStorage.UpdateFeatureFlag(uuid.Nil, string(types.FeatureTerminal), false)
137+
assert.Error(t, err)
138+
139+
// Transaction should be rolled back
140+
flags, err := featureStorage.GetFeatureFlags(org.ID)
141+
assert.NoError(t, err)
142+
assert.Empty(t, flags)
143+
})
144+
}

0 commit comments

Comments
 (0)