diff --git a/backend/drive/migrations/0011_normalize_empty_root.py b/backend/drive/migrations/0011_normalize_empty_root.py new file mode 100644 index 0000000..f3da1ec --- /dev/null +++ b/backend/drive/migrations/0011_normalize_empty_root.py @@ -0,0 +1,53 @@ +"""Normalize fresh installations to the physical storage root. + +Older databases with existing /root content MUST be converted explicitly using +`manage.py flatten_drive_root --apply`, after a backup. This migration does not +move, delete or rewrite any user file. +""" +from pathlib import Path + +from django.conf import settings +from django.db import migrations, transaction +from django.db.models import Q + + +def normalize_unused_root(apps, schema_editor): + Space = apps.get_model('drive', 'DriveSpace') + Item = apps.get_model('drive', 'DriveItem') + Upload = apps.get_model('drive', 'UploadSession') + root = Space.objects.filter(parent__isnull=True, private_owner__isnull=True).order_by('pk').first() + if root is None or root.relative_path != 'root': + return + + # The previous migrations create a logical "root" even for an empty DB. + # Existing installations must not be migrated behind the user's back: + # physical bytes must be moved consistently with DB paths. + old_dir = Path(settings.DRIVE_STORAGE_ROOT).resolve() / 'root' + if (old_dir.exists() and (not old_dir.is_dir() or any(old_dir.iterdir()))): + print('GoodDrive: root/ has data; run flatten_drive_root --apply after backup.') + return + if (Item.objects.filter(storage_location__startswith='root/').exists() + or Upload.objects.exists()): + print('GoodDrive: existing files/uploads; run flatten_drive_root --apply after backup.') + return + + old = list(Space.objects.filter(Q(pk=root.pk) | Q(relative_path__startswith='root/'))) + new = {s.pk: ('' if s.pk == root.pk else s.relative_path[5:]) for s in old} + if any(not path or path.startswith('/') for pk, path in new.items() if pk != root.pk): + print('GoodDrive: invalid old space paths; use flatten_drive_root after inspection.') + return + other_paths = set(Space.objects.exclude(pk__in=new).values_list('relative_path', flat=True)) + if any(path in other_paths for path in new.values()): + print('GoodDrive: space paths conflict; use flatten_drive_root after inspection.') + return + + with transaction.atomic(): + for space in old: + space.relative_path = new[space.pk] + Space.objects.bulk_update(old, ['relative_path']) + print('GoodDrive: new database root uses DRIVE_STORAGE_ROOT directly (no /root/).') + + +class Migration(migrations.Migration): + dependencies = [('drive', '0009_managed_spaces')] + operations = [migrations.RunPython(normalize_unused_root, migrations.RunPython.noop)] diff --git a/backend/drive/migrations/0012_merge_20261014_0129.py b/backend/drive/migrations/0012_merge_20261014_0129.py new file mode 100644 index 0000000..59f7e06 --- /dev/null +++ b/backend/drive/migrations/0012_merge_20261014_0129.py @@ -0,0 +1,14 @@ +# Generated by Django 5.2.18 on 2026-10-13 23:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('drive', '0010_merge_0008_alter_spacegrant_id_0009_managed_spaces'), + ('drive', '0011_normalize_empty_root'), + ] + + operations = [ + ] diff --git a/frontend/index.html b/frontend/index.html index f8d5751..68db850 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + GooDrive diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg deleted file mode 100644 index a30291c..0000000 --- a/frontend/public/favicon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/public/icon.png b/frontend/public/icon.png new file mode 100644 index 0000000..c68b3af Binary files /dev/null and b/frontend/public/icon.png differ diff --git a/frontend/src/components/drive/DriveSidebar.jsx b/frontend/src/components/drive/DriveSidebar.jsx index 2f902b2..36cb6d6 100644 --- a/frontend/src/components/drive/DriveSidebar.jsx +++ b/frontend/src/components/drive/DriveSidebar.jsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { Button } from '@/components/ui/button' import ContextActions, {spaceOptions, mobileDots} from './ContextActions' import { MoreHorizontal, Plus } from 'lucide-react' -import { Database, FolderPlus, HardDrive, LogOut, FolderLock, FolderOpen, X } from 'lucide-react' +import { Database, FolderPlus, LogOut, FolderLock, FolderOpen, X } from 'lucide-react' import { sections } from '@/config/navigation' import { fmtSize } from '@/lib/drive-format' @@ -58,10 +58,14 @@ export default function DriveSidebar({ controller, user, onLogout }) {