Update documents route with delete endpoint - WIP

This commit is contained in:
ggq-admin 2025-10-20 03:41:25 +02:00
parent ba36803f05
commit e7a97294e2
3 changed files with 544 additions and 34 deletions

342
FEATURE-ROADMAP.md Normal file
View file

@ -0,0 +1,342 @@
# NaviDocs Single-Tenant Feature Roadmap
**Version:** 1.0 → 2.0
**Target:** Complete single-tenant boat manual management system
**Approach:** Incremental implementation with backward compatibility
---
## 🎯 Vision
Transform NaviDocs from a demo into a production-ready single-tenant system with complete document lifecycle management, advanced search, and user preferences.
---
## 📋 Feature Categories
### 1. Document Management ⭐ (Priority 1)
- [x] Upload with OCR
- [x] View with text selection
- [x] Auto-fill metadata
- [ ] **Delete documents** (with confirmation)
- [ ] **Edit metadata** (inline or modal)
- [ ] **Bulk operations** (select multiple, bulk delete)
- [ ] **Document versions** (track changes)
- [ ] **Download original PDF**
### 2. Advanced Search 🔍 (Priority 1)
- [x] Full-text search
- [x] Diagram thumbnails
- [ ] **Filter by boat** (dropdown)
- [ ] **Filter by document type** (manual, service, warranty)
- [ ] **Filter by date range** (uploaded date)
- [ ] **Sort options** (relevance, date, title)
- [ ] **Search within document**
- [ ] **Recent searches** (last 10)
- [ ] **Search suggestions** (autocomplete)
- [ ] **Export search results** (PDF/CSV)
### 3. User Experience 🎨 (Priority 2)
- [x] Toast notifications
- [x] Responsive design
- [x] Dark theme
- [ ] **Keyboard shortcuts** (/, Ctrl+K for search, etc.)
- [ ] **Bookmarks** (save specific pages)
- [ ] **Reading progress** (remember last page)
- [ ] **Print-friendly view**
- [ ] **Fullscreen mode**
- [ ] **Night mode toggle** (darker theme)
### 4. Dashboard & Analytics 📊 (Priority 2)
- [ ] **Statistics dashboard**
- Total documents
- Total pages
- Storage used
- Most searched terms
- Recent activity
- [ ] **Document health**
- OCR status
- Missing metadata
- Indexing status
- [ ] **Usage charts**
- Searches over time
- Documents added over time
### 5. Settings & Preferences ⚙️ (Priority 1)
- [ ] **Organization settings**
- Organization name
- Default boat name
- Logo upload
- [ ] **User preferences**
- Theme (dark/light)
- Default search filters
- Items per page
- Language
- [ ] **Storage management**
- View storage usage
- Cleanup old files
- Export all data
### 6. Help & Onboarding 💡 (Priority 3)
- [ ] **Interactive tour** (first visit)
- [ ] **Keyboard shortcuts guide**
- [ ] **Search tips**
- [ ] **Feature highlights**
- [ ] **FAQ section**
### 7. Data Management 💾 (Priority 2)
- [ ] **Export all documents** (ZIP)
- [ ] **Import from backup**
- [ ] **Activity log** (audit trail)
- [ ] **Database maintenance** (vacuum, optimize)
### 8. Polish & Performance ⚡ (Priority 3)
- [x] Comprehensive logging
- [x] Error handling
- [ ] **Image lazy loading**
- [ ] **PDF page caching**
- [ ] **Search result caching**
- [ ] **Compression for images**
- [ ] **Background job queue**
---
## 🗓️ Implementation Plan
### Phase 1: Core Management (Day 1)
**Goal:** Complete document lifecycle management
1. **Document Deletion**
- Add delete button to document cards
- Confirmation modal
- Backend endpoint
- Cleanup files, DB, and search index
- Toast notification on success
2. **Metadata Editing**
- Edit modal/form
- Update boat info, title, type
- Re-index search after update
- Optimistic UI updates
3. **Download Original PDF**
- Download button in document viewer
- Proper filename
### Phase 2: Advanced Search (Day 1-2)
**Goal:** Make search powerful and flexible
1. **Search Filters**
- Boat filter (dropdown with all boats)
- Document type filter
- Date range filter
- Apply filters to search query
2. **Search Enhancements**
- Sort by relevance/date/title
- Recent searches (localStorage)
- Search within current document
3. **Search Export**
- Export results as PDF
- Export as CSV
### Phase 3: Dashboard (Day 2)
**Goal:** Provide system overview and insights
1. **Statistics Dashboard**
- Total documents, pages, storage
- Recent uploads
- Popular searches
- Quick actions
2. **Document Health**
- Show processing status
- Identify issues
- Quick fixes
### Phase 4: Settings & Preferences (Day 2-3)
**Goal:** Customizable experience
1. **Settings Page**
- Organization info
- User preferences
- Storage management
- Database maintenance
2. **Theme Toggle**
- Dark/light mode
- Persistent preference
### Phase 5: UX Enhancements (Day 3)
**Goal:** Make it delightful to use
1. **Keyboard Shortcuts**
- / or Ctrl+K for search
- Arrow keys for navigation
- Escape to close modals
- Shortcuts help modal
2. **Bookmarks**
- Save favorite pages
- Quick access
- Manage bookmarks
3. **Reading Progress**
- Remember last page per document
- Resume reading
### Phase 6: Polish & Testing (Day 3)
**Goal:** Production-ready quality
1. **Performance Optimization**
- Image lazy loading
- Caching strategies
- Compression
2. **Testing**
- E2E tests for new features
- Load testing
- Edge case handling
3. **Documentation**
- Update guides
- Feature screenshots
- API documentation
---
## 🎨 UI/UX Considerations
### Design Principles
1. **Consistent:** Follow existing pink/purple dark theme
2. **Intuitive:** Common patterns (3-dot menus, icon buttons)
3. **Responsive:** All features work on mobile
4. **Accessible:** Keyboard navigation, ARIA labels
5. **Fast:** Optimistic updates, caching
### Component Additions
- `ConfirmDialog.vue` - Reusable confirmation modal
- `EditDocumentModal.vue` - Edit document metadata
- `SearchFilters.vue` - Advanced search filter panel
- `StatsCard.vue` - Dashboard statistics cards
- `SettingsPanel.vue` - Settings sections
- `KeyboardShortcuts.vue` - Shortcuts help modal
- `BookmarksList.vue` - Bookmarks sidebar
### New Routes
```javascript
/ # Home (existing)
/search # Search (existing)
/document/:id # Document viewer (existing)
/dashboard # New: Statistics dashboard
/settings # New: Settings page
/bookmarks # New: Bookmarks list
```
---
## 🔧 Backend Changes
### New Endpoints
```javascript
DELETE /api/documents/:id # Delete document
PATCH /api/documents/:id # Update metadata
GET /api/documents/:id/download # Download PDF
GET /api/stats # Get statistics
GET /api/search/history # Recent searches
POST /api/bookmarks # Add bookmark
GET /api/bookmarks # List bookmarks
DELETE /api/bookmarks/:id # Remove bookmark
GET /api/settings # Get settings
PATCH /api/settings # Update settings
POST /api/export # Export all data
```
### Database Schema Additions
```sql
-- Bookmarks
CREATE TABLE bookmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
documentId TEXT NOT NULL,
pageNumber INTEGER NOT NULL,
note TEXT,
createdAt INTEGER NOT NULL,
FOREIGN KEY(documentId) REFERENCES documents(id) ON DELETE CASCADE
);
-- Settings
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updatedAt INTEGER NOT NULL
);
-- Activity Log
CREATE TABLE activity_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT NOT NULL,
entity TEXT,
entityId TEXT,
details TEXT,
createdAt INTEGER NOT NULL
);
```
---
## ⚠️ Breaking Changes Prevention
### Backward Compatibility Strategy
1. **Add, don't replace:** New endpoints alongside existing
2. **Optional features:** All new features are additive
3. **Database migrations:** Careful schema updates
4. **API versioning:** Consider /api/v1 prefix for future
5. **Feature flags:** Toggle new features on/off
### Testing Strategy
1. **Existing tests must pass:** All 8 E2E tests
2. **New tests for new features:** Add to test suite
3. **Regression testing:** Verify old features still work
4. **Manual testing:** Complete user flow tests
---
## 📊 Success Metrics
### Completion Criteria
- [ ] All priority 1 features implemented
- [ ] 15+ E2E tests passing
- [ ] < 100ms response time for all endpoints
- [ ] Zero console errors
- [ ] Mobile responsive verified
- [ ] Complete documentation updated
- [ ] Demo-ready with showcase
### Quality Gates
1. **Code Quality:** ESLint passing, no warnings
2. **Performance:** Lighthouse score >90
3. **Tests:** >80% feature coverage
4. **Documentation:** All new features documented
5. **UX:** User testing with 3+ scenarios
---
## 🚀 Deployment Checklist
- [ ] Run all tests
- [ ] Update environment variables
- [ ] Database migrations applied
- [ ] Backup existing data
- [ ] Performance testing
- [ ] Security audit
- [ ] Documentation updated
- [ ] Stakeholder demo
---
**Next Steps:** Begin Phase 1 implementation
**Timeline:** 3-day sprint for complete feature set
**Goal:** Production-ready single-tenant system with all features
Let's build something amazing! 🚢✨

