From 2d2b596f2ca29d8b194494ba5749348f5ed25aaf Mon Sep 17 00:00:00 2001 From: LordMathis Date: Mon, 14 Oct 2024 21:08:37 +0200 Subject: [PATCH] Add db support for workspaces --- backend/internal/db/migrations.go | 47 +++++++++++++++++++++++++++ backend/internal/db/settings.go | 28 ++++++++-------- backend/internal/db/user.go | 31 ++++++++++++++++++ backend/internal/db/workspace.go | 48 ++++++++++++++++++++++++++++ backend/internal/filesystem/paths.go | 29 +++++++++++++++++ backend/internal/models/settings.go | 34 +++++++------------- backend/internal/models/user.go | 17 ++++++++++ backend/internal/models/workspace.go | 16 ++++++++++ 8 files changed, 212 insertions(+), 38 deletions(-) create mode 100644 backend/internal/db/user.go create mode 100644 backend/internal/db/workspace.go create mode 100644 backend/internal/filesystem/paths.go create mode 100644 backend/internal/models/user.go create mode 100644 backend/internal/models/workspace.go diff --git a/backend/internal/db/migrations.go b/backend/internal/db/migrations.go index 1973da6..c70a2f5 100644 --- a/backend/internal/db/migrations.go +++ b/backend/internal/db/migrations.go @@ -18,6 +18,53 @@ var migrations = []Migration{ settings JSON NOT NULL )`, }, + { + Version: 2, + SQL: ` + -- Create users table + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create workspaces table + CREATE TABLE IF NOT EXISTS workspaces ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (id) + ); + + -- Create workspace_settings table + CREATE TABLE IF NOT EXISTS workspace_settings ( + workspace_id INTEGER PRIMARY KEY, + settings JSON NOT NULL, + FOREIGN KEY (workspace_id) REFERENCES workspaces (id) + ); + + -- Migrate existing settings to a default user and workspace + INSERT INTO users (username, email, password_hash) + VALUES ('default_user', 'default@example.com', 'placeholder_hash'); + + INSERT INTO workspaces (user_id, name, root_path) + SELECT 1, 'Default Workspace' + WHERE NOT EXISTS (SELECT 1 FROM workspaces); + + INSERT INTO workspace_settings (workspace_id, settings) + SELECT w.id, s.settings + FROM workspaces w + CROSS JOIN settings s + WHERE w.name = 'Default Workspace' + AND NOT EXISTS (SELECT 1 FROM workspace_settings); + + -- Drop the old settings table + DROP TABLE IF EXISTS settings; + `, + }, } func (db *DB) Migrate() error { diff --git a/backend/internal/db/settings.go b/backend/internal/db/settings.go index f0da94a..00820b6 100644 --- a/backend/internal/db/settings.go +++ b/backend/internal/db/settings.go @@ -7,39 +7,37 @@ import ( "novamd/internal/models" ) -func (db *DB) GetSettings(userID int) (models.Settings, error) { - var settings models.Settings +func (db *DB) GetWorkspaceSettings(workspaceID int) (*models.WorkspaceSettings, error) { + var settings models.WorkspaceSettings var settingsJSON []byte - err := db.QueryRow("SELECT user_id, settings FROM settings WHERE user_id = ?", userID).Scan(&settings.UserID, &settingsJSON) + err := db.QueryRow("SELECT workspace_id, settings FROM workspace_settings WHERE workspace_id = ?", workspaceID). + Scan(&settings.WorkspaceID, &settingsJSON) if err != nil { if err == sql.ErrNoRows { // If no settings found, return default settings - settings.UserID = userID + settings.WorkspaceID = workspaceID settings.Settings = models.UserSettings{} // This will be filled with defaults later - return settings, nil + return &settings, nil } - return settings, err + return nil, err } err = json.Unmarshal(settingsJSON, &settings.Settings) if err != nil { - return settings, err + return nil, err } - return settings, nil + return &settings, nil } -func (db *DB) SaveSettings(settings models.Settings) error { - if err := settings.Validate(); err != nil { - return err - } - +func (db *DB) SaveWorkspaceSettings(settings *models.WorkspaceSettings) error { settingsJSON, err := json.Marshal(settings.Settings) if err != nil { return err } - _, err = db.Exec("INSERT OR REPLACE INTO settings (user_id, settings) VALUES (?, json(?))", settings.UserID, string(settingsJSON)) + _, err = db.Exec("INSERT OR REPLACE INTO workspace_settings (workspace_id, settings) VALUES (?, ?)", + settings.WorkspaceID, settingsJSON) return err -} +} \ No newline at end of file diff --git a/backend/internal/db/user.go b/backend/internal/db/user.go new file mode 100644 index 0000000..610d07d --- /dev/null +++ b/backend/internal/db/user.go @@ -0,0 +1,31 @@ +package db + +import ( + "novamd/internal/models" +) + +func (db *DB) CreateUser(user *models.User) error { + _, err := db.Exec("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)", + user.Username, user.Email, user.PasswordHash) + return err +} + +func (db *DB) GetUserByID(id int) (*models.User, error) { + user := &models.User{} + err := db.QueryRow("SELECT id, username, email, created_at FROM users WHERE id = ?", id). + Scan(&user.ID, &user.Username, &user.Email, &user.CreatedAt) + if err != nil { + return nil, err + } + return user, nil +} + +func (db *DB) GetUserByUsername(username string) (*models.User, error) { + user := &models.User{} + err := db.QueryRow("SELECT id, username, email, password_hash, created_at FROM users WHERE username = ?", username). + Scan(&user.ID, &user.Username, &user.Email, &user.PasswordHash, &user.CreatedAt) + if err != nil { + return nil, err + } + return user, nil +} \ No newline at end of file diff --git a/backend/internal/db/workspace.go b/backend/internal/db/workspace.go new file mode 100644 index 0000000..0e8f3d4 --- /dev/null +++ b/backend/internal/db/workspace.go @@ -0,0 +1,48 @@ +package db + +import ( + "novamd/internal/models" +) + +func (db *DB) CreateWorkspace(workspace *models.Workspace) error { + result, err := db.Exec("INSERT INTO workspaces (user_id, name) VALUES (?, ?, ?)", + workspace.UserID, workspace.Name) + if err != nil { + return err + } + id, err := result.LastInsertId() + if err != nil { + return err + } + workspace.ID = int(id) + return nil +} + +func (db *DB) GetWorkspaceByID(id int) (*models.Workspace, error) { + workspace := &models.Workspace{} + err := db.QueryRow("SELECT id, user_id, name, root_path, created_at FROM workspaces WHERE id = ?", id). + Scan(&workspace.ID, &workspace.UserID, &workspace.Name, &workspace.CreatedAt) + if err != nil { + return nil, err + } + return workspace, nil +} + +func (db *DB) GetWorkspacesByUserID(userID int) ([]*models.Workspace, error) { + rows, err := db.Query("SELECT id, user_id, name, root_path, created_at FROM workspaces WHERE user_id = ?", userID) + if err != nil { + return nil, err + } + defer rows.Close() + + var workspaces []*models.Workspace + for rows.Next() { + workspace := &models.Workspace{} + err := rows.Scan(&workspace.ID, &workspace.UserID, &workspace.Name, &workspace.CreatedAt) + if err != nil { + return nil, err + } + workspaces = append(workspaces, workspace) + } + return workspaces, nil +} \ No newline at end of file diff --git a/backend/internal/filesystem/paths.go b/backend/internal/filesystem/paths.go new file mode 100644 index 0000000..fa0762e --- /dev/null +++ b/backend/internal/filesystem/paths.go @@ -0,0 +1,29 @@ +package filesystem + +import ( + "fmt" + "os" + "path/filepath" + + "novamd/internal/models" +) + +// GetWorkspacePath returns the file system path for a given workspace +func GetWorkspacePath(workspace *models.Workspace) string { + baseDir := os.Getenv("NOVAMD_WORKDIR") + if baseDir == "" { + baseDir = "./data" // Default if not set + } + return filepath.Join(baseDir, fmt.Sprintf("%d", workspace.UserID), workspace.Name) +} + +// GetFilePath returns the file system path for a given file within a workspace +func GetFilePath(workspace *models.Workspace, relativeFilePath string) string { + return filepath.Join(GetWorkspacePath(workspace), relativeFilePath) +} + +// EnsureWorkspaceDirectory creates the workspace directory if it doesn't exist +func EnsureWorkspaceDirectory(workspace *models.Workspace) error { + dir := GetWorkspacePath(workspace) + return os.MkdirAll(dir, 0755) +} \ No newline at end of file diff --git a/backend/internal/models/settings.go b/backend/internal/models/settings.go index 602bd45..52a15cb 100644 --- a/backend/internal/models/settings.go +++ b/backend/internal/models/settings.go @@ -17,42 +17,30 @@ type UserSettings struct { GitCommitMsgTemplate string `json:"gitCommitMsgTemplate"` } -type Settings struct { - UserID int `json:"userId" validate:"required,min=1"` - Settings UserSettings `json:"settings" validate:"required"` -} - -var defaultUserSettings = UserSettings{ - Theme: "light", - AutoSave: false, - GitEnabled: false, - GitCommitMsgTemplate: "Update ${filename}", +type WorkspaceSettings struct { + WorkspaceID int `json:"workspaceId" validate:"required,min=1"` + Settings UserSettings `json:"settings" validate:"required"` } var validate = validator.New() -func (s *Settings) Validate() error { +func (s *UserSettings) Validate() error { return validate.Struct(s) } -func (s *Settings) SetDefaults() { - if s.Settings.Theme == "" { - s.Settings.Theme = defaultUserSettings.Theme - } - if s.Settings.GitCommitMsgTemplate == "" { - s.Settings.GitCommitMsgTemplate = defaultUserSettings.GitCommitMsgTemplate - } +func (ws *WorkspaceSettings) Validate() error { + return validate.Struct(ws) } -func (s *Settings) UnmarshalJSON(data []byte) error { - type Alias Settings +func (ws *WorkspaceSettings) UnmarshalJSON(data []byte) error { + type Alias WorkspaceSettings aux := &struct { *Alias }{ - Alias: (*Alias)(s), + Alias: (*Alias)(ws), } if err := json.Unmarshal(data, &aux); err != nil { return err } - return s.Validate() -} + return ws.Validate() +} \ No newline at end of file diff --git a/backend/internal/models/user.go b/backend/internal/models/user.go new file mode 100644 index 0000000..9283327 --- /dev/null +++ b/backend/internal/models/user.go @@ -0,0 +1,17 @@ +package models + +import ( + "time" +) + +type User struct { + ID int `json:"id" validate:"required,min=1"` + Username string `json:"username" validate:"required"` + Email string `json:"email" validate:"required,email"` + PasswordHash string `json:"-"` + CreatedAt time.Time `json:"createdAt"` +} + +func (u *User) Validate() error { + return validate.Struct(u) +} \ No newline at end of file diff --git a/backend/internal/models/workspace.go b/backend/internal/models/workspace.go new file mode 100644 index 0000000..ced4816 --- /dev/null +++ b/backend/internal/models/workspace.go @@ -0,0 +1,16 @@ +package models + +import ( + "time" +) + +type Workspace struct { + ID int `json:"id" validate:"required,min=1"` + UserID int `json:"userId" validate:"required,min=1"` + Name string `json:"name" validate:"required"` + CreatedAt time.Time `json:"createdAt"` +} + +func (w *Workspace) Validate() error { + return validate.Struct(w) +}