Files
Oxicloud/doc/favorites-and-recent.md
T

172 lines
5.5 KiB
Markdown
Raw Normal View History

# 13 - Favorites and Recent Items
Two per-user item tracking features:
- **Favorites** -- users mark files and folders for quick access.
- **Recent Items** -- automatically tracks recently accessed files and folders.
Both require PostgreSQL and are only available when a database connection is configured.
---
## Favorites
### Architecture
| Layer | Component | File |
|---|---|---|
| Application Port | **FavoritesUseCase**, **FavoritesRepositoryPort** | `src/application/ports/favorites_ports.rs` |
| Application Service | **FavoritesService** | `src/application/services/favorites_service.rs` |
| Application DTO | **FavoriteItemDto** | `src/application/dtos/favorites_dto.rs` |
| Infrastructure | **FavoritesPgRepository** | `src/infrastructure/repositories/pg/favorites_pg_repository.rs` |
| Interfaces | `favorites_handler` (free functions) | `src/interfaces/api/handlers/favorites_handler.rs` |
### DTO
```rust
pub struct FavoriteItemDto {
pub id: String,
pub user_id: String,
pub item_id: String,
pub item_type: String, // "file" | "folder"
pub created_at: DateTime<Utc>,
}
```
### REST API
All routes under `/api/favorites`, require authentication. User ID comes from the JWT token.
| Method | Path | Handler | Description |
|---|---|---|---|
| `GET` | `/api/favorites/` | `get_favorites` | List all favorites for current user |
| `POST` | `/api/favorites/{item_type}/{item_id}` | `add_favorite` | Add a file or folder to favorites |
| `DELETE` | `/api/favorites/{item_type}/{item_id}` | `remove_favorite` | Remove from favorites |
- `item_type` must be `"file"` or `"folder"` (validated by service)
- Adding a duplicate is idempotent (`ON CONFLICT DO NOTHING`)
- Results ordered by `created_at DESC`
### Database Schema
```sql
CREATE TABLE IF NOT EXISTS auth.user_favorites (
id SERIAL PRIMARY KEY,
user_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
item_id TEXT NOT NULL,
item_type TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, item_id, item_type)
);
```
Indexes: `user_id`, `item_id`, `item_type`, `created_at`, composite `(user_id, item_type)`.
### Example
```bash
# Add file to favorites
curl -X POST -H "Authorization: Bearer $TOKEN" \
"https://oxicloud.example.com/api/favorites/file/abc-123"
# List favorites
curl -H "Authorization: Bearer $TOKEN" \
"https://oxicloud.example.com/api/favorites/"
# Remove from favorites
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://oxicloud.example.com/api/favorites/folder/def-456"
```
---
## Recent Items
### Architecture
| Layer | Component | File |
|---|---|---|
| Application Port | **RecentItemsUseCase**, **RecentItemsRepositoryPort** | `src/application/ports/recent_ports.rs` |
| Application Service | **RecentService** | `src/application/services/recent_service.rs` |
| Application DTO | **RecentItemDto** | `src/application/dtos/recent_dto.rs` |
| Infrastructure | **RecentItemsPgRepository** | `src/infrastructure/repositories/pg/recent_items_pg_repository.rs` |
| Interfaces | `recent_handler` (free functions) | `src/interfaces/api/handlers/recent_handler.rs` |
### DTO
```rust
pub struct RecentItemDto {
pub id: String,
pub user_id: String,
pub item_id: String,
pub item_type: String, // "file" | "folder"
pub accessed_at: DateTime<Utc>,
}
```
### REST API
All routes under `/api/recent`, require authentication.
| Method | Path | Handler | Description |
|---|---|---|---|
| `GET` | `/api/recent/` | `get_recent_items` | List recent items (optional `?limit=N`) |
| `POST` | `/api/recent/{item_type}/{item_id}` | `record_item_access` | Record an access (upsert) |
| `DELETE` | `/api/recent/{item_type}/{item_id}` | `remove_from_recent` | Remove specific item |
| `DELETE` | `/api/recent/clear` | `clear_recent_items` | Clear all recent items |
### Behavior
- **Max items per user**: 50 (configured in DI, clamped to 1-100)
- **Upsert**: re-accessing an item updates its `accessed_at` timestamp
- **Auto-prune**: after recording access, old items beyond the limit are automatically pruned
- **Ordering**: results ordered by `accessed_at DESC`
- **Limit parameter**: `?limit=N` caps results (defaults to and cannot exceed **max_recent_items**)
### Database Schema
```sql
CREATE TABLE IF NOT EXISTS auth.user_recent_files (
id SERIAL PRIMARY KEY,
user_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
item_id TEXT NOT NULL,
item_type TEXT NOT NULL,
accessed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, item_id, item_type)
);
```
Indexes: `user_id`, `item_id`, `item_type`, `accessed_at`, composite `(user_id, accessed_at DESC)`.
### Example
```bash
# Record file access
curl -X POST -H "Authorization: Bearer $TOKEN" \
"https://oxicloud.example.com/api/recent/file/abc-123"
# Get recent items (last 10)
curl -H "Authorization: Bearer $TOKEN" \
"https://oxicloud.example.com/api/recent/?limit=10"
# Clear history
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://oxicloud.example.com/api/recent/clear"
```
## DI Wiring
Both services require PostgreSQL:
```rust
// Favorites
let repo = Arc::new(FavoritesPgRepository::new(db_pool.clone()));
let favorites_service = Arc::new(FavoritesService::new(repo));
// Recent
let repo = Arc::new(RecentItemsPgRepository::new(db_pool.clone()));
let recent_service = Arc::new(RecentService::new(repo, 50)); // max 50 items
```
Stored as `Option<Arc<...>>` in **AppState** -- only available when DB is connected.