Skip to content

Commit 75c615c

Browse files
committed
refactor: split models.Workspace into server and CLI types
Define store.LocalWorkspace for CLI-side workspace tracking, removing the coupling between the server database model and the CLI's local SQLite store. Each type now only contains fields relevant to its database, eliminating phantom columns and the NOT NULL OwnerID foreign key that referenced a non-existent users table in the CLI DB.
1 parent 2202786 commit 75c615c

File tree

10 files changed

+68
-43
lines changed

10 files changed

+68
-43
lines changed

cmd/nebi/client.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"time"
1010

1111
"github.com/nebari-dev/nebi/internal/cliclient"
12-
"github.com/nebari-dev/nebi/internal/models"
1312
"github.com/nebari-dev/nebi/internal/store"
1413
)
1514

@@ -76,7 +75,7 @@ func validateWorkspaceName(name string) error {
7675

7776
// lookupOrigin returns the origin fields for the current working directory workspace.
7877
// Returns nil (no error) if no workspace is tracked or no origin is set.
79-
func lookupOrigin() (*models.Workspace, error) {
78+
func lookupOrigin() (*store.LocalWorkspace, error) {
8079
cwd, err := os.Getwd()
8180
if err != nil {
8281
return nil, fmt.Errorf("getting working directory: %w", err)

cmd/nebi/init.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"os/exec"
77
"path/filepath"
88

9-
"github.com/nebari-dev/nebi/internal/models"
109
"github.com/nebari-dev/nebi/internal/store"
1110
"github.com/spf13/cobra"
1211
)
@@ -57,7 +56,7 @@ func runInit(cmd *cobra.Command, args []string) error {
5756
}
5857

5958
name := filepath.Base(cwd)
60-
ws := &models.Workspace{
59+
ws := &store.LocalWorkspace{
6160
Name: name,
6261
Path: cwd,
6362
}
@@ -95,7 +94,7 @@ func ensureInit(dir string) error {
9594
}
9695

9796
name := filepath.Base(absDir)
98-
ws := &models.Workspace{
97+
ws := &store.LocalWorkspace{
9998
Name: name,
10099
Path: absDir,
101100
}

cmd/nebi/pull.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"path/filepath"
99
"strings"
1010

11-
"github.com/nebari-dev/nebi/internal/models"
1211
"github.com/nebari-dev/nebi/internal/store"
1312
"github.com/spf13/cobra"
1413
)
@@ -238,7 +237,7 @@ func setupGlobalWorkspace(name string, force bool) (string, error) {
238237
}
239238

240239
wsDir := s.GlobalWorkspaceDir(name)
241-
ws := &models.Workspace{
240+
ws := &store.LocalWorkspace{
242241
Name: name,
243242
Path: wsDir,
244243
IsGlobal: true,

cmd/nebi/status.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"path/filepath"
99

1010
"github.com/nebari-dev/nebi/internal/cliclient"
11-
"github.com/nebari-dev/nebi/internal/models"
1211
"github.com/nebari-dev/nebi/internal/store"
1312
"github.com/spf13/cobra"
1413
)
@@ -102,7 +101,7 @@ func runStatus(cmd *cobra.Command, args []string) error {
102101
return nil
103102
}
104103

105-
func checkServerOrigin(s *store.Store, serverURL string, ws *models.Workspace) string {
104+
func checkServerOrigin(s *store.Store, serverURL string, ws *store.LocalWorkspace) string {
106105
creds, err := s.LoadCredentials()
107106
if err != nil || creds.Token == "" {
108107
return "Not logged in"

cmd/nebi/workspace.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"strings"
99
"text/tabwriter"
1010

11-
"github.com/nebari-dev/nebi/internal/models"
1211
"github.com/nebari-dev/nebi/internal/store"
1312
"github.com/spf13/cobra"
1413
)
@@ -290,7 +289,7 @@ func runWorkspacePromote(cmd *cobra.Command, args []string) error {
290289
}
291290
}
292291

293-
ws := &models.Workspace{
292+
ws := &store.LocalWorkspace{
294293
Name: name,
295294
Path: wsDir,
296295
IsGlobal: true,
@@ -338,7 +337,7 @@ func runWorkspaceRemoveLocal(arg string) error {
338337
}
339338
defer s.Close()
340339

341-
var ws *models.Workspace
340+
var ws *store.LocalWorkspace
342341
if strings.Contains(arg, "/") || strings.Contains(arg, string(filepath.Separator)) {
343342
absPath, err := filepath.Abs(arg)
344343
if err != nil {

internal/models/workspace.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@ type Workspace struct {
2727
Status WorkspaceStatus `gorm:"not null;default:'pending'" json:"status"`
2828
PackageManager string `gorm:"not null" json:"package_manager"` // "pixi" or "uv"
2929
SizeBytes int64 `gorm:"default:0" json:"size_bytes"`
30-
Path string `gorm:"" json:"path,omitempty"`
31-
Source string `gorm:"default:'managed'" json:"source"` // "local", "managed"
32-
IsGlobal bool `gorm:"default:false" json:"is_global,omitempty"`
33-
OriginName string `json:"origin_name,omitempty"`
34-
OriginTag string `json:"origin_tag,omitempty"`
35-
OriginAction string `json:"origin_action,omitempty"`
36-
OriginTomlHash string `json:"origin_toml_hash,omitempty"`
37-
OriginLockHash string `json:"origin_lock_hash,omitempty"`
3830
CreatedAt time.Time `json:"created_at"`
3931
UpdatedAt time.Time `json:"updated_at"`
4032
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`

internal/store/local_workspace.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package store
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
"gorm.io/gorm"
8+
)
9+
10+
// LocalWorkspace represents a workspace tracked by the CLI in its local SQLite database.
11+
// This is separate from models.Workspace which is used by the server.
12+
type LocalWorkspace struct {
13+
ID uuid.UUID `gorm:"type:text;primary_key" json:"id"`
14+
Name string `gorm:"not null" json:"name"`
15+
Status string `gorm:"not null;default:'ready'" json:"status"`
16+
PackageManager string `gorm:"not null" json:"package_manager"`
17+
Path string `gorm:"" json:"path,omitempty"`
18+
Source string `gorm:"default:'managed'" json:"source"`
19+
IsGlobal bool `gorm:"default:false" json:"is_global,omitempty"`
20+
OriginName string `json:"origin_name,omitempty"`
21+
OriginTag string `json:"origin_tag,omitempty"`
22+
OriginAction string `json:"origin_action,omitempty"`
23+
OriginTomlHash string `json:"origin_toml_hash,omitempty"`
24+
OriginLockHash string `json:"origin_lock_hash,omitempty"`
25+
CreatedAt time.Time `json:"created_at"`
26+
UpdatedAt time.Time `json:"updated_at"`
27+
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
28+
}
29+
30+
// TableName ensures GORM uses the "workspaces" table.
31+
func (LocalWorkspace) TableName() string {
32+
return "workspaces"
33+
}
34+
35+
// BeforeCreate hook to generate UUID.
36+
func (w *LocalWorkspace) BeforeCreate(tx *gorm.DB) error {
37+
if w.ID == uuid.Nil {
38+
w.ID = uuid.New()
39+
}
40+
return nil
41+
}

internal/store/store.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"runtime"
88

99
"github.com/glebarez/sqlite"
10-
"github.com/nebari-dev/nebi/internal/models"
1110
"gorm.io/gorm"
1211
"gorm.io/gorm/logger"
1312
)
@@ -45,7 +44,7 @@ func Open(dataDir string) (*Store, error) {
4544
db.Exec("PRAGMA journal_mode=WAL")
4645

4746
// AutoMigrate workspace + config/credentials tables
48-
if err := db.AutoMigrate(&models.Workspace{}, &Config{}, &Credentials{}); err != nil {
47+
if err := db.AutoMigrate(&LocalWorkspace{}, &Config{}, &Credentials{}); err != nil {
4948
return nil, fmt.Errorf("migrating schema: %w", err)
5049
}
5150

internal/store/store_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"testing"
66

77
"github.com/google/uuid"
8-
"github.com/nebari-dev/nebi/internal/models"
98
)
109

1110
func testStore(t *testing.T) *Store {
@@ -31,7 +30,7 @@ func TestWorkspaceRoundTrip(t *testing.T) {
3130
}
3231

3332
// Create
34-
ws := &models.Workspace{
33+
ws := &LocalWorkspace{
3534
Name: "project",
3635
Path: "/home/user/project",
3736
}
@@ -92,7 +91,7 @@ func TestGlobalWorkspace(t *testing.T) {
9291
s := testStore(t)
9392

9493
wsDir := s.GlobalWorkspaceDir("test-uuid-123")
95-
ws := &models.Workspace{
94+
ws := &LocalWorkspace{
9695
Name: "data-science",
9796
Path: wsDir,
9897
IsGlobal: true,
@@ -116,7 +115,7 @@ func TestGlobalWorkspace(t *testing.T) {
116115
func TestOriginFields(t *testing.T) {
117116
s := testStore(t)
118117

119-
ws := &models.Workspace{
118+
ws := &LocalWorkspace{
120119
Name: "project",
121120
Path: "/home/user/project",
122121
OriginName: "my-env",
@@ -197,14 +196,14 @@ func TestGlobalWorkspaceDir(t *testing.T) {
197196
func TestDefaults(t *testing.T) {
198197
s := testStore(t)
199198

200-
ws := &models.Workspace{
199+
ws := &LocalWorkspace{
201200
Name: "test",
202201
Path: "/tmp/test",
203202
}
204203
s.CreateWorkspace(ws)
205204

206205
got, _ := s.GetWorkspace(ws.ID)
207-
if got.Status != models.WsStatusReady {
206+
if got.Status != "ready" {
208207
t.Errorf("expected status 'ready', got %q", got.Status)
209208
}
210209
if got.Source != "local" {

internal/store/workspace.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,29 @@ import (
44
"fmt"
55

66
"github.com/google/uuid"
7-
"github.com/nebari-dev/nebi/internal/models"
87
)
98

109
// ListWorkspaces returns all workspaces.
11-
func (s *Store) ListWorkspaces() ([]models.Workspace, error) {
12-
var wss []models.Workspace
10+
func (s *Store) ListWorkspaces() ([]LocalWorkspace, error) {
11+
var wss []LocalWorkspace
1312
if err := s.db.Find(&wss).Error; err != nil {
1413
return nil, fmt.Errorf("listing workspaces: %w", err)
1514
}
1615
return wss, nil
1716
}
1817

1918
// GetWorkspace returns a workspace by ID.
20-
func (s *Store) GetWorkspace(id uuid.UUID) (*models.Workspace, error) {
21-
var ws models.Workspace
19+
func (s *Store) GetWorkspace(id uuid.UUID) (*LocalWorkspace, error) {
20+
var ws LocalWorkspace
2221
if err := s.db.Where("id = ?", id).First(&ws).Error; err != nil {
2322
return nil, fmt.Errorf("getting workspace: %w", err)
2423
}
2524
return &ws, nil
2625
}
2726

2827
// FindWorkspaceByPath returns the workspace at the given path, or nil if not found.
29-
func (s *Store) FindWorkspaceByPath(path string) (*models.Workspace, error) {
30-
var ws models.Workspace
28+
func (s *Store) FindWorkspaceByPath(path string) (*LocalWorkspace, error) {
29+
var ws LocalWorkspace
3130
result := s.db.Where("path = ?", path).First(&ws)
3231
if result.Error != nil {
3332
if result.RowsAffected == 0 {
@@ -39,8 +38,8 @@ func (s *Store) FindWorkspaceByPath(path string) (*models.Workspace, error) {
3938
}
4039

4140
// FindWorkspaceByName returns the first workspace with the given name, or nil if not found.
42-
func (s *Store) FindWorkspaceByName(name string) (*models.Workspace, error) {
43-
var ws models.Workspace
41+
func (s *Store) FindWorkspaceByName(name string) (*LocalWorkspace, error) {
42+
var ws LocalWorkspace
4443
result := s.db.Where("name = ?", name).First(&ws)
4544
if result.Error != nil {
4645
if result.RowsAffected == 0 {
@@ -52,8 +51,8 @@ func (s *Store) FindWorkspaceByName(name string) (*models.Workspace, error) {
5251
}
5352

5453
// FindGlobalWorkspaceByName returns the first global workspace with the given name.
55-
func (s *Store) FindGlobalWorkspaceByName(name string) (*models.Workspace, error) {
56-
var ws models.Workspace
54+
func (s *Store) FindGlobalWorkspaceByName(name string) (*LocalWorkspace, error) {
55+
var ws LocalWorkspace
5756
result := s.db.Where("name = ? AND is_global = ?", name, true).First(&ws)
5857
if result.Error != nil {
5958
if result.RowsAffected == 0 {
@@ -65,12 +64,12 @@ func (s *Store) FindGlobalWorkspaceByName(name string) (*models.Workspace, error
6564
}
6665

6766
// CreateWorkspace creates a new workspace record.
68-
func (s *Store) CreateWorkspace(ws *models.Workspace) error {
67+
func (s *Store) CreateWorkspace(ws *LocalWorkspace) error {
6968
if ws.ID == uuid.Nil {
7069
ws.ID = uuid.New()
7170
}
7271
if ws.Status == "" {
73-
ws.Status = models.WsStatusReady
72+
ws.Status = "ready"
7473
}
7574
if ws.Source == "" {
7675
ws.Source = "local"
@@ -82,11 +81,11 @@ func (s *Store) CreateWorkspace(ws *models.Workspace) error {
8281
}
8382

8483
// SaveWorkspace updates an existing workspace record.
85-
func (s *Store) SaveWorkspace(ws *models.Workspace) error {
84+
func (s *Store) SaveWorkspace(ws *LocalWorkspace) error {
8685
return s.db.Save(ws).Error
8786
}
8887

8988
// DeleteWorkspace removes a workspace by ID (hard delete).
9089
func (s *Store) DeleteWorkspace(id uuid.UUID) error {
91-
return s.db.Unscoped().Where("id = ?", id).Delete(&models.Workspace{}).Error
90+
return s.db.Unscoped().Where("id = ?", id).Delete(&LocalWorkspace{}).Error
9291
}

0 commit comments

Comments
 (0)