mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eb53bb07b | ||
|
|
6ac04270b9 | ||
|
|
b0510d7c21 | ||
|
|
dc5f271882 | ||
|
|
8f718771c9 | ||
|
|
d8eea05dca | ||
|
|
b2a94274d7 | ||
|
|
77c2712ebb | ||
|
|
a9dc29f82c |
@@ -272,10 +272,13 @@ window.__RUNTIME_CONFIG__ = {
|
||||
};
|
||||
JSEOF
|
||||
|
||||
# Inject analytics script if provided
|
||||
# Inject analytics script if provided (only if not already injected)
|
||||
if [ -n "\${ANALYTICS_SCRIPT:-}" ]; then
|
||||
echo "Injecting analytics script..."
|
||||
sed -i "s#</head># \${ANALYTICS_SCRIPT}\n </head>#" /app/ui/build/index.html
|
||||
if ! grep -q "rybbit.databasus.com" /app/ui/build/index.html 2>/dev/null; then
|
||||
echo "Injecting analytics script..."
|
||||
sed -i "s#</head># \${ANALYTICS_SCRIPT}\\
|
||||
</head>#" /app/ui/build/index.html
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure proper ownership of data directory
|
||||
|
||||
@@ -103,6 +103,16 @@ func (s *BackupsScheduler) IsSchedulerRunning() bool {
|
||||
return s.lastBackupTime.After(time.Now().UTC().Add(-schedulerHealthcheckThreshold))
|
||||
}
|
||||
|
||||
func (s *BackupsScheduler) IsBackupNodesAvailable() bool {
|
||||
nodes, err := s.backupNodesRegistry.GetAvailableNodes()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get available nodes for health check", "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return len(nodes) > 0
|
||||
}
|
||||
|
||||
func (s *BackupsScheduler) StartBackup(databaseID uuid.UUID, isCallNotifier bool) {
|
||||
backupConfig, err := s.backupConfigService.GetBackupConfigByDbId(databaseID)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
users_services "databasus-backend/internal/features/users/services"
|
||||
users_testing "databasus-backend/internal/features/users/testing"
|
||||
workspaces_controllers "databasus-backend/internal/features/workspaces/controllers"
|
||||
workspaces_repositories "databasus-backend/internal/features/workspaces/repositories"
|
||||
workspaces_testing "databasus-backend/internal/features/workspaces/testing"
|
||||
"databasus-backend/internal/util/encryption"
|
||||
test_utils "databasus-backend/internal/util/testing"
|
||||
@@ -1969,6 +1970,143 @@ func Test_TransferSystemStorage_TransferBlocked(t *testing.T) {
|
||||
workspaces_testing.RemoveTestWorkspace(workspaceB, router)
|
||||
}
|
||||
|
||||
func Test_DeleteWorkspace_SystemStoragesFromAnotherWorkspaceNotRemovedAndWorkspaceDeletedSuccessfully(
|
||||
t *testing.T,
|
||||
) {
|
||||
router := createRouter()
|
||||
GetStorageService().SetStorageDatabaseCounter(&mockStorageDatabaseCounter{})
|
||||
|
||||
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
|
||||
workspaceA := workspaces_testing.CreateTestWorkspace("Workspace A", admin, router)
|
||||
workspaceD := workspaces_testing.CreateTestWorkspace("Workspace D", admin, router)
|
||||
|
||||
// Create a system storage in workspace A
|
||||
systemStorage := &Storage{
|
||||
WorkspaceID: workspaceA.ID,
|
||||
Type: StorageTypeLocal,
|
||||
Name: "Test System Storage " + uuid.New().String(),
|
||||
IsSystem: true,
|
||||
LocalStorage: &local_storage.LocalStorage{},
|
||||
}
|
||||
var savedSystemStorage Storage
|
||||
test_utils.MakePostRequestAndUnmarshal(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/storages",
|
||||
"Bearer "+admin.Token,
|
||||
*systemStorage,
|
||||
http.StatusOK,
|
||||
&savedSystemStorage,
|
||||
)
|
||||
assert.True(t, savedSystemStorage.IsSystem)
|
||||
assert.Equal(t, workspaceA.ID, savedSystemStorage.WorkspaceID)
|
||||
|
||||
// Create a regular storage in workspace D
|
||||
regularStorage := createNewStorage(workspaceD.ID)
|
||||
var savedRegularStorage Storage
|
||||
test_utils.MakePostRequestAndUnmarshal(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/storages",
|
||||
"Bearer "+admin.Token,
|
||||
*regularStorage,
|
||||
http.StatusOK,
|
||||
&savedRegularStorage,
|
||||
)
|
||||
assert.False(t, savedRegularStorage.IsSystem)
|
||||
assert.Equal(t, workspaceD.ID, savedRegularStorage.WorkspaceID)
|
||||
|
||||
// Delete workspace D
|
||||
workspaces_testing.DeleteWorkspace(workspaceD, admin.Token, router)
|
||||
|
||||
// Verify system storage from workspace A still exists
|
||||
repository := &StorageRepository{}
|
||||
systemStorageAfterDeletion, err := repository.FindByID(savedSystemStorage.ID)
|
||||
assert.NoError(t, err, "System storage should still exist after workspace D deletion")
|
||||
assert.NotNil(t, systemStorageAfterDeletion)
|
||||
assert.Equal(t, savedSystemStorage.ID, systemStorageAfterDeletion.ID)
|
||||
assert.True(t, systemStorageAfterDeletion.IsSystem)
|
||||
assert.Equal(t, workspaceA.ID, systemStorageAfterDeletion.WorkspaceID)
|
||||
|
||||
// Verify regular storage from workspace D was deleted
|
||||
regularStorageAfterDeletion, err := repository.FindByID(savedRegularStorage.ID)
|
||||
assert.Error(t, err, "Regular storage should be deleted with workspace D")
|
||||
assert.Nil(t, regularStorageAfterDeletion)
|
||||
|
||||
// Cleanup
|
||||
deleteStorage(t, router, savedSystemStorage.ID, admin.Token)
|
||||
workspaces_testing.RemoveTestWorkspace(workspaceA, router)
|
||||
}
|
||||
|
||||
func Test_DeleteWorkspace_WithOwnSystemStorage_ReturnsForbidden(t *testing.T) {
|
||||
router := createRouter()
|
||||
GetStorageService().SetStorageDatabaseCounter(&mockStorageDatabaseCounter{})
|
||||
|
||||
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
|
||||
workspaceA := workspaces_testing.CreateTestWorkspace("Workspace A", admin, router)
|
||||
|
||||
// Create a system storage assigned to workspace A
|
||||
systemStorage := &Storage{
|
||||
WorkspaceID: workspaceA.ID,
|
||||
Type: StorageTypeLocal,
|
||||
Name: "System Storage in A " + uuid.New().String(),
|
||||
IsSystem: true,
|
||||
LocalStorage: &local_storage.LocalStorage{},
|
||||
}
|
||||
|
||||
var savedSystemStorage Storage
|
||||
test_utils.MakePostRequestAndUnmarshal(
|
||||
t,
|
||||
router,
|
||||
"/api/v1/storages",
|
||||
"Bearer "+admin.Token,
|
||||
*systemStorage,
|
||||
http.StatusOK,
|
||||
&savedSystemStorage,
|
||||
)
|
||||
assert.True(t, savedSystemStorage.IsSystem)
|
||||
assert.Equal(t, workspaceA.ID, savedSystemStorage.WorkspaceID)
|
||||
|
||||
// Attempt to delete workspace A - should fail because it has a system storage
|
||||
resp := workspaces_testing.MakeAPIRequest(
|
||||
router,
|
||||
"DELETE",
|
||||
"/api/v1/workspaces/"+workspaceA.ID.String(),
|
||||
"Bearer "+admin.Token,
|
||||
nil,
|
||||
)
|
||||
assert.Equal(t, http.StatusBadRequest, resp.Code, "Workspace deletion should fail")
|
||||
assert.Contains(
|
||||
t,
|
||||
resp.Body.String(),
|
||||
"system storage cannot be deleted due to workspace deletion",
|
||||
"Error message should indicate system storage prevents deletion",
|
||||
)
|
||||
|
||||
// Verify workspace still exists
|
||||
workspaceRepo := &workspaces_repositories.WorkspaceRepository{}
|
||||
workspaceAfterFailedDeletion, err := workspaceRepo.GetWorkspaceByID(workspaceA.ID)
|
||||
assert.NoError(t, err, "Workspace should still exist after failed deletion")
|
||||
assert.NotNil(t, workspaceAfterFailedDeletion)
|
||||
assert.Equal(t, workspaceA.ID, workspaceAfterFailedDeletion.ID)
|
||||
|
||||
// Verify system storage still exists
|
||||
repository := &StorageRepository{}
|
||||
storageAfterFailedDeletion, err := repository.FindByID(savedSystemStorage.ID)
|
||||
assert.NoError(t, err, "System storage should still exist after failed deletion")
|
||||
assert.NotNil(t, storageAfterFailedDeletion)
|
||||
assert.Equal(t, savedSystemStorage.ID, storageAfterFailedDeletion.ID)
|
||||
assert.True(t, storageAfterFailedDeletion.IsSystem)
|
||||
|
||||
// Cleanup: Delete system storage first, then workspace can be deleted
|
||||
deleteStorage(t, router, savedSystemStorage.ID, admin.Token)
|
||||
workspaces_testing.DeleteWorkspace(workspaceA, admin.Token, router)
|
||||
|
||||
// Verify workspace was successfully deleted after storage removal
|
||||
_, err = workspaceRepo.GetWorkspaceByID(workspaceA.ID)
|
||||
assert.Error(t, err, "Workspace should be deleted after storage was removed")
|
||||
}
|
||||
|
||||
func createRouter() *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.New()
|
||||
@@ -1983,6 +2121,7 @@ func createRouter() *gin.Engine {
|
||||
}
|
||||
|
||||
audit_logs.SetupDependencies()
|
||||
SetupDependencies()
|
||||
GetStorageService().SetStorageDatabaseCounter(&mockStorageDatabaseCounter{})
|
||||
|
||||
return router
|
||||
|
||||
@@ -25,6 +25,32 @@ func (s *StorageService) SetStorageDatabaseCounter(storageDatabaseCounter Storag
|
||||
s.storageDatabaseCounter = storageDatabaseCounter
|
||||
}
|
||||
|
||||
func (s *StorageService) OnBeforeWorkspaceDeletion(workspaceID uuid.UUID) error {
|
||||
storages, err := s.storageRepository.FindByWorkspaceID(workspaceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get storages for workspace deletion: %w", err)
|
||||
}
|
||||
|
||||
for _, storage := range storages {
|
||||
if storage.IsSystem && storage.WorkspaceID != workspaceID {
|
||||
// skip system storage from another workspace
|
||||
continue
|
||||
}
|
||||
|
||||
if storage.IsSystem && storage.WorkspaceID == workspaceID {
|
||||
return fmt.Errorf(
|
||||
"system storage cannot be deleted due to workspace deletion, please transfer or remove storage first",
|
||||
)
|
||||
}
|
||||
|
||||
if err := s.storageRepository.Delete(storage); err != nil {
|
||||
return fmt.Errorf("failed to delete storage %s: %w", storage.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageService) SaveStorage(
|
||||
user *users_models.User,
|
||||
workspaceID uuid.UUID,
|
||||
@@ -351,18 +377,3 @@ func (s *StorageService) TransferStorageToWorkspace(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageService) OnBeforeWorkspaceDeletion(workspaceID uuid.UUID) error {
|
||||
storages, err := s.storageRepository.FindByWorkspaceID(workspaceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get storages for workspace deletion: %w", err)
|
||||
}
|
||||
|
||||
for _, storage := range storages {
|
||||
if err := s.storageRepository.Delete(storage); err != nil {
|
||||
return fmt.Errorf("failed to delete storage %s: %w", storage.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ func (s *HealthcheckService) performHealthCheck() error {
|
||||
if !s.backupBackgroundService.IsSchedulerRunning() {
|
||||
return errors.New("backups are not running for more than 5 minutes")
|
||||
}
|
||||
|
||||
if !s.backupBackgroundService.IsBackupNodesAvailable() {
|
||||
return errors.New("no backup nodes available")
|
||||
}
|
||||
}
|
||||
|
||||
if config.GetEnv().IsProcessingNode {
|
||||
|
||||
@@ -71,6 +71,7 @@ func tryInitVictoriaLogs() *VictoriaLogsWriter {
|
||||
|
||||
// Try to get config - this may fail early in startup
|
||||
url := getVictoriaLogsURL()
|
||||
username := getVictoriaLogsUsername()
|
||||
password := getVictoriaLogsPassword()
|
||||
|
||||
if url == "" {
|
||||
@@ -78,7 +79,7 @@ func tryInitVictoriaLogs() *VictoriaLogsWriter {
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewVictoriaLogsWriter(url, password)
|
||||
return NewVictoriaLogsWriter(url, username, password)
|
||||
}
|
||||
|
||||
func ensureEnvLoaded() {
|
||||
@@ -126,6 +127,10 @@ func getVictoriaLogsURL() string {
|
||||
return os.Getenv("VICTORIA_LOGS_URL")
|
||||
}
|
||||
|
||||
func getVictoriaLogsUsername() string {
|
||||
return os.Getenv("VICTORIA_LOGS_USERNAME")
|
||||
}
|
||||
|
||||
func getVictoriaLogsPassword() string {
|
||||
return os.Getenv("VICTORIA_LOGS_PASSWORD")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ type logEntry struct {
|
||||
|
||||
type VictoriaLogsWriter struct {
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
httpClient *http.Client
|
||||
logChannel chan logEntry
|
||||
@@ -33,11 +34,12 @@ type VictoriaLogsWriter struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewVictoriaLogsWriter(url, password string) *VictoriaLogsWriter {
|
||||
func NewVictoriaLogsWriter(url, username, password string) *VictoriaLogsWriter {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
writer := &VictoriaLogsWriter{
|
||||
url: url,
|
||||
username: username,
|
||||
password: password,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
@@ -149,9 +151,9 @@ func (w *VictoriaLogsWriter) sendHTTP(entries []logEntry) error {
|
||||
// Set headers
|
||||
req.Header.Set("Content-Type", "application/x-ndjson")
|
||||
|
||||
// Set Basic Auth (password as username, empty password)
|
||||
// Set Basic Auth (username:password)
|
||||
if w.password != "" {
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(w.password + ":"))
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(w.username + ":" + w.password))
|
||||
req.Header.Set("Authorization", "Basic "+auth)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user