# Storage Drive — Complete Feature Documentation

> **Audience:** New developers and normal users.  
> **Last updated:** May 2026

---

## Table of Contents

1. [Overview](#1-overview)
2. [Architecture & Key Files](#2-architecture--key-files)
3. [Database Models](#3-database-models)
4. [Permission System (3-Layer)](#4-permission-system-3-layer)
5. [Sub-Menu: My Storage](#5-sub-menu-my-storage)
6. [Sub-Menu: Shared With Me](#6-sub-menu-shared-with-me)
7. [Sub-Menu: Trashed Storage](#7-sub-menu-trashed-storage)
8. [Sub-Menu: Storage Analytics](#8-sub-menu-storage-analytics)
9. [Route Reference](#9-route-reference)
10. [File Storage on Disk](#10-file-storage-on-disk)
11. [Supported Item Types](#11-supported-item-types)
12. [Supported File Types for Upload](#12-supported-file-types-for-upload)
13. [End-to-End Workflow Diagram](#13-end-to-end-workflow-diagram)
14. [Developer Notes & Gotchas](#14-developer-notes--gotchas)

---

## 1. Overview

**Storage Drive** is a cloud-file-manager-style module embedded in the backend admin panel. It allows users to:

- Organise files, rich-text notes, external links, and folders inside a personal drive.
- Share individual items (or entire folder trees) with specific users or roles.
- Browse items that other users have shared with them.
- Soft-delete items to a Trash can and later restore or permanently delete them.
- View system-wide storage usage via an analytics dashboard.

Each authenticated user automatically owns **one Storage Drive** (one row in the `storage_drive` table). Everything inside that drive is stored as rows in `storage_drive_items`.

---

## 2. Architecture & Key Files

```
app/
├── Http/Controllers/Backend/StorageDrive/
│   ├── BaseStorageDriveController.php   ← Abstract base: shared helpers, permission wrappers
│   ├── MyStorageController.php          ← My Storage: browse, CRUD, share, download
│   ├── SharedWithMeController.php       ← Shared With Me: listing and browsing
│   ├── TrashedStorageController.php     ← Trashed Storage: restore / permanent delete
│   └── StorageAnalyticsController.php   ← Analytics: per-user storage usage charts
│
├── Models/StorageDrive/
│   ├── StorageDrive.php                 ← One drive per user (owner_id)
│   ├── StorageDriveItem.php             ← Items: folder, file, text, link
│   └── StorageDriveItemShare.php        ← Share records (user or role, view or edit)
│
└── Services/StorageDrive/
    └── PermissionService.php            ← Share-based item-level permission logic

routes/web.php                           ← All Storage Drive routes (prefix: my_storage, shared_with_me, trashed_storage)
```

**View prefix:** `resources/views/backend/storage_drive/`  
**Storage disk:** `storage_drive` (configured in `config/filesystems.php`)

---

## 3. Database Models

### `storage_drive`
| Column      | Type    | Description                          |
|-------------|---------|--------------------------------------|
| `id`        | integer | Primary key                          |
| `owner_id`  | integer | FK → `users.id` (one drive per user) |

### `storage_drive_items`
| Column              | Type    | Description                                                     |
|---------------------|---------|-----------------------------------------------------------------|
| `id`                | integer | Primary key                                                     |
| `storage_drive_id`  | integer | FK → `storage_drive.id`                                        |
| `parent_id`         | integer | FK → self (`storage_drive_items.id`), nullable = root level     |
| `type`              | enum    | `folder`, `file`, `text`, `link`                                |
| `name`              | string  | Display name (file names preserve original extension)           |
| `mime_type`         | string  | MIME type for files; `text/html` for text; `application/link` for links |
| `size`              | integer | Bytes (files and text items; null for folders and links)        |
| `path`              | string  | Relative path on disk (files only); raw URL for links           |
| `meta`              | JSON    | `{content: "..."}` for text items; `{title, link}` for links   |
| `created_by`        | integer | FK → `users.id`                                                 |
| `deleted_at`        | timestamp | Soft-delete timestamp (NULL = active)                        |

### `storage_drive_item_shares`
| Column                    | Type    | Description                                         |
|---------------------------|---------|-----------------------------------------------------|
| `id`                      | integer | Primary key                                         |
| `storage_drive_item_id`   | integer | FK → `storage_drive_items.id`                       |
| `shared_with_user_id`     | integer | FK → `users.id` (nullable if role share)            |
| `shared_with_role_id`     | integer | FK → `roles.id` (nullable if user share)            |
| `permission`              | enum    | `view` or `edit`                                    |

---

## 4. Permission System (3-Layer)

The module uses a **three-layer permission model**. All three layers must be satisfied for a user to perform an action.

### Layer 1 — Top-Level Admin
Permission key: `admin.can_manage_storage_drive.view`

A user with this permission bypasses all lower-level checks:
- Sees **all users' drives and folders** at the root level.
- Can manage and browse any drive.
- Can manage Trash for all users.
- All `canAdd`, `canEdit`, `canDelete`, `canView` flags are automatically `true`.

### Layer 2 — Menu-Level Permissions
Per sub-menu, granular CRUD flags are checked from the role/permission system:

| Sub-Menu         | Permission Prefix           | Flags available              |
|------------------|-----------------------------|------------------------------|
| My Storage       | `admin.my_storage`          | `.view` `.add` `.edit` `.delete` |
| Shared With Me   | `admin.shared_with_me`      | `.view` `.add` `.edit` `.delete` |
| Trashed Storage  | `admin.trashed_storage`     | `.view` `.add` `.edit` `.delete` |

General API endpoints (store folder, upload file, rename, delete, move, copy, share) accept a user if they hold the action in **either** `my_storage` **or** `shared_with_me`.

### Layer 3 — Share-Based Item-Level (PermissionService)
Implemented in `PermissionService.php`. Applies only to non-admin, non-owner users.

**Access resolution logic for any item:**
1. If the user owns the storage drive → full `edit` access.
2. Check direct share on the item itself.
3. Check **inherited** share from any **ancestor folder** (walk up the tree).
4. Highest permission wins (`edit` > `view`).
5. If no share found → no access.

**Permission levels:**
- `view` — can browse, read, and download the item.
- `edit` — can view + rename, move, copy, delete, and share the item.

**Special copy rule:** Copying only requires `view` on the source item (less restrictive than move/rename/delete, which require `edit`).

---

## 5. Sub-Menu: My Storage

**URL:** `/admin/my_storage/manage`  
**Controller:** `MyStorageController`  
**Route name:** `storage_drive.manage`

### 5.1 What Users See

The manage page is the main file-browser view. On page load:

1. The system auto-resolves the user's own drive (no drive ID required in the URL).
2. If `folder_id` is in the query string, that folder's contents are shown; otherwise the root is shown.
3. If `drive_id` or `item_id` is passed (e.g. from a Shared-With-Me link), the appropriate drive/folder is resolved.
4. Breadcrumb navigation is displayed from Root → current folder.

**What each user role sees at root:**
- **Top-Level Admin:** All users' root-level folders across all drives.
- **Drive Owner (regular user):** Only their own drive's root items.
- **Shared user (non-owner):** Only the root-level items they have been granted access to.

### 5.2 Item Types Displayed

Items are split into four groups and displayed separately:
- **Folders** — navigable containers.
- **Files** — uploaded binary files (PDF, images, Office docs, etc.).
- **Text Notes** — rich HTML content created with CKEditor.
- **Links** — external URLs saved as bookmarks.

### 5.3 Available Actions

| Action              | Req. Permission | Notes                                                                                         |
|---------------------|-----------------|-----------------------------------------------------------------------------------------------|
| **Create Folder**   | `add`           | Must be inside an existing folder (non-admin); admin can create at root.                      |
| **Upload File(s)**  | `add`           | Multiple files via Dropzone. Max file size configurable via `storage_drive_max_file_size` setting (default 5 MB). |
| **Add Text Note**   | `add`           | CKEditor content saved in `meta.content`. Size stored as content length in bytes.             |
| **Add Link**        | `add`           | Saves title + URL. Stored in `meta` and `path`.                                               |
| **Rename**          | `edit`          | Files preserve original extension automatically.                                              |
| **Move**            | `edit`          | Drag or modal. Cannot move a folder into itself or its own descendants.                       |
| **Copy**            | `add`           | Recursively copies folder tree; physical files are duplicated on disk with new UUID filenames.|
| **Delete (Soft)**   | `delete`        | Sends item(s) to Trash. Physical files stay on disk. Folders soft-deleted recursively.        |
| **Download Folder** | `view`          | Streams a `.zip` file. Text items exported as `.html`, links as `.url` (Internet Shortcut).  |
| **Download Selected** | `view`        | Zip of multiple selected items.                                                               |
| **View File**       | `view`          | Opens file inline in browser with correct MIME type.                                          |
| **Share Item**      | `edit`          | See Section 5.4.                                                                              |
| **Edit Text Note**  | `edit`          | Updates `meta.content` and recalculates `size`.                                               |
| **Edit Link**       | `edit`          | Updates `name`, `path`, and `meta`.                                                           |
| **Get Item Details**| `view`          | API endpoint returning item `id`, `name`, `type`, `path`, `meta`.                            |

### 5.4 Sharing Items

**Route:** `POST /admin/my_storage/share`  
**Who can share:** Drive owner, top-level admin, or any user with `edit` permission on the item.

**Share targets:**
- One or multiple **specific users**.
- One or multiple **roles** (everyone with that role gets access).
- Cannot mix users and roles in the same request.

**Permission levels:** `view` or `edit`.

**Duplicate prevention:** If the exact same share (same item + same user/role) already exists, it is skipped silently.

**Remove a share:** `POST /admin/my_storage/share/remove` with `share_id`. The drive owner's share cannot be removed.

**List shares:** `GET /admin/my_storage/share/list?storage_drive_item_id=X` — returns all active shares for an item.

### 5.5 Auto Rename on Name Conflict

When an item is moved, copied, or renamed to a name that already exists in the destination folder, the system automatically appends a numeric suffix:  
`report.pdf` → `report (1).pdf` → `report (2).pdf`, etc.

### 5.6 Folder Size Calculation

Folder sizes are computed **hierarchically**: a folder's displayed size is the sum of all descendant file sizes (recursively), calculated in-memory from two DB queries (no recursive SQL). Sizes are formatted to KB/MB/GB for display.

---

## 6. Sub-Menu: Shared With Me

**URL:** `/admin/shared_with_me/`  
**Controller:** `SharedWithMeController`  
**Route name:** `storage_drive.shared.with.me`

### 6.1 What Users See

The landing page lists all items (files, folders, text notes, links) that have been **explicitly shared** with the current user (directly or via role membership), from any drive they do not own.

Items are grouped by their source drive (i.e., by the drive owner).

### 6.2 Browsing Shared Content

**URL:** `/admin/shared_with_me/browse?drive_id=X&folder_id=Y`  
**Route name:** `storage_drive.shared.browse`

Clicking into a shared folder opens a browse view that:
- Uses **share-based permissions only** — admin/owner elevation is **never applied** here.
- Displays a breadcrumb trail showing only the ancestors the user can access.
- Lists only children the user has share-based access to.
- Displays each item's effective permission (`view` or `edit`).

### 6.3 Actions Available in Shared Browse

The shared browse view reuses the same manage template, but with `is_shared_mode = true`. Available actions depend on the item's effective permission:
- `view` permission: browse folders, view/download files and text notes, follow links.
- `edit` permission: same as view + rename, move, copy, delete (soft), share.

File viewing and folder download use dedicated routes:
- View file: `GET /admin/shared_with_me/file/{id}/view`
- Download folder: `GET /admin/shared_with_me/folder/{id}/download`

---

## 7. Sub-Menu: Trashed Storage

**URL:** `/admin/trashed_storage/`  
**Controller:** `TrashedStorageController`  
**Route name:** `storage_drive.trashed`

### 7.1 What Users See

A DataTable (server-side, with search and sort) listing all **soft-deleted** items:

| Column       | Description                                              |
|--------------|----------------------------------------------------------|
| Type icon    | Visual indicator of item type (folder, file ext, text, link) |
| Name         | Item name + type label                                   |
| Owner        | Full name of the drive owner                             |
| Size         | File/folder size (hierarchical for folders)              |
| Deleted On   | Soft-delete timestamp                                    |
| Actions      | Restore / Permanently Delete buttons                     |

**Visibility scoping:**
- **Top-Level Admin:** sees all trashed items across all users.
- **Regular user:** sees only items from their own drive.

The DataTable shows only **top-level** deleted items (items whose parent is not also in the trash), preventing duplicate display of folder children.

### 7.2 Restore

**Route:** `POST /admin/trashed_storage/restore`  
**Permission:** `trashed_storage.edit`

- Restores the item back to active state (`deleted_at = null`).
- For **folders**: restores recursively — the folder and all its soft-deleted descendants are restored.
- Non-admin users can only restore items from their own drive.

**Bulk Restore:** `POST /admin/trashed_storage/bulk-restore` — accepts an array of `ids`.

### 7.3 Permanent Delete

**Route:** `POST /admin/trashed_storage/permanent-delete`  
**Permission:** `trashed_storage.delete`

- **Permanently removes** the DB record (`forceDelete`) and deletes the physical file from disk.
- For **folders**: recursively force-deletes all descendants first, then the folder.
- All associated share records (`storage_drive_item_shares`) are also deleted.
- Non-admin users can only permanently delete items from their own drive.

**Bulk Permanent Delete:** `POST /admin/trashed_storage/bulk-permanent-delete` — accepts an array of `ids`.

> ⚠️ **Warning:** Permanent delete is irreversible. There is no recovery path once a file is permanently deleted.

---

## 8. Sub-Menu: Storage Analytics

**URL:** `/admin/storage_analytics`  
**Controller:** `StorageAnalyticsController`  
**Route name:** `storage_drive.analytics`

### 8.1 What the Dashboard Shows

An analytics overview of storage consumption across all users:

| Metric                  | Description                                                    |
|-------------------------|----------------------------------------------------------------|
| **Total Active Storage**   | Sum of all active (non-trashed) file sizes across all drives |
| **Total Trashed Storage**  | Sum of all soft-deleted file sizes across all drives         |
| **Total Combined Storage** | Active + Trashed                                             |
| **Total Drives**           | Number of provisioned drives                                 |
| **Per-User Active Usage**  | Breakdown of active bytes per user, sorted largest first     |
| **Per-User Trashed Usage** | Breakdown of trashed bytes per user, sorted largest first    |

### 8.2 How Data is Computed

- A single Eloquent query loads every drive with owner info and two aggregated sums:
  - `active_bytes`: sum of `size` for active (non-trashed) file items.
  - `trashed_bytes`: sum of `size` for soft-deleted file items.
- Results are grouped by `owner_id` in PHP, not SQL, to avoid type-casting issues.
- Users with 0 bytes in a category are excluded from that chart.

> **Note:** Only `file`-type items have a `size`. Folders, text notes, and links do not contribute to storage totals.

---

## 9. Route Reference

All routes are under the `/admin` prefix and protected by the `auth` middleware.

### My Storage Routes (`/admin/my_storage/`)

| Method | URI                             | Route Name                          | Action                          |
|--------|---------------------------------|-------------------------------------|---------------------------------|
| GET    | `manage`                        | `storage_drive.manage`              | Browse drive / folder           |
| POST   | `folder/store`                  | `storage_drive.folder.store`        | Create folder                   |
| POST   | `text/store`                    | `storage_drive.text.store`          | Create text note                |
| POST   | `text/update`                   | `storage_drive.text.update`         | Update text note                |
| POST   | `link/store`                    | `storage_drive.link.store`          | Create link                     |
| POST   | `link/update`                   | `storage_drive.link.update`         | Update link                     |
| POST   | `file/upload`                   | `storage_drive.file.upload`         | Upload file(s)                  |
| POST   | `item/rename`                   | `storage_drive.item.rename`         | Rename item                     |
| POST   | `item/delete`                   | `storage_drive.item.delete`         | Soft-delete item                |
| POST   | `item/move`                     | `storage_drive.item.move`           | Move item to folder             |
| POST   | `item/copy`                     | `storage_drive.item.copy`           | Copy item to folder             |
| GET    | `folders/list`                  | `storage_drive.folders.list`        | List folders (for move modal)   |
| GET    | `item/details`                  | `storage_drive.item.details`        | Get item details (API)          |
| GET    | `file/{id}/view`                | `storage_drive.file.view`           | View file inline                |
| GET    | `folder/{id}/download`          | `storage_drive.folder.download`     | Download folder as ZIP          |
| POST   | `selected/download`             | `storage_drive.selected.download`   | Download selected items as ZIP  |
| POST   | `duplicate/{id}`                | `storage_drive.duplicate`           | Duplicate entire drive          |
| GET    | `share/list`                    | `storage_drive.share.list`          | List shares for an item         |
| POST   | `share`                         | `storage_drive.share.item`          | Share item with user(s)/role(s) |
| POST   | `share/remove`                  | `storage_drive.share.remove`        | Remove a share record           |

### Shared With Me Routes (`/admin/shared_with_me/`)

| Method | URI              | Route Name                          | Action                     |
|--------|------------------|-------------------------------------|----------------------------|
| GET    | `/`              | `storage_drive.shared.with.me`      | Shared items landing page  |
| GET    | `browse`         | `storage_drive.shared.browse`       | Browse shared drive/folder |
| GET    | `file/{id}/view` | `shared_with_me.file.view`          | View shared file inline    |
| GET    | `folder/{id}/download` | `shared_with_me.folder.download` | Download shared folder as ZIP |

### Trashed Storage Routes (`/admin/trashed_storage/`)

| Method | URI                    | Route Name                              | Action                      |
|--------|------------------------|-----------------------------------------|-----------------------------|
| GET    | `/`                    | `storage_drive.trashed`                 | Trashed items DataTable     |
| POST   | `restore`              | `storage_drive.item.restore`            | Restore single item         |
| POST   | `permanent-delete`     | `storage_drive.item.permanent_delete`   | Permanently delete item     |
| POST   | `bulk-restore`         | `storage_drive.item.bulk_restore`       | Bulk restore                |
| POST   | `bulk-permanent-delete`| `storage_drive.item.bulk_permanent_delete` | Bulk permanent delete    |

### Storage Analytics Route

| Method | URI                  | Route Name                    | Action             |
|--------|----------------------|-------------------------------|--------------------|
| GET    | `storage_analytics`  | `storage_drive.analytics`     | Analytics dashboard|

---

## 10. File Storage on Disk

Physical files uploaded by users are stored on the `storage_drive` filesystem disk (configured in `config/filesystems.php`).

**Directory structure on disk:**
```
{storage_drive_disk_root}/
└── {storage_drive_id}/
    └── files/
        └── {YYYY}/
            └── {MM}/
                └── {DD}/
                    └── {uuid}.{ext}
```

- Each file gets a **UUID-based filename** to prevent name collisions on disk.
- The original user-facing filename is stored in `storage_drive_items.name`.
- The disk path (`storage_drive_items.path`) is the relative path under the disk root.
- **Soft-deleted files remain on disk** until permanently deleted.
- On permanent delete, the physical file is removed with `Storage::disk('storage_drive')->delete($path)`.

**Temporary ZIP files** for downloads are written to the `local` disk under `tmp/storage_drive_zips/` and deleted immediately after the response is sent.

---

## 11. Supported Item Types

| Type     | DB `type` value | Storage           | Download format      | Notes                             |
|----------|-----------------|-------------------|----------------------|-----------------------------------|
| Folder   | `folder`        | DB only (no file) | ZIP (recursive)      | Can be nested to any depth        |
| File     | `file`          | Disk + DB         | Original file        | See allowed extensions below      |
| Text Note | `text`         | DB (`meta.content`)| `.html` file        | Editable via CKEditor             |
| Link     | `link`          | DB (`path` + `meta`) | `.url` file (Internet Shortcut) | Clicking opens external URL |

---

## 12. Supported File Types for Upload

The upload validator enforces both MIME type and extension:

| Category | Extensions                                      |
|----------|-------------------------------------------------|
| Documents | `pdf`, `doc`, `docx`, `xls`, `xlsx`, `ppt`, `pptx` |
| Images    | `jpg`, `jpeg`, `png`, `gif`, `webp`             |
| Text/Data | `txt`, `csv`                                    |
| Archive   | `zip`                                           |

**Max file size:** Configurable via the `storage_drive_max_file_size` system setting (in MB). Defaults to **5 MB** if not set.

---

## 13. End-to-End Workflow Diagram

```
User logs in
     │
     ▼
[My Storage — manage page]
     │
     ├── Auto-resolves user's own drive (owner_id = auth user)
     │
     ├── Browse folders / Navigate via breadcrumbs
     │       └── URL: ?folder_id=N
     │
     ├── Create items
     │       ├── New Folder    → POST storage_drive.folder.store
     │       ├── Upload File   → POST storage_drive.file.upload
     │       ├── Text Note     → POST storage_drive.text.store
     │       └── Add Link      → POST storage_drive.link.store
     │
     ├── Manage items
     │       ├── Rename        → POST storage_drive.item.rename
     │       ├── Move          → POST storage_drive.item.move
     │       ├── Copy          → POST storage_drive.item.copy
     │       ├── Download      → GET  storage_drive.folder.download
     │       │                   POST storage_drive.selected.download
     │       └── View File     → GET  storage_drive.file.view
     │
     ├── Share items
     │       ├── Share with user(s) or role(s)
     │       │       └── POST storage_drive.share.item
     │       │             (permission: view or edit)
     │       └── Remove share  → POST storage_drive.share.remove
     │
     └── Soft-delete items
             └── POST storage_drive.item.delete
                     │
                     ▼
             [Trashed Storage]
                     │
                     ├── Restore     → POST storage_drive.item.restore
                     │                 (folder: restores all descendants)
                     └── Perm. Delete → POST storage_drive.item.permanent_delete
                                        (file removed from disk; irreversible)


[Shared With Me page]
     │
     ├── Lists items shared with current user (across all drives)
     ├── Grouped by drive owner
     │
     └── Browse shared folder → GET storage_drive.shared.browse
             │
             ├── Share-based permissions only (no admin elevation)
             ├── Breadcrumb shows only accessible ancestors
             └── Actions limited to effective permission (view or edit)


[Storage Analytics page]
     └── Per-user active and trashed storage usage
         Sorted by size descending
```

---

## 14. Developer Notes & Gotchas

- **One drive per user.** A user's drive is never auto-created by the `manage` action — it must be provisioned separately (e.g. via admin or `StorageDrive::create()`). If no drive exists, the manage page renders an empty state.

- **Admin root view.** When a top-level admin views the root (`folder_id = null`), they see root-level items from **all drives** (not just their own). This is intentional for full oversight.

- **System folders.** Items marked as system folders (`isSystemFolder()`) cannot be renamed, moved, or deleted, even by admins.

- **Non-admin root restriction.** Regular users cannot create, move, copy, or delete items at the root level (only inside folders). Root-level operations require admin.

- **Parent chain permission inheritance.** A share on a parent folder grants access to all descendants. This is resolved in `PermissionService::getAncestorIds()` using an in-memory parent map (single query per drive per request, cached statically).

- **Folder size computation.** Done in-memory via `computeFolderSizes()` (2 queries). Uses a recursive closure with a local cache to avoid redundant traversal.

- **Duplicate name handling.** `getUniqueNameInFolder()` appends `(1)`, `(2)`, etc. automatically on rename, move, or copy — the user does not need to do anything.

- **Zip temp files.** Stored under Laravel's `local` disk at `tmp/storage_drive_zips/` and deleted after send via `deleteFileAfterSend(true)`. If a request fails mid-stream, the temp file may remain and should be cleaned up by a scheduled job.

- **Soft-delete vs. permanent delete.** `delete()` = soft-delete (Laravel's `SoftDeletes`; row remains with `deleted_at` set). `forceDelete()` = permanent (row removed + physical file deleted). Share records are cleaned up only on permanent delete.

- **Share deduplication.** `PermissionService::shareExists()` prevents creating duplicate share records. The `shareItem` method silently increments a `$skipped` counter for already-existing shares and returns an appropriate message.

- **Trashed DataTable top-level filter.** The query in `TrashedStorageController::trashed()` filters to show only top-level deleted items — items whose `parent_id` is not itself a soft-deleted item. This prevents showing the same content twice when a whole folder tree is deleted.

- **Copy vs. Move permissions.** `copy` requires `view` on source + `edit` on destination. `move` requires `edit` on source. This asymmetry is intentional: read-only users can copy to places they can write.
