mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Implement workspace deletion
This commit is contained in:
@@ -143,24 +143,57 @@ func DeleteWorkspace(db *db.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace, err := db.GetWorkspaceByID(workspaceID)
|
// Check if this is the user's last workspace
|
||||||
|
workspaces, err := db.GetWorkspacesByUserID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Workspace not found", http.StatusNotFound)
|
http.Error(w, "Failed to get workspaces", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if workspace.UserID != userID {
|
if len(workspaces) <= 1 {
|
||||||
http.Error(w, "Unauthorized access to workspace", http.StatusForbidden)
|
http.Error(w, "Cannot delete the last workspace", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.DeleteWorkspace(workspaceID); err != nil {
|
// Find another workspace to set as last
|
||||||
|
var nextWorkspaceID int
|
||||||
|
for _, ws := range workspaces {
|
||||||
|
if ws.ID != workspaceID {
|
||||||
|
nextWorkspaceID = ws.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to start transaction", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Update last workspace ID first
|
||||||
|
err = db.UpdateLastWorkspaceTx(tx, userID, nextWorkspaceID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to update last workspace", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the workspace
|
||||||
|
err = db.DeleteWorkspaceTx(tx, workspaceID)
|
||||||
|
if err != nil {
|
||||||
http.Error(w, "Failed to delete workspace", http.StatusInternalServerError)
|
http.Error(w, "Failed to delete workspace", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
// Commit transaction
|
||||||
w.Write([]byte("Workspace deleted successfully"))
|
if err = tx.Commit(); err != nil {
|
||||||
|
http.Error(w, "Failed to commit transaction", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the next workspace ID in the response so frontend knows where to redirect
|
||||||
|
respondJSON(w, map[string]int{"nextWorkspaceId": nextWorkspaceID})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"novamd/internal/models"
|
"novamd/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -149,3 +150,13 @@ func (db *DB) DeleteWorkspace(id int) error {
|
|||||||
_, err := db.Exec("DELETE FROM workspaces WHERE id = ?", id)
|
_, err := db.Exec("DELETE FROM workspaces WHERE id = ?", id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) DeleteWorkspaceTx(tx *sql.Tx, id int) error {
|
||||||
|
_, err := tx.Exec("DELETE FROM workspaces WHERE id = ?", id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) UpdateLastWorkspaceTx(tx *sql.Tx, userID, workspaceID int) error {
|
||||||
|
_, err := tx.Exec("UPDATE users SET last_workspace_id = ? WHERE id = ?", workspaceID, userID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ import React, { useState } from 'react';
|
|||||||
import { Box, Button, Title } from '@mantine/core';
|
import { Box, Button, Title } from '@mantine/core';
|
||||||
import DeleteWorkspaceModal from '../modals/DeleteWorkspaceModal';
|
import DeleteWorkspaceModal from '../modals/DeleteWorkspaceModal';
|
||||||
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
import { useModalContext } from '../../contexts/ModalContext';
|
||||||
|
|
||||||
const DangerZoneSettings = () => {
|
const DangerZoneSettings = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace, workspaces, deleteCurrentWorkspace } =
|
||||||
|
useWorkspace();
|
||||||
|
const { setSettingsModalVisible } = useModalContext();
|
||||||
const [deleteModalOpened, setDeleteModalOpened] = useState(false);
|
const [deleteModalOpened, setDeleteModalOpened] = useState(false);
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = async () => {
|
||||||
// TODO: Implement delete functionality
|
await deleteCurrentWorkspace();
|
||||||
setDeleteModalOpened(false);
|
setDeleteModalOpened(false);
|
||||||
|
setSettingsModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +27,12 @@ const DangerZoneSettings = () => {
|
|||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => setDeleteModalOpened(true)}
|
onClick={() => setDeleteModalOpened(true)}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
disabled={workspaces.length <= 1}
|
||||||
|
title={
|
||||||
|
workspaces.length <= 1
|
||||||
|
? 'Cannot delete the last workspace'
|
||||||
|
: 'Delete this workspace'
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Delete Workspace
|
Delete Workspace
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
getWorkspace,
|
getWorkspace,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
updateLastWorkspace,
|
updateLastWorkspace,
|
||||||
|
deleteWorkspace,
|
||||||
|
listWorkspaces,
|
||||||
} from '../services/api';
|
} from '../services/api';
|
||||||
import { DEFAULT_WORKSPACE_SETTINGS } from '../utils/constants';
|
import { DEFAULT_WORKSPACE_SETTINGS } from '../utils/constants';
|
||||||
|
|
||||||
@@ -19,9 +21,26 @@ const WorkspaceContext = createContext();
|
|||||||
|
|
||||||
export const WorkspaceProvider = ({ children }) => {
|
export const WorkspaceProvider = ({ children }) => {
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useState(null);
|
const [currentWorkspace, setCurrentWorkspace] = useState(null);
|
||||||
|
const [workspaces, setWorkspaces] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
|
const loadWorkspaces = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const workspaceList = await listWorkspaces();
|
||||||
|
setWorkspaces(workspaceList);
|
||||||
|
return workspaceList;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load workspaces:', error);
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Failed to load workspaces list',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadWorkspaceData = useCallback(async (workspaceId) => {
|
const loadWorkspaceData = useCallback(async (workspaceId) => {
|
||||||
try {
|
try {
|
||||||
const workspace = await getWorkspace(workspaceId);
|
const workspace = await getWorkspace(workspaceId);
|
||||||
@@ -37,6 +56,24 @@ export const WorkspaceProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const loadFirstAvailableWorkspace = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const allWorkspaces = await listWorkspaces();
|
||||||
|
if (allWorkspaces.length > 0) {
|
||||||
|
const firstWorkspace = allWorkspaces[0];
|
||||||
|
await updateLastWorkspace(firstWorkspace.id);
|
||||||
|
await loadWorkspaceData(firstWorkspace.id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load first available workspace:', error);
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Failed to load workspace',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeWorkspace = async () => {
|
const initializeWorkspace = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -44,10 +81,12 @@ export const WorkspaceProvider = ({ children }) => {
|
|||||||
if (lastWorkspaceId) {
|
if (lastWorkspaceId) {
|
||||||
await loadWorkspaceData(lastWorkspaceId);
|
await loadWorkspaceData(lastWorkspaceId);
|
||||||
} else {
|
} else {
|
||||||
console.warn('No last workspace found');
|
await loadFirstAvailableWorkspace();
|
||||||
}
|
}
|
||||||
|
await loadWorkspaces();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize workspace:', error);
|
console.error('Failed to initialize workspace:', error);
|
||||||
|
await loadFirstAvailableWorkspace();
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -61,6 +100,7 @@ export const WorkspaceProvider = ({ children }) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
await updateLastWorkspace(workspaceId);
|
await updateLastWorkspace(workspaceId);
|
||||||
await loadWorkspaceData(workspaceId);
|
await loadWorkspaceData(workspaceId);
|
||||||
|
await loadWorkspaces();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to switch workspace:', error);
|
console.error('Failed to switch workspace:', error);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
@@ -73,6 +113,44 @@ export const WorkspaceProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const deleteCurrentWorkspace = useCallback(async () => {
|
||||||
|
if (!currentWorkspace) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const allWorkspaces = await loadWorkspaces();
|
||||||
|
if (allWorkspaces.length <= 1) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message:
|
||||||
|
'Cannot delete the last workspace. At least one workspace must exist.',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete workspace and get the next workspace ID
|
||||||
|
const response = await deleteWorkspace(currentWorkspace.id);
|
||||||
|
|
||||||
|
// Load the new workspace data
|
||||||
|
await loadWorkspaceData(response.nextWorkspaceId);
|
||||||
|
|
||||||
|
notifications.show({
|
||||||
|
title: 'Success',
|
||||||
|
message: 'Workspace deleted successfully',
|
||||||
|
color: 'green',
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadWorkspaces();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete workspace:', error);
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Failed to delete workspace',
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentWorkspace]);
|
||||||
|
|
||||||
const updateSettings = useCallback(
|
const updateSettings = useCallback(
|
||||||
async (newSettings) => {
|
async (newSettings) => {
|
||||||
if (!currentWorkspace) return;
|
if (!currentWorkspace) return;
|
||||||
@@ -89,27 +167,32 @@ export const WorkspaceProvider = ({ children }) => {
|
|||||||
);
|
);
|
||||||
setCurrentWorkspace(response);
|
setCurrentWorkspace(response);
|
||||||
setColorScheme(response.theme);
|
setColorScheme(response.theme);
|
||||||
|
await loadWorkspaces();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save settings:', error);
|
console.error('Failed to save settings:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentWorkspace]
|
[currentWorkspace, setColorScheme]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update just the color scheme without saving to backend
|
const updateColorScheme = useCallback(
|
||||||
const updateColorScheme = useCallback((newTheme) => {
|
(newTheme) => {
|
||||||
setColorScheme(newTheme);
|
setColorScheme(newTheme);
|
||||||
}, []);
|
},
|
||||||
|
[setColorScheme]
|
||||||
|
);
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
currentWorkspace,
|
currentWorkspace,
|
||||||
|
workspaces,
|
||||||
settings: currentWorkspace || DEFAULT_WORKSPACE_SETTINGS,
|
settings: currentWorkspace || DEFAULT_WORKSPACE_SETTINGS,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
loading,
|
loading,
|
||||||
colorScheme,
|
colorScheme,
|
||||||
updateColorScheme,
|
updateColorScheme,
|
||||||
switchWorkspace,
|
switchWorkspace,
|
||||||
|
deleteCurrentWorkspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user