Add comprehensive database setup and user management system

- Implement PostgreSQL database schema with users and bookmarks tables
- Add database connection pooling with retry logic and error handling
- Create migration system with automatic schema initialization
- Add database CLI tools for management (init, status, validate, etc.)
- Include comprehensive error handling and diagnostics
- Add development seed data and testing utilities
- Implement health monitoring and connection pool statistics
- Create detailed documentation and troubleshooting guide

Database features:
- Users table with authentication fields and email verification
- Bookmarks table with user association and metadata
- Proper indexes for performance optimization
- Automatic timestamp triggers
- Transaction support with rollback handling
- Connection pooling (20 max connections, 30s idle timeout)
- Graceful shutdown handling

CLI commands available:
- npm run db:init - Initialize database
- npm run db:status - Check database status
- npm run db:validate - Validate schema
- npm run db:test - Run database tests
- npm run db:diagnostics - Full diagnostics
This commit is contained in:
2025-07-19 23:21:50 +02:00
commit 0abee5b794
66 changed files with 45023 additions and 0 deletions

View File

@ -0,0 +1,339 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Organization Features - Bookmark Manager</title>
<link rel="stylesheet" href="styles.css">
<style>
.test-section {
background: white;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-section h3 {
color: #2c3e50;
margin-bottom: 15px;
}
.test-button {
margin: 5px;
padding: 10px 15px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.test-button:hover {
background: #2980b9;
}
.test-results {
margin-top: 15px;
padding: 10px;
background: #f8f9fa;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Organization Features Test</h1>
</header>
<div class="test-section">
<h3>Test Data Setup</h3>
<button class="test-button" onclick="createTestBookmarks()">Create Test Bookmarks</button>
<button class="test-button" onclick="clearTestData()">Clear Test Data</button>
<div id="setupResults" class="test-results"></div>
</div>
<div class="test-section">
<h3>Drag and Drop Test</h3>
<p>After creating test bookmarks, try dragging bookmarks between folders to test the drag-and-drop functionality.</p>
<button class="test-button" onclick="testDragAndDrop()">Test Programmatic Move</button>
<div id="dragDropResults" class="test-results"></div>
</div>
<div class="test-section">
<h3>Bulk Operations Test</h3>
<button class="test-button" onclick="testBulkMode()">Toggle Bulk Mode</button>
<button class="test-button" onclick="testBulkSelection()">Test Bulk Selection</button>
<button class="test-button" onclick="testBulkMove()">Test Bulk Move</button>
<div id="bulkResults" class="test-results"></div>
</div>
<div class="test-section">
<h3>Sorting Test</h3>
<button class="test-button" onclick="testSortByTitle()">Sort by Title</button>
<button class="test-button" onclick="testSortByDate()">Sort by Date</button>
<button class="test-button" onclick="testSortByFolder()">Sort by Folder</button>
<div id="sortResults" class="test-results"></div>
</div>
<div class="test-section">
<h3>Folder Management Test</h3>
<button class="test-button" onclick="testCreateFolder()">Create Test Folder</button>
<button class="test-button" onclick="testRenameFolder()">Rename Folder</button>
<button class="test-button" onclick="testMergeFolder()">Merge Folders</button>
<button class="test-button" onclick="testDeleteFolder()">Delete Folder</button>
<div id="folderResults" class="test-results"></div>
</div>
<!-- Include the main bookmark manager interface -->
<div id="bookmarksList" class="bookmarks-list"></div>
<!-- Bulk Actions Bar -->
<div id="bulkActions" class="bulk-actions" style="display: none;">
<div class="bulk-selection-info">
<span class="selection-count">0 selected</span>
</div>
<div class="bulk-action-buttons">
<select id="bulkMoveFolder" class="bulk-folder-select">
<option value="">Move to folder...</option>
</select>
<button id="bulkMoveBtn" class="btn btn-secondary" disabled>Move</button>
<button id="bulkDeleteBtn" class="btn btn-danger" disabled>Delete Selected</button>
<button id="bulkSelectAllBtn" class="btn btn-secondary">Select All</button>
<button id="bulkClearSelectionBtn" class="btn btn-secondary">Clear Selection</button>
</div>
</div>
</div>
<script src="script.js"></script>
<script>
let testManager;
// Initialize test manager
document.addEventListener('DOMContentLoaded', () => {
testManager = new BookmarkManager();
});
function createTestBookmarks() {
const testBookmarks = [
{
id: Date.now() + 1,
title: 'Google',
url: 'https://www.google.com',
folder: 'Search Engines',
addDate: Date.now() - 86400000, // 1 day ago
icon: '',
status: 'unknown'
},
{
id: Date.now() + 2,
title: 'GitHub',
url: 'https://github.com',
folder: 'Development',
addDate: Date.now() - 172800000, // 2 days ago
icon: '',
status: 'unknown'
},
{
id: Date.now() + 3,
title: 'Stack Overflow',
url: 'https://stackoverflow.com',
folder: 'Development',
addDate: Date.now() - 259200000, // 3 days ago
icon: '',
status: 'unknown'
},
{
id: Date.now() + 4,
title: 'YouTube',
url: 'https://www.youtube.com',
folder: 'Entertainment',
addDate: Date.now(),
icon: '',
status: 'unknown'
},
{
id: Date.now() + 5,
title: 'Wikipedia',
url: 'https://www.wikipedia.org',
folder: '', // No folder
addDate: Date.now() - 345600000, // 4 days ago
icon: '',
status: 'unknown'
}
];
testManager.bookmarks = testBookmarks;
testManager.saveBookmarksToStorage();
testManager.renderBookmarks();
testManager.updateStats();
document.getElementById('setupResults').textContent =
`Created ${testBookmarks.length} test bookmarks across multiple folders.`;
}
function clearTestData() {
testManager.bookmarks = [];
testManager.saveBookmarksToStorage();
testManager.renderBookmarks();
testManager.updateStats();
document.getElementById('setupResults').textContent = 'Test data cleared.';
}
function testDragAndDrop() {
if (testManager.bookmarks.length === 0) {
document.getElementById('dragDropResults').textContent = 'No bookmarks to test. Create test bookmarks first.';
return;
}
// Test programmatic move
const bookmark = testManager.bookmarks[0];
const oldFolder = bookmark.folder;
const newFolder = 'Test Folder';
testManager.moveBookmarkToFolder(bookmark.id, newFolder);
document.getElementById('dragDropResults').textContent =
`Moved "${bookmark.title}" from "${oldFolder || 'Uncategorized'}" to "${newFolder}". Check the UI to verify the change.`;
}
function testBulkMode() {
testManager.toggleBulkMode();
const isActive = testManager.bulkMode;
document.getElementById('bulkResults').textContent =
`Bulk mode ${isActive ? 'activated' : 'deactivated'}. ${isActive ? 'Checkboxes should now be visible on bookmark items.' : 'Checkboxes should be hidden.'}`;
}
function testBulkSelection() {
if (!testManager.bulkMode) {
document.getElementById('bulkResults').textContent = 'Bulk mode must be active first.';
return;
}
if (testManager.bookmarks.length === 0) {
document.getElementById('bulkResults').textContent = 'No bookmarks to select. Create test bookmarks first.';
return;
}
// Select first two bookmarks
const firstTwo = testManager.bookmarks.slice(0, 2);
firstTwo.forEach(bookmark => {
testManager.toggleBookmarkSelection(bookmark.id);
});
document.getElementById('bulkResults').textContent =
`Selected ${firstTwo.length} bookmarks: ${firstTwo.map(b => b.title).join(', ')}`;
}
function testBulkMove() {
if (testManager.bulkSelection.size === 0) {
document.getElementById('bulkResults').textContent = 'No bookmarks selected. Use bulk selection test first.';
return;
}
const targetFolder = 'Bulk Moved';
const selectedCount = testManager.bulkSelection.size;
testManager.bulkMoveToFolder(targetFolder);
document.getElementById('bulkResults').textContent =
`Moved ${selectedCount} bookmarks to "${targetFolder}" folder.`;
}
function testSortByTitle() {
if (testManager.bookmarks.length === 0) {
document.getElementById('sortResults').textContent = 'No bookmarks to sort. Create test bookmarks first.';
return;
}
const beforeTitles = testManager.bookmarks.map(b => b.title);
testManager.sortBookmarks('title', 'asc');
const afterTitles = testManager.bookmarks.map(b => b.title);
document.getElementById('sortResults').textContent =
`Sorted by title. Before: [${beforeTitles.join(', ')}] After: [${afterTitles.join(', ')}]`;
}
function testSortByDate() {
if (testManager.bookmarks.length === 0) {
document.getElementById('sortResults').textContent = 'No bookmarks to sort. Create test bookmarks first.';
return;
}
testManager.sortBookmarks('date', 'desc');
const sortedTitles = testManager.bookmarks.map(b => b.title);
document.getElementById('sortResults').textContent =
`Sorted by date (newest first): [${sortedTitles.join(', ')}]`;
}
function testSortByFolder() {
if (testManager.bookmarks.length === 0) {
document.getElementById('sortResults').textContent = 'No bookmarks to sort. Create test bookmarks first.';
return;
}
testManager.sortBookmarks('folder', 'asc');
const sortedByFolder = testManager.bookmarks.map(b => `${b.title} (${b.folder || 'No folder'})`);
document.getElementById('sortResults').textContent =
`Sorted by folder: [${sortedByFolder.join(', ')}]`;
}
function testCreateFolder() {
const folderName = 'Test Created Folder';
testManager.createFolder(folderName);
document.getElementById('folderResults').textContent =
`Created folder "${folderName}". Check the bookmark list for the new folder.`;
}
function testRenameFolder() {
const folders = Object.keys(testManager.getFolderStats());
if (folders.length === 0) {
document.getElementById('folderResults').textContent = 'No folders to rename. Create test bookmarks first.';
return;
}
const oldName = folders[0];
const newName = oldName + ' (Renamed)';
testManager.renameFolder(oldName, newName);
document.getElementById('folderResults').textContent =
`Renamed folder "${oldName}" to "${newName}".`;
}
function testMergeFolder() {
const folders = Object.keys(testManager.getFolderStats());
if (folders.length < 2) {
document.getElementById('folderResults').textContent = 'Need at least 2 folders to test merge. Create more test bookmarks.';
return;
}
const sourceFolder = folders[0];
const targetFolder = folders[1];
testManager.mergeFolder(sourceFolder, targetFolder);
document.getElementById('folderResults').textContent =
`Merged folder "${sourceFolder}" into "${targetFolder}".`;
}
function testDeleteFolder() {
const folders = Object.keys(testManager.getFolderStats());
if (folders.length === 0) {
document.getElementById('folderResults').textContent = 'No folders to delete. Create test bookmarks first.';
return;
}
const folderToDelete = folders[0];
testManager.deleteFolder(folderToDelete);
document.getElementById('folderResults').textContent =
`Deleted folder "${folderToDelete}". Bookmarks moved to Uncategorized.`;
}
</script>
</body>
</html>