mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-07 16:34:26 +00:00
Merge pull request #22 from LordMathis/fix/missing-git-email
Fix missing commit author name and email
This commit is contained in:
@@ -16,13 +16,15 @@ const GitSettings = ({
|
|||||||
gitToken,
|
gitToken,
|
||||||
gitAutoCommit,
|
gitAutoCommit,
|
||||||
gitCommitMsgTemplate,
|
gitCommitMsgTemplate,
|
||||||
|
gitCommitName,
|
||||||
|
gitCommitEmail,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing="md">
|
<Stack spacing="md">
|
||||||
<Grid gutter="md" align="center">
|
<Grid gutter="md" align="center">
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Text size="sm">Enable Git</Text>
|
<Text size="sm">Enable Git Repository</Text>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Group justify="flex-end">
|
<Group justify="flex-end">
|
||||||
@@ -41,6 +43,7 @@ const GitSettings = ({
|
|||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={gitUrl}
|
value={gitUrl}
|
||||||
|
description="The URL of your Git repository"
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onInputChange('gitUrl', event.currentTarget.value)
|
onInputChange('gitUrl', event.currentTarget.value)
|
||||||
}
|
}
|
||||||
@@ -50,11 +53,12 @@ const GitSettings = ({
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Text size="sm">Git Username</Text>
|
<Text size="sm">Username</Text>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={gitUser}
|
value={gitUser}
|
||||||
|
description="The username used to authenticate with the repository"
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onInputChange('gitUser', event.currentTarget.value)
|
onInputChange('gitUser', event.currentTarget.value)
|
||||||
}
|
}
|
||||||
@@ -64,11 +68,12 @@ const GitSettings = ({
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Text size="sm">Git Token</Text>
|
<Text size="sm">Access Token</Text>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
value={gitToken}
|
value={gitToken}
|
||||||
|
description="Personal access token with repository read/write permissions"
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onInputChange('gitToken', event.currentTarget.value)
|
onInputChange('gitToken', event.currentTarget.value)
|
||||||
}
|
}
|
||||||
@@ -78,7 +83,7 @@ const GitSettings = ({
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Text size="sm">Auto Commit</Text>
|
<Text size="sm">Commit on Save</Text>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<Group justify="flex-end">
|
<Group justify="flex-end">
|
||||||
@@ -98,6 +103,7 @@ const GitSettings = ({
|
|||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={gitCommitMsgTemplate}
|
value={gitCommitMsgTemplate}
|
||||||
|
description="Template for automated commit messages. Use ${filename} and ${action} as a placeholder."
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
onInputChange('gitCommitMsgTemplate', event.currentTarget.value)
|
onInputChange('gitCommitMsgTemplate', event.currentTarget.value)
|
||||||
}
|
}
|
||||||
@@ -105,6 +111,36 @@ const GitSettings = ({
|
|||||||
placeholder="Enter commit message template"
|
placeholder="Enter commit message template"
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<Text size="sm">Commit Author</Text>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<TextInput
|
||||||
|
value={gitCommitName}
|
||||||
|
description="Name to appear in commit history. Leave empty to use Git username."
|
||||||
|
onChange={(event) =>
|
||||||
|
onInputChange('gitCommitName', event.currentTarget.value)
|
||||||
|
}
|
||||||
|
disabled={!gitEnabled}
|
||||||
|
placeholder="Enter commit author name."
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<Text size="sm">Commit Author Email</Text>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<TextInput
|
||||||
|
value={gitCommitEmail}
|
||||||
|
description="Email address to associate with your commits"
|
||||||
|
onChange={(event) =>
|
||||||
|
onInputChange('gitCommitEmail', event.currentTarget.value)
|
||||||
|
}
|
||||||
|
disabled={!gitEnabled}
|
||||||
|
placeholder="Enter commit author email."
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ const WorkspaceSettings = () => {
|
|||||||
gitToken: currentWorkspace.gitToken,
|
gitToken: currentWorkspace.gitToken,
|
||||||
gitAutoCommit: currentWorkspace.gitAutoCommit,
|
gitAutoCommit: currentWorkspace.gitAutoCommit,
|
||||||
gitCommitMsgTemplate: currentWorkspace.gitCommitMsgTemplate,
|
gitCommitMsgTemplate: currentWorkspace.gitCommitMsgTemplate,
|
||||||
|
gitCommitName: currentWorkspace.gitCommitName,
|
||||||
|
gitCommitEmail: currentWorkspace.gitCommitEmail,
|
||||||
};
|
};
|
||||||
dispatch({ type: 'INIT_SETTINGS', payload: settings });
|
dispatch({ type: 'INIT_SETTINGS', payload: settings });
|
||||||
}
|
}
|
||||||
@@ -204,6 +206,8 @@ const WorkspaceSettings = () => {
|
|||||||
gitToken={state.localSettings.gitToken}
|
gitToken={state.localSettings.gitToken}
|
||||||
gitAutoCommit={state.localSettings.gitAutoCommit}
|
gitAutoCommit={state.localSettings.gitAutoCommit}
|
||||||
gitCommitMsgTemplate={state.localSettings.gitCommitMsgTemplate}
|
gitCommitMsgTemplate={state.localSettings.gitCommitMsgTemplate}
|
||||||
|
gitCommitName={state.localSettings.gitCommitName}
|
||||||
|
gitCommitEmail={state.localSettings.gitCommitEmail}
|
||||||
onInputChange={handleInputChange}
|
onInputChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -12,12 +13,14 @@ import (
|
|||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/go-chi/httprate"
|
"github.com/go-chi/httprate"
|
||||||
"github.com/unrolled/secure"
|
"github.com/unrolled/secure"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"novamd/internal/api"
|
"novamd/internal/api"
|
||||||
"novamd/internal/auth"
|
"novamd/internal/auth"
|
||||||
"novamd/internal/config"
|
"novamd/internal/config"
|
||||||
"novamd/internal/db"
|
"novamd/internal/db"
|
||||||
"novamd/internal/handlers"
|
"novamd/internal/handlers"
|
||||||
|
"novamd/internal/models"
|
||||||
"novamd/internal/secrets"
|
"novamd/internal/secrets"
|
||||||
"novamd/internal/storage"
|
"novamd/internal/storage"
|
||||||
)
|
)
|
||||||
@@ -47,6 +50,12 @@ func NewServer(cfg *config.Config) (*Server, error) {
|
|||||||
// Initialize filesystem
|
// Initialize filesystem
|
||||||
storageManager := storage.NewService(cfg.WorkDir)
|
storageManager := storage.NewService(cfg.WorkDir)
|
||||||
|
|
||||||
|
// Setup admin user
|
||||||
|
err = setupAdminUser(database, storageManager, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to setup admin user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize router
|
// Initialize router
|
||||||
router := initRouter(cfg)
|
router := initRouter(cfg)
|
||||||
|
|
||||||
@@ -168,3 +177,47 @@ func (s *Server) setupRoutes(jwtManager auth.JWTManager, sessionService *auth.Se
|
|||||||
// Handle all other routes with static file server
|
// Handle all other routes with static file server
|
||||||
s.router.Get("/*", handlers.NewStaticHandler(s.config.StaticPath).ServeHTTP)
|
s.router.Get("/*", handlers.NewStaticHandler(s.config.StaticPath).ServeHTTP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupAdminUser(db db.Database, w storage.WorkspaceManager, cfg *config.Config) error {
|
||||||
|
|
||||||
|
adminEmail := cfg.AdminEmail
|
||||||
|
adminPassword := cfg.AdminPassword
|
||||||
|
|
||||||
|
// Check if admin user exists
|
||||||
|
adminUser, err := db.GetUserByEmail(adminEmail)
|
||||||
|
if adminUser != nil {
|
||||||
|
return nil // Admin user already exists
|
||||||
|
} else if err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the password
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to hash password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create admin user
|
||||||
|
adminUser = &models.User{
|
||||||
|
Email: adminEmail,
|
||||||
|
DisplayName: "Admin",
|
||||||
|
PasswordHash: string(hashedPassword),
|
||||||
|
Role: models.RoleAdmin,
|
||||||
|
}
|
||||||
|
|
||||||
|
createdUser, err := db.CreateUser(adminUser)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create admin user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize workspace directory
|
||||||
|
err = w.InitializeUserWorkspace(createdUser.ID, createdUser.LastWorkspaceID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize admin workspace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Created admin user with ID: %d and default workspace with ID: %d", createdUser.ID, createdUser.LastWorkspaceID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,70 +15,65 @@ var migrations = []Migration{
|
|||||||
{
|
{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
SQL: `
|
SQL: `
|
||||||
-- Create users table
|
-- Create users table
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
display_name TEXT,
|
display_name TEXT,
|
||||||
password_hash TEXT NOT NULL,
|
password_hash TEXT NOT NULL,
|
||||||
role TEXT NOT NULL CHECK(role IN ('admin', 'editor', 'viewer')),
|
role TEXT NOT NULL CHECK(role IN ('admin', 'editor', 'viewer')),
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
last_workspace_id INTEGER
|
last_workspace_id INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Create workspaces table with integrated settings
|
-- Create workspaces table with integrated settings
|
||||||
CREATE TABLE IF NOT EXISTS workspaces (
|
CREATE TABLE IF NOT EXISTS workspaces (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
last_opened_file_path TEXT,
|
last_opened_file_path TEXT,
|
||||||
-- Settings fields
|
-- Settings fields
|
||||||
theme TEXT NOT NULL DEFAULT 'light' CHECK(theme IN ('light', 'dark')),
|
theme TEXT NOT NULL DEFAULT 'light' CHECK(theme IN ('light', 'dark')),
|
||||||
auto_save BOOLEAN NOT NULL DEFAULT 0,
|
auto_save BOOLEAN NOT NULL DEFAULT 0,
|
||||||
git_enabled BOOLEAN NOT NULL DEFAULT 0,
|
git_enabled BOOLEAN NOT NULL DEFAULT 0,
|
||||||
git_url TEXT,
|
git_url TEXT,
|
||||||
git_user TEXT,
|
git_user TEXT,
|
||||||
git_token TEXT,
|
git_token TEXT,
|
||||||
git_auto_commit BOOLEAN NOT NULL DEFAULT 0,
|
git_auto_commit BOOLEAN NOT NULL DEFAULT 0,
|
||||||
git_commit_msg_template TEXT DEFAULT '${action} ${filename}',
|
git_commit_msg_template TEXT DEFAULT '${action} ${filename}',
|
||||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
git_commit_name TEXT,
|
||||||
);
|
git_commit_email TEXT,
|
||||||
`,
|
show_hidden_files BOOLEAN NOT NULL DEFAULT 0,
|
||||||
},
|
created_by INTEGER REFERENCES users(id),
|
||||||
{
|
updated_by INTEGER REFERENCES users(id),
|
||||||
Version: 2,
|
updated_at TIMESTAMP,
|
||||||
SQL: `
|
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||||
-- Create sessions table for authentication
|
);
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
refresh_token TEXT NOT NULL,
|
|
||||||
expires_at TIMESTAMP NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Add show_hidden_files field to workspaces
|
-- Create sessions table for authentication
|
||||||
ALTER TABLE workspaces ADD COLUMN show_hidden_files BOOLEAN NOT NULL DEFAULT 0;
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
refresh_token TEXT NOT NULL,
|
||||||
|
expires_at TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
-- Add indexes for performance
|
-- Create system_settings table for application settings
|
||||||
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
|
CREATE TABLE IF NOT EXISTS system_settings (
|
||||||
CREATE INDEX idx_sessions_expires_at ON sessions(expires_at);
|
key TEXT PRIMARY KEY,
|
||||||
CREATE INDEX idx_sessions_refresh_token ON sessions(refresh_token);
|
value TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
-- Add audit fields to workspaces
|
-- Create indexes for performance
|
||||||
ALTER TABLE workspaces ADD COLUMN created_by INTEGER REFERENCES users(id);
|
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
|
||||||
ALTER TABLE workspaces ADD COLUMN updated_by INTEGER REFERENCES users(id);
|
CREATE INDEX idx_sessions_expires_at ON sessions(expires_at);
|
||||||
ALTER TABLE workspaces ADD COLUMN updated_at TIMESTAMP;
|
CREATE INDEX idx_sessions_refresh_token ON sessions(refresh_token);
|
||||||
|
`,
|
||||||
-- Create system_settings table for application settings
|
|
||||||
CREATE TABLE IF NOT EXISTS system_settings (
|
|
||||||
key TEXT PRIMARY KEY,
|
|
||||||
value TEXT NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);`,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ func TestMigrate(t *testing.T) {
|
|||||||
t.Fatalf("failed to get migration version: %v", err)
|
t.Fatalf("failed to get migration version: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version != 2 { // Current number of migrations in production code
|
if version != 1 { // Current number of migrations in production code
|
||||||
t.Errorf("expected migration version 2, got %d", version)
|
t.Errorf("expected migration version 1, got %d", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify number of migration entries matches versions applied
|
// Verify number of migration entries matches versions applied
|
||||||
@@ -38,8 +38,8 @@ func TestMigrate(t *testing.T) {
|
|||||||
t.Fatalf("failed to count migrations: %v", err)
|
t.Fatalf("failed to count migrations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if count != 2 {
|
if count != 1 {
|
||||||
t.Errorf("expected 2 migration entries, got %d", count)
|
t.Errorf("expected 1 migration entries, got %d", count)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -82,8 +82,8 @@ func TestMigrate(t *testing.T) {
|
|||||||
t.Fatalf("failed to count migrations: %v", err)
|
t.Fatalf("failed to count migrations: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if count != 2 {
|
if count != 1 {
|
||||||
t.Errorf("expected 2 migration entries, got %d", count)
|
t.Errorf("expected 1 migration entries, got %d", count)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -118,8 +118,8 @@ func TestMigrate(t *testing.T) {
|
|||||||
t.Fatalf("failed to get migration version: %v", err)
|
t.Fatalf("failed to get migration version: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version != 2 {
|
if version != 1 {
|
||||||
t.Errorf("expected migration version to remain at 2, got %d", version)
|
t.Errorf("expected migration version to remain at 1, got %d", version)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ func (db *database) createWorkspaceTx(tx *sql.Tx, workspace *models.Workspace) e
|
|||||||
user_id, name,
|
user_id, name,
|
||||||
theme, auto_save, show_hidden_files,
|
theme, auto_save, show_hidden_files,
|
||||||
git_enabled, git_url, git_user, git_token,
|
git_enabled, git_url, git_user, git_token,
|
||||||
git_auto_commit, git_commit_msg_template
|
git_auto_commit, git_commit_msg_template,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
git_commit_name, git_commit_email
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
workspace.UserID, workspace.Name,
|
workspace.UserID, workspace.Name,
|
||||||
workspace.Theme, workspace.AutoSave, workspace.ShowHiddenFiles,
|
workspace.Theme, workspace.AutoSave, workspace.ShowHiddenFiles,
|
||||||
workspace.GitEnabled, workspace.GitURL, workspace.GitUser, workspace.GitToken,
|
workspace.GitEnabled, workspace.GitURL, workspace.GitUser, workspace.GitToken,
|
||||||
workspace.GitAutoCommit, workspace.GitCommitMsgTemplate,
|
workspace.GitAutoCommit, workspace.GitCommitMsgTemplate,
|
||||||
|
workspace.GitCommitName, workspace.GitCommitEmail,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ func (db *database) CreateWorkspace(workspace *models.Workspace) error {
|
|||||||
INSERT INTO workspaces (
|
INSERT INTO workspaces (
|
||||||
user_id, name, theme, auto_save, show_hidden_files,
|
user_id, name, theme, auto_save, show_hidden_files,
|
||||||
git_enabled, git_url, git_user, git_token,
|
git_enabled, git_url, git_user, git_token,
|
||||||
git_auto_commit, git_commit_msg_template
|
git_auto_commit, git_commit_msg_template,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
git_commit_name, git_commit_email
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
workspace.UserID, workspace.Name, workspace.Theme, workspace.AutoSave, workspace.ShowHiddenFiles,
|
workspace.UserID, workspace.Name, workspace.Theme, workspace.AutoSave, workspace.ShowHiddenFiles,
|
||||||
workspace.GitEnabled, workspace.GitURL, workspace.GitUser, encryptedToken,
|
workspace.GitEnabled, workspace.GitURL, workspace.GitUser, encryptedToken,
|
||||||
workspace.GitAutoCommit, workspace.GitCommitMsgTemplate,
|
workspace.GitAutoCommit, workspace.GitCommitMsgTemplate, workspace.GitCommitName, workspace.GitCommitEmail,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -51,7 +52,8 @@ func (db *database) GetWorkspaceByID(id int) (*models.Workspace, error) {
|
|||||||
id, user_id, name, created_at,
|
id, user_id, name, created_at,
|
||||||
theme, auto_save, show_hidden_files,
|
theme, auto_save, show_hidden_files,
|
||||||
git_enabled, git_url, git_user, git_token,
|
git_enabled, git_url, git_user, git_token,
|
||||||
git_auto_commit, git_commit_msg_template
|
git_auto_commit, git_commit_msg_template,
|
||||||
|
git_commit_name, git_commit_email
|
||||||
FROM workspaces
|
FROM workspaces
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
id,
|
id,
|
||||||
@@ -59,7 +61,7 @@ func (db *database) GetWorkspaceByID(id int) (*models.Workspace, error) {
|
|||||||
&workspace.ID, &workspace.UserID, &workspace.Name, &workspace.CreatedAt,
|
&workspace.ID, &workspace.UserID, &workspace.Name, &workspace.CreatedAt,
|
||||||
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
||||||
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
||||||
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate, &workspace.GitCommitName, &workspace.GitCommitEmail,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -84,7 +86,8 @@ func (db *database) GetWorkspaceByName(userID int, workspaceName string) (*model
|
|||||||
id, user_id, name, created_at,
|
id, user_id, name, created_at,
|
||||||
theme, auto_save, show_hidden_files,
|
theme, auto_save, show_hidden_files,
|
||||||
git_enabled, git_url, git_user, git_token,
|
git_enabled, git_url, git_user, git_token,
|
||||||
git_auto_commit, git_commit_msg_template
|
git_auto_commit, git_commit_msg_template,
|
||||||
|
git_commit_name, git_commit_email
|
||||||
FROM workspaces
|
FROM workspaces
|
||||||
WHERE user_id = ? AND name = ?`,
|
WHERE user_id = ? AND name = ?`,
|
||||||
userID, workspaceName,
|
userID, workspaceName,
|
||||||
@@ -93,6 +96,7 @@ func (db *database) GetWorkspaceByName(userID int, workspaceName string) (*model
|
|||||||
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
||||||
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
||||||
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
||||||
|
&workspace.GitCommitName, &workspace.GitCommitEmail,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -127,7 +131,9 @@ func (db *database) UpdateWorkspace(workspace *models.Workspace) error {
|
|||||||
git_user = ?,
|
git_user = ?,
|
||||||
git_token = ?,
|
git_token = ?,
|
||||||
git_auto_commit = ?,
|
git_auto_commit = ?,
|
||||||
git_commit_msg_template = ?
|
git_commit_msg_template = ?,
|
||||||
|
git_commit_name = ?,
|
||||||
|
git_commit_email = ?
|
||||||
WHERE id = ? AND user_id = ?`,
|
WHERE id = ? AND user_id = ?`,
|
||||||
workspace.Name,
|
workspace.Name,
|
||||||
workspace.Theme,
|
workspace.Theme,
|
||||||
@@ -139,6 +145,8 @@ func (db *database) UpdateWorkspace(workspace *models.Workspace) error {
|
|||||||
encryptedToken,
|
encryptedToken,
|
||||||
workspace.GitAutoCommit,
|
workspace.GitAutoCommit,
|
||||||
workspace.GitCommitMsgTemplate,
|
workspace.GitCommitMsgTemplate,
|
||||||
|
workspace.GitCommitName,
|
||||||
|
workspace.GitCommitEmail,
|
||||||
workspace.ID,
|
workspace.ID,
|
||||||
workspace.UserID,
|
workspace.UserID,
|
||||||
)
|
)
|
||||||
@@ -152,7 +160,8 @@ func (db *database) GetWorkspacesByUserID(userID int) ([]*models.Workspace, erro
|
|||||||
id, user_id, name, created_at,
|
id, user_id, name, created_at,
|
||||||
theme, auto_save, show_hidden_files,
|
theme, auto_save, show_hidden_files,
|
||||||
git_enabled, git_url, git_user, git_token,
|
git_enabled, git_url, git_user, git_token,
|
||||||
git_auto_commit, git_commit_msg_template
|
git_auto_commit, git_commit_msg_template,
|
||||||
|
git_commit_name, git_commit_email
|
||||||
FROM workspaces
|
FROM workspaces
|
||||||
WHERE user_id = ?`,
|
WHERE user_id = ?`,
|
||||||
userID,
|
userID,
|
||||||
@@ -171,6 +180,7 @@ func (db *database) GetWorkspacesByUserID(userID int) ([]*models.Workspace, erro
|
|||||||
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
||||||
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
||||||
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
||||||
|
&workspace.GitCommitName, &workspace.GitCommitEmail,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -201,7 +211,9 @@ func (db *database) UpdateWorkspaceSettings(workspace *models.Workspace) error {
|
|||||||
git_user = ?,
|
git_user = ?,
|
||||||
git_token = ?,
|
git_token = ?,
|
||||||
git_auto_commit = ?,
|
git_auto_commit = ?,
|
||||||
git_commit_msg_template = ?
|
git_commit_msg_template = ?,
|
||||||
|
git_commit_name = ?,
|
||||||
|
git_commit_email = ?
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
workspace.Theme,
|
workspace.Theme,
|
||||||
workspace.AutoSave,
|
workspace.AutoSave,
|
||||||
@@ -212,6 +224,8 @@ func (db *database) UpdateWorkspaceSettings(workspace *models.Workspace) error {
|
|||||||
workspace.GitToken,
|
workspace.GitToken,
|
||||||
workspace.GitAutoCommit,
|
workspace.GitAutoCommit,
|
||||||
workspace.GitCommitMsgTemplate,
|
workspace.GitCommitMsgTemplate,
|
||||||
|
workspace.GitCommitName,
|
||||||
|
workspace.GitCommitEmail,
|
||||||
workspace.ID,
|
workspace.ID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
@@ -261,7 +275,8 @@ func (db *database) GetAllWorkspaces() ([]*models.Workspace, error) {
|
|||||||
id, user_id, name, created_at,
|
id, user_id, name, created_at,
|
||||||
theme, auto_save, show_hidden_files,
|
theme, auto_save, show_hidden_files,
|
||||||
git_enabled, git_url, git_user, git_token,
|
git_enabled, git_url, git_user, git_token,
|
||||||
git_auto_commit, git_commit_msg_template
|
git_auto_commit, git_commit_msg_template,
|
||||||
|
git_commit_name, git_commit_email
|
||||||
FROM workspaces`,
|
FROM workspaces`,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -278,6 +293,7 @@ func (db *database) GetAllWorkspaces() ([]*models.Workspace, error) {
|
|||||||
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
&workspace.Theme, &workspace.AutoSave, &workspace.ShowHiddenFiles,
|
||||||
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
&workspace.GitEnabled, &workspace.GitURL, &workspace.GitUser, &encryptedToken,
|
||||||
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
&workspace.GitAutoCommit, &workspace.GitCommitMsgTemplate,
|
||||||
|
&workspace.GitCommitName, &workspace.GitCommitEmail,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ func TestWorkspaceOperations(t *testing.T) {
|
|||||||
GitToken: "secret-token",
|
GitToken: "secret-token",
|
||||||
GitAutoCommit: true,
|
GitAutoCommit: true,
|
||||||
GitCommitMsgTemplate: "${action} ${filename}",
|
GitCommitMsgTemplate: "${action} ${filename}",
|
||||||
|
GitCommitName: "Test User",
|
||||||
|
GitCommitEmail: "test@example.com",
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -244,6 +246,8 @@ func TestWorkspaceOperations(t *testing.T) {
|
|||||||
workspace.GitToken = "new-token"
|
workspace.GitToken = "new-token"
|
||||||
workspace.GitAutoCommit = true
|
workspace.GitAutoCommit = true
|
||||||
workspace.GitCommitMsgTemplate = "custom ${filename}"
|
workspace.GitCommitMsgTemplate = "custom ${filename}"
|
||||||
|
workspace.GitCommitName = "Test User"
|
||||||
|
workspace.GitCommitEmail = "test@example.com"
|
||||||
|
|
||||||
if err := database.UpdateWorkspace(workspace); err != nil {
|
if err := database.UpdateWorkspace(workspace); err != nil {
|
||||||
t.Fatalf("failed to update workspace: %v", err)
|
t.Fatalf("failed to update workspace: %v", err)
|
||||||
@@ -424,6 +428,12 @@ func verifyWorkspace(t *testing.T, actual, expected *models.Workspace) {
|
|||||||
if actual.GitCommitMsgTemplate != expected.GitCommitMsgTemplate {
|
if actual.GitCommitMsgTemplate != expected.GitCommitMsgTemplate {
|
||||||
t.Errorf("GitCommitMsgTemplate = %v, want %v", actual.GitCommitMsgTemplate, expected.GitCommitMsgTemplate)
|
t.Errorf("GitCommitMsgTemplate = %v, want %v", actual.GitCommitMsgTemplate, expected.GitCommitMsgTemplate)
|
||||||
}
|
}
|
||||||
|
if actual.GitCommitName != expected.GitCommitName {
|
||||||
|
t.Errorf("GitCommitName = %v, want %v", actual.GitCommitName, expected.GitCommitName)
|
||||||
|
}
|
||||||
|
if actual.GitCommitEmail != expected.GitCommitEmail {
|
||||||
|
t.Errorf("GitCommitEmail = %v, want %v", actual.GitCommitEmail, expected.GitCommitEmail)
|
||||||
|
}
|
||||||
if actual.CreatedAt.IsZero() {
|
if actual.CreatedAt.IsZero() {
|
||||||
t.Error("CreatedAt should not be zero")
|
t.Error("CreatedAt should not be zero")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds the configuration for a Git client
|
// Config holds the configuration for a Git client
|
||||||
type Config struct {
|
type Config struct {
|
||||||
URL string
|
URL string
|
||||||
Username string
|
Username string
|
||||||
Token string
|
Token string
|
||||||
WorkDir string
|
WorkDir string
|
||||||
|
CommitName string
|
||||||
|
CommitEmail string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client defines the interface for Git operations
|
// Client defines the interface for Git operations
|
||||||
@@ -34,13 +38,15 @@ type client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new git Client instance
|
// New creates a new git Client instance
|
||||||
func New(url, username, token, workDir string) Client {
|
func New(url, username, token, workDir, commitName, commitEmail string) Client {
|
||||||
return &client{
|
return &client{
|
||||||
Config: Config{
|
Config: Config{
|
||||||
URL: url,
|
URL: url,
|
||||||
Username: username,
|
Username: username,
|
||||||
Token: token,
|
Token: token,
|
||||||
WorkDir: workDir,
|
WorkDir: workDir,
|
||||||
|
CommitName: commitName,
|
||||||
|
CommitEmail: commitEmail,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +116,13 @@ func (c *client) Commit(message string) error {
|
|||||||
return fmt.Errorf("failed to add changes: %w", err)
|
return fmt.Errorf("failed to add changes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Commit(message, &git.CommitOptions{})
|
_, err = w.Commit(message, &git.CommitOptions{
|
||||||
|
Author: &object.Signature{
|
||||||
|
Name: c.CommitName,
|
||||||
|
Email: c.CommitEmail,
|
||||||
|
When: time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to commit changes: %w", err)
|
return fmt.Errorf("failed to commit changes: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func setupTestHarness(t *testing.T) *testHarness {
|
|||||||
|
|
||||||
// Create storage with mock git client
|
// Create storage with mock git client
|
||||||
storageOpts := storage.Options{
|
storageOpts := storage.Options{
|
||||||
NewGitClient: func(url, user, token, path string) git.Client {
|
NewGitClient: func(url, user, token, path, commitName, commitEmail string) git.Client {
|
||||||
return mockGit
|
return mockGit
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ func (h *Handler) CreateWorkspace() http.HandlerFunc {
|
|||||||
workspace.GitURL,
|
workspace.GitURL,
|
||||||
workspace.GitUser,
|
workspace.GitUser,
|
||||||
workspace.GitToken,
|
workspace.GitToken,
|
||||||
|
workspace.GitCommitName,
|
||||||
|
workspace.GitCommitEmail,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
http.Error(w, "Failed to setup git repo: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to setup git repo: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -96,7 +98,9 @@ func gitSettingsChanged(new, old *models.Workspace) bool {
|
|||||||
if new.GitEnabled {
|
if new.GitEnabled {
|
||||||
return new.GitURL != old.GitURL ||
|
return new.GitURL != old.GitURL ||
|
||||||
new.GitUser != old.GitUser ||
|
new.GitUser != old.GitUser ||
|
||||||
new.GitToken != old.GitToken
|
new.GitToken != old.GitToken ||
|
||||||
|
new.GitCommitName != old.GitCommitName ||
|
||||||
|
new.GitCommitEmail != old.GitCommitEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -135,6 +139,8 @@ func (h *Handler) UpdateWorkspace() http.HandlerFunc {
|
|||||||
workspace.GitURL,
|
workspace.GitURL,
|
||||||
workspace.GitUser,
|
workspace.GitUser,
|
||||||
workspace.GitToken,
|
workspace.GitToken,
|
||||||
|
workspace.GitCommitName,
|
||||||
|
workspace.GitCommitEmail,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
http.Error(w, "Failed to setup git repo: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to setup git repo: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -54,12 +54,14 @@ func TestWorkspaceHandlers_Integration(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("create with git settings", func(t *testing.T) {
|
t.Run("create with git settings", func(t *testing.T) {
|
||||||
workspace := &models.Workspace{
|
workspace := &models.Workspace{
|
||||||
Name: "Git Workspace",
|
Name: "Git Workspace",
|
||||||
GitEnabled: true,
|
GitEnabled: true,
|
||||||
GitURL: "https://github.com/test/repo.git",
|
GitURL: "https://github.com/test/repo.git",
|
||||||
GitUser: "testuser",
|
GitUser: "testuser",
|
||||||
GitToken: "testtoken",
|
GitToken: "testtoken",
|
||||||
GitAutoCommit: true,
|
GitAutoCommit: true,
|
||||||
|
GitCommitName: "Test User",
|
||||||
|
GitCommitEmail: "test@example.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
rr := h.makeRequest(t, http.MethodPost, "/api/v1/workspaces", workspace, h.RegularToken, nil)
|
rr := h.makeRequest(t, http.MethodPost, "/api/v1/workspaces", workspace, h.RegularToken, nil)
|
||||||
@@ -73,6 +75,8 @@ func TestWorkspaceHandlers_Integration(t *testing.T) {
|
|||||||
assert.Equal(t, workspace.GitUser, created.GitUser)
|
assert.Equal(t, workspace.GitUser, created.GitUser)
|
||||||
assert.Equal(t, workspace.GitToken, created.GitToken)
|
assert.Equal(t, workspace.GitToken, created.GitToken)
|
||||||
assert.Equal(t, workspace.GitAutoCommit, created.GitAutoCommit)
|
assert.Equal(t, workspace.GitAutoCommit, created.GitAutoCommit)
|
||||||
|
assert.Equal(t, workspace.GitCommitName, created.GitCommitName)
|
||||||
|
assert.Equal(t, workspace.GitCommitEmail, created.GitCommitEmail)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid workspace", func(t *testing.T) {
|
t.Run("invalid workspace", func(t *testing.T) {
|
||||||
@@ -161,13 +165,15 @@ func TestWorkspaceHandlers_Integration(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("enable git", func(t *testing.T) {
|
t.Run("enable git", func(t *testing.T) {
|
||||||
update := &models.Workspace{
|
update := &models.Workspace{
|
||||||
Name: workspace.Name,
|
Name: workspace.Name,
|
||||||
Theme: "dark",
|
Theme: "dark",
|
||||||
GitEnabled: true,
|
GitEnabled: true,
|
||||||
GitURL: "https://github.com/test/repo.git",
|
GitURL: "https://github.com/test/repo.git",
|
||||||
GitUser: "testuser",
|
GitUser: "testuser",
|
||||||
GitToken: "testtoken",
|
GitToken: "testtoken",
|
||||||
GitAutoCommit: true,
|
GitAutoCommit: true,
|
||||||
|
GitCommitName: "Test User",
|
||||||
|
GitCommitEmail: "test@example.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
rr := h.makeRequest(t, http.MethodPut, baseURL, update, h.RegularToken, nil)
|
rr := h.makeRequest(t, http.MethodPut, baseURL, update, h.RegularToken, nil)
|
||||||
@@ -180,6 +186,8 @@ func TestWorkspaceHandlers_Integration(t *testing.T) {
|
|||||||
assert.Equal(t, update.GitURL, updated.GitURL)
|
assert.Equal(t, update.GitURL, updated.GitURL)
|
||||||
assert.Equal(t, update.GitUser, updated.GitUser)
|
assert.Equal(t, update.GitUser, updated.GitUser)
|
||||||
assert.Equal(t, update.GitToken, updated.GitToken)
|
assert.Equal(t, update.GitToken, updated.GitToken)
|
||||||
|
assert.Equal(t, update.GitAutoCommit, updated.GitAutoCommit)
|
||||||
|
assert.Equal(t, update.GitCommitName, updated.GitCommitName)
|
||||||
|
|
||||||
// Mock should have been called to setup git
|
// Mock should have been called to setup git
|
||||||
assert.True(t, h.MockGit.IsInitialized())
|
assert.True(t, h.MockGit.IsInitialized())
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type Workspace struct {
|
|||||||
GitToken string `json:"gitToken" validate:"required_if=GitEnabled true"`
|
GitToken string `json:"gitToken" validate:"required_if=GitEnabled true"`
|
||||||
GitAutoCommit bool `json:"gitAutoCommit"`
|
GitAutoCommit bool `json:"gitAutoCommit"`
|
||||||
GitCommitMsgTemplate string `json:"gitCommitMsgTemplate"`
|
GitCommitMsgTemplate string `json:"gitCommitMsgTemplate"`
|
||||||
|
GitCommitName string `json:"gitCommitName"`
|
||||||
|
GitCommitEmail string `json:"gitCommitEmail" validate:"omitempty,required_if=GitEnabled true,email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the workspace struct
|
// Validate validates the workspace struct
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ type mockFS struct {
|
|||||||
StatError error
|
StatError error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//revive:disable:unexported-return
|
||||||
func NewMockFS() *mockFS {
|
func NewMockFS() *mockFS {
|
||||||
return &mockFS{
|
return &mockFS{
|
||||||
ReadCalls: make(map[string]int),
|
ReadCalls: make(map[string]int),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
// RepositoryManager defines the interface for managing Git repositories.
|
// RepositoryManager defines the interface for managing Git repositories.
|
||||||
type RepositoryManager interface {
|
type RepositoryManager interface {
|
||||||
SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error
|
SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken, commitName, commitEmail string) error
|
||||||
DisableGitRepo(userID, workspaceID int)
|
DisableGitRepo(userID, workspaceID int)
|
||||||
StageCommitAndPush(userID, workspaceID int, message string) error
|
StageCommitAndPush(userID, workspaceID int, message string) error
|
||||||
Pull(userID, workspaceID int) error
|
Pull(userID, workspaceID int) error
|
||||||
@@ -15,12 +15,12 @@ type RepositoryManager interface {
|
|||||||
|
|
||||||
// SetupGitRepo sets up a Git repository for the given userID and workspaceID.
|
// SetupGitRepo sets up a Git repository for the given userID and workspaceID.
|
||||||
// The repository is cloned from the given gitURL using the given gitUser and gitToken.
|
// The repository is cloned from the given gitURL using the given gitUser and gitToken.
|
||||||
func (s *Service) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error {
|
func (s *Service) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken, commitName, commitEmail string) error {
|
||||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
if _, ok := s.GitRepos[userID]; !ok {
|
if _, ok := s.GitRepos[userID]; !ok {
|
||||||
s.GitRepos[userID] = make(map[int]git.Client)
|
s.GitRepos[userID] = make(map[int]git.Client)
|
||||||
}
|
}
|
||||||
s.GitRepos[userID][workspaceID] = s.newGitClient(gitURL, gitUser, gitToken, workspacePath)
|
s.GitRepos[userID][workspaceID] = s.newGitClient(gitURL, gitUser, gitToken, workspacePath, commitName, commitEmail)
|
||||||
return s.GitRepos[userID][workspaceID].EnsureRepo()
|
return s.GitRepos[userID][workspaceID].EnsureRepo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ func TestSetupGitRepo(t *testing.T) {
|
|||||||
gitURL string
|
gitURL string
|
||||||
gitUser string
|
gitUser string
|
||||||
gitToken string
|
gitToken string
|
||||||
|
commitEmail string
|
||||||
mockErr error
|
mockErr error
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
@@ -65,6 +66,7 @@ func TestSetupGitRepo(t *testing.T) {
|
|||||||
gitURL: "https://github.com/user/repo",
|
gitURL: "https://github.com/user/repo",
|
||||||
gitUser: "user",
|
gitUser: "user",
|
||||||
gitToken: "token",
|
gitToken: "token",
|
||||||
|
commitEmail: "test@example.com",
|
||||||
mockErr: nil,
|
mockErr: nil,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -75,6 +77,7 @@ func TestSetupGitRepo(t *testing.T) {
|
|||||||
gitURL: "https://github.com/user/repo",
|
gitURL: "https://github.com/user/repo",
|
||||||
gitUser: "user",
|
gitUser: "user",
|
||||||
gitToken: "token",
|
gitToken: "token",
|
||||||
|
commitEmail: "test@example.com",
|
||||||
mockErr: errors.New("git initialization failed"),
|
mockErr: errors.New("git initialization failed"),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
@@ -86,7 +89,7 @@ func TestSetupGitRepo(t *testing.T) {
|
|||||||
mockClient := &MockGitClient{ReturnError: tc.mockErr}
|
mockClient := &MockGitClient{ReturnError: tc.mockErr}
|
||||||
|
|
||||||
// Create a client factory that returns our configured mock
|
// Create a client factory that returns our configured mock
|
||||||
mockClientFactory := func(_, _, _, _ string) git.Client {
|
mockClientFactory := func(_, _, _, _, _, _ string) git.Client {
|
||||||
return mockClient
|
return mockClient
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +99,7 @@ func TestSetupGitRepo(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Setup the git repo
|
// Setup the git repo
|
||||||
err := s.SetupGitRepo(tc.userID, tc.workspaceID, tc.gitURL, tc.gitUser, tc.gitToken)
|
err := s.SetupGitRepo(tc.userID, tc.workspaceID, tc.gitURL, tc.gitUser, tc.gitToken, tc.gitUser, tc.commitEmail)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -131,7 +134,7 @@ func TestGitOperations(t *testing.T) {
|
|||||||
mockFS := NewMockFS()
|
mockFS := NewMockFS()
|
||||||
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
||||||
Fs: mockFS,
|
Fs: mockFS,
|
||||||
NewGitClient: func(_, _, _, _ string) git.Client { return &MockGitClient{} },
|
NewGitClient: func(_, _, _, _, _, _ string) git.Client { return &MockGitClient{} },
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("operations on non-configured workspace", func(t *testing.T) {
|
t.Run("operations on non-configured workspace", func(t *testing.T) {
|
||||||
@@ -203,7 +206,7 @@ func TestDisableGitRepo(t *testing.T) {
|
|||||||
mockFS := NewMockFS()
|
mockFS := NewMockFS()
|
||||||
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
s := storage.NewServiceWithOptions("test-root", storage.Options{
|
||||||
Fs: mockFS,
|
Fs: mockFS,
|
||||||
NewGitClient: func(_, _, _, _ string) git.Client { return &MockGitClient{} },
|
NewGitClient: func(_, _, _, _, _, _ string) git.Client { return &MockGitClient{} },
|
||||||
})
|
})
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type Manager interface {
|
|||||||
// Service represents the file system structure.
|
// Service represents the file system structure.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
fs fileSystem
|
fs fileSystem
|
||||||
newGitClient func(url, user, token, path string) git.Client
|
newGitClient func(url, user, token, path, commitName, commitEmail string) git.Client
|
||||||
RootDir string
|
RootDir string
|
||||||
GitRepos map[int]map[int]git.Client // map[userID]map[workspaceID]*git.Client
|
GitRepos map[int]map[int]git.Client // map[userID]map[workspaceID]*git.Client
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ type Service struct {
|
|||||||
// Options represents the options for the storage service.
|
// Options represents the options for the storage service.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Fs fileSystem
|
Fs fileSystem
|
||||||
NewGitClient func(url, user, token, path string) git.Client
|
NewGitClient func(url, user, token, path, commitName, commitEmail string) git.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates a new Storage instance with the default options and the given rootDir root directory.
|
// NewService creates a new Storage instance with the default options and the given rootDir root directory.
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
|
|
||||||
"novamd/internal/db"
|
|
||||||
"novamd/internal/models"
|
|
||||||
"novamd/internal/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserService struct {
|
|
||||||
DB db.Database
|
|
||||||
Storage storage.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserService(database db.Database, s storage.Manager) *UserService {
|
|
||||||
return &UserService{
|
|
||||||
DB: database,
|
|
||||||
Storage: s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *UserService) SetupAdminUser(adminEmail, adminPassword string) (*models.User, error) {
|
|
||||||
// Check if admin user exists
|
|
||||||
adminUser, err := s.DB.GetUserByEmail(adminEmail)
|
|
||||||
if adminUser != nil {
|
|
||||||
return adminUser, nil // Admin user already exists
|
|
||||||
} else if err != sql.ErrNoRows {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the password
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to hash password: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create admin user
|
|
||||||
adminUser = &models.User{
|
|
||||||
Email: adminEmail,
|
|
||||||
DisplayName: "Admin",
|
|
||||||
PasswordHash: string(hashedPassword),
|
|
||||||
Role: models.RoleAdmin,
|
|
||||||
}
|
|
||||||
|
|
||||||
createdUser, err := s.DB.CreateUser(adminUser)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create admin user: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize workspace directory
|
|
||||||
err = s.Storage.InitializeUserWorkspace(createdUser.ID, createdUser.LastWorkspaceID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to initialize admin workspace: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Created admin user with ID: %d and default workspace with ID: %d", createdUser.ID, createdUser.LastWorkspaceID)
|
|
||||||
|
|
||||||
return adminUser, nil
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user