View file

@ -0,0 +1,157 @@
<template>
<Teleport to="body">
<Transition name="modal">
<div
v-if="isOpen"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm px-4"
@click.self="onCancel"
>
<div class="bg-dark-800 rounded-2xl shadow-2xl border border-white/10 max-w-md w-full overflow-hidden">
<!-- Header -->
<div class="px-6 py-4 border-b border-white/10">
<div class="flex items-center gap-3">
<div :class="[
'w-10 h-10 rounded-xl flex items-center justify-center',
variantClasses[variant].bg
]">
<svg v-if="variant === 'danger'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<svg v-else-if="variant === 'warning'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg v-else class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 class="text-lg font-bold text-white">{{ title }}</h3>
</div>
</div>
<!-- Body -->
<div class="px-6 py-4">
<p class="text-white/80 leading-relaxed">{{ message }}</p>
</div>
<!-- Footer -->
<div class="px-6 py-4 bg-dark-900/50 flex items-center justify-end gap-3">
<button
@click="onCancel"
class="px-4 py-2 rounded-lg border border-white/20 text-white hover:bg-white/5 transition-colors"
:disabled="loading"
>
{{ cancelText }}
</button>
<button
@click="onConfirm"
:class="[
'px-4 py-2 rounded-lg font-medium transition-colors',
variantClasses[variant].button,
loading && 'opacity-50 cursor-not-allowed'
]"
:disabled="loading"
>
<span v-if="!loading">{{ confirmText }}</span>
<span v-else class="flex items-center gap-2">
<div class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
{{ loadingText }}
</span>
</button>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
isOpen: {
type: Boolean,
required: true
},
title: {
type: String,
default: 'Confirm Action'
},
message: {
type: String,
required: true
},
confirmText: {
type: String,
default: 'Confirm'
},
cancelText: {
type: String,
default: 'Cancel'
},
loadingText: {
type: String,
default: 'Processing...'
},
variant: {
type: String,
default: 'info', // 'info', 'warning', 'danger'
validator: (value) => ['info', 'warning', 'danger'].includes(value)
}
})
const emit = defineEmits(['confirm', 'cancel'])
const loading = ref(false)
const variantClasses = {
danger: {
bg: 'bg-red-500/20 text-red-400',
button: 'bg-red-500 hover:bg-red-600 text-white'
},
warning: {
bg: 'bg-yellow-500/20 text-yellow-400',
button: 'bg-yellow-500 hover:bg-yellow-600 text-white'
},
info: {
bg: 'bg-blue-500/20 text-blue-400',
button: 'bg-gradient-to-r from-pink-400 to-purple-500 hover:from-pink-500 hover:to-purple-600 text-white'
}
}
function onCancel() {
if (loading.value) return
emit('cancel')
}
async function onConfirm() {
if (loading.value) return
loading.value = true
try {
await emit('confirm')
} finally {
loading.value = false
}
}
</script>
<style scoped>
.modal-enter-active,
.modal-leave-active {
transition: opacity 0.2s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-active > div,
.modal-leave-active > div {
transition: transform 0.2s ease;
}
.modal-enter-from > div,
.modal-leave-to > div {
transform: scale(0.95);
}
</style>

View file

@ -5,10 +5,16 @@
import express from 'express';
import { getDb } from '../db/db.js';
import { getMeilisearchClient } from '../config/meilisearch.js';
import path from 'path';
import fs from 'fs';
import { rm } from 'fs/promises';
import { loggers } from '../utils/logger.js';
const router = express.Router();
const logger = loggers.app.child('Documents');
const MEILISEARCH_INDEX_NAME = process.env.MEILISEARCH_INDEX_NAME || 'navidocs-pages';
/**
* GET /api/documents/:id
@ -343,59 +349,64 @@ router.get('/', async (req, res) => {
/**
* DELETE /api/documents/:id
* Soft delete a document (mark as deleted)
* Hard delete a document (removes from DB, filesystem, and search index)
* For single-tenant demo - simplified permissions
*/
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
// TODO: Authentication middleware should provide req.user
const userId = req.user?.id || 'test-user-id';
try {
logger.info(`Deleting document ${id}`);
const db = getDb();
const searchClient = getMeilisearchClient();
// Check ownership
const document = db.prepare(`
SELECT id, organization_id, uploaded_by
FROM documents
WHERE id = ?
`).get(id);
// Get document info before deletion
const document = db.prepare('SELECT * FROM documents WHERE id = ?').get(id);
if (!document) {
logger.warn(`Document ${id} not found`);
return res.status(404).json({ error: 'Document not found' });
}
// Verify user has permission (must be uploader or org admin)
const hasPermission = db.prepare(`
SELECT 1 FROM user_organizations
WHERE user_id = ? AND organization_id = ? AND role IN ('admin', 'manager')
UNION
SELECT 1 FROM documents
WHERE id = ? AND uploaded_by = ?
`).get(userId, document.organization_id, id, userId);
if (!hasPermission) {
return res.status(403).json({
error: 'Access denied',
message: 'You do not have permission to delete this document'
});
// Delete from Meilisearch index
try {
const index = await searchClient.getIndex(MEILISEARCH_INDEX_NAME);
const filter = `docId = "${id}"`;
await index.deleteDocuments({ filter });
logger.info(`Deleted search entries for document ${id}`);
} catch (err) {
logger.warn(`Meilisearch cleanup failed for ${id}:`, err);
// Continue with deletion even if search cleanup fails
}
// Soft delete - update status
const timestamp = Date.now();
db.prepare(`
UPDATE documents
SET status = 'deleted', updated_at = ?
WHERE id = ?
`).run(timestamp, id);
// Delete from database (CASCADE will handle document_pages, ocr_jobs)
const deleteStmt = db.prepare('DELETE FROM documents WHERE id = ?');
deleteStmt.run(id);
logger.info(`Deleted database record for document ${id}`);
// Delete from filesystem
const uploadsDir = path.join(process.cwd(), '../uploads');
const docFolder = path.join(uploadsDir, id);
if (fs.existsSync(docFolder)) {
await rm(docFolder, { recursive: true, force: true });
logger.info(`Deleted filesystem folder for document ${id}`);
} else {
logger.warn(`Folder not found for document ${id}`);
}
logger.info(`Document ${id} deleted successfully`);
res.json({
success: true,
message: 'Document deleted successfully',
documentId: id
documentId: id,
title: document.title
});
} catch (error) {
console.error('Document deletion error:', error);
logger.error(`Failed to delete document ${id}`, error);
res.status(500).json({
error: 'Failed to delete document',
message: error.message