This major update implements a complete user annotation system: ## 🎯 New Features - User notes with position tracking for media items - Text highlighting with customizable colors - Highlight-note associations for detailed annotations - Full CRUD API for both notes and highlights - Backward compatibility with existing ebook endpoints ## 📊 Database Changes - Add media_notes table (id, media_item_id, user_id, content, position, timestamps) - Add media_highlights table (id, media_item_id, user_id, selection_text, start/end_position, color, optional note_id) - Add foreign key relationships with CASCADE deletes - Add proper indexes for performance - Add database schema views for ebook backward compatibility ## 🔧 API Implementation - Complete REST API endpoints for notes and highlights - JWT authentication with proper middleware bypass - Request validation with meaningful error responses - UUID validation and type safety - Support for hex color codes in highlights ## 🧪 Testing & Documentation - Comprehensive test suite covering authentication scenarios - Bruno API collection for manual testing - Detailed testing guide with troubleshooting - Updated documentation in README and TESTING.md ## 📁 Backward Compatibility - Existing ebook endpoints continue working - Database views maintain API contracts - No breaking changes for existing integrations The annotation system is now fully functional and ready for production use.
54 lines
9.8 KiB
Go
54 lines
9.8 KiB
Go
// Code generated by templ - DO NOT EDIT.
|
|
|
|
// templ: version: v0.3.977
|
|
package templates
|
|
|
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
|
|
import "github.com/a-h/templ"
|
|
import templruntime "github.com/a-h/templ/runtime"
|
|
|
|
func AdminLibrary(user User) templ.Component {
|
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
return templ_7745c5c3_CtxErr
|
|
}
|
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
if !templ_7745c5c3_IsBuffer {
|
|
defer func() {
|
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
if templ_7745c5c3_Err == nil {
|
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
}
|
|
}()
|
|
}
|
|
ctx = templ.InitializeContext(ctx)
|
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
if templ_7745c5c3_Var1 == nil {
|
|
templ_7745c5c3_Var1 = templ.NopComponent
|
|
}
|
|
ctx = templ.ClearChildren(ctx)
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Library Management - Bookmann</title><script src=\"https://cdn.tailwindcss.com\"></script><style>\n :root {\n --bg-primary: #1a1b26;\n --bg-secondary: #16161e;\n --text-primary: #a9b1d6;\n --text-secondary: #565f89;\n --accent: #7aa2f7;\n --border: #414868;\n }\n .theme-tokyo-night {\n --bg-primary: #1a1b26;\n --bg-secondary: #16161e;\n --text-primary: #a9b1d6;\n --text-secondary: #565f89;\n --accent: #7aa2f7;\n --border: #414868;\n }\n body {\n background-color: var(--bg-primary);\n color: var(--text-primary);\n }\n .card {\n background-color: var(--bg-secondary);\n border-color: var(--border);\n }\n .btn-primary {\n background-color: var(--accent);\n color: var(--bg-primary);\n }\n .btn-primary:hover {\n opacity: 0.8;\n }\n </style></head><body class=\"theme-tokyo-night\"><nav class=\"border-b\" style=\"border-color: var(--border); background-color: var(--bg-secondary)\"><div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\"><div class=\"flex justify-between items-center h-16\"><div class=\"flex items-center\"><h1 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">📚 Bookmann</h1></div><div class=\"flex items-center space-x-4\"><a href=\"/\" class=\"px-3 py-2 text-sm hover:opacity-80\" style=\"color: var(--text-secondary)\">Library</a> <span style=\"color: var(--text-secondary)\">Welcome, ")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
var templ_7745c5c3_Var2 string
|
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `admin_library.templ`, Line: 53, Col: 91}
|
|
}
|
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "!</span> <button onclick=\"logout()\" class=\"btn-primary px-4 py-2 rounded text-sm\">Logout</button></div></div></div></nav><div class=\"flex min-h-screen\" style=\"background-color: var(--bg-primary)\"><aside class=\"w-64 border-r\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><div class=\"p-6\"><h2 class=\"text-lg font-semibold mb-6\" style=\"color: var(--text-primary)\">Admin Panel</h2><nav class=\"space-y-2\"><a href=\"/admin\" class=\"block px-4 py-2 rounded-lg hover:opacity-80\" style=\"color: var(--text-primary)\">🏠 Dashboard</a> <a href=\"/admin/profile\" class=\"block px-4 py-2 rounded-lg hover:opacity-80\" style=\"color: var(--text-primary)\">👤 Profile Settings</a> <a href=\"/admin/library\" class=\"block px-4 py-2 rounded-lg bg-accent text-bg-primary\" style=\"color: var(--text-primary)\">📚 Library Management</a></nav></div></aside><main class=\"flex-1 p-8\"><div class=\"max-w-4xl\"><div class=\"mb-8\"><div class=\"flex items-center space-x-4 mb-4\"><a href=\"/admin\" class=\"btn-secondary px-4 py-2 rounded-lg font-medium\">← Back to Dashboard</a></div><h1 class=\"text-3xl font-bold mb-2\" style=\"color: var(--text-primary)\">Library Management</h1><p style=\"color: var(--text-secondary)\">Configure library scanning settings and manage ebook folders</p></div><div class=\"space-y-8\"><div class=\"card p-6 rounded-lg border\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><h3 class=\"text-xl font-semibold mb-6\" style=\"color: var(--text-primary)\">Ebook Folders</h3><p style=\"color: var(--text-secondary)\" class=\"mb-4\">Manage folders that Bookmann scans for ebooks</p><div id=\"folders-list\" class=\"space-y-3 mb-6\"><!-- Folders will be loaded here --></div><form hx-post=\"/api/auth/ebook-folders\" hx-target=\"#add-folder-result\" hx-swap=\"innerHTML\" hx-headers='{\"Authorization\": \"Bearer \" + localStorage.getItem(\"token\")}' class=\"flex space-x-3\"><input type=\"text\" name=\"folder_path\" placeholder=\"/path/to/ebooks\" class=\"flex-1 px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\" required> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded\">Add Folder</button></form><div id=\"add-folder-result\" class=\"mt-2\"></div></div><div class=\"card p-6 rounded-lg border\" style=\"background-color: var(--bg-secondary); border-color: var(--border)\"><h3 class=\"text-xl font-semibold mb-6\" style=\"color: var(--text-primary)\">Scan Settings</h3><div class=\"mb-6\"><h4 class=\"text-lg font-medium mb-3\" style=\"color: var(--text-primary)\">Scan Frequency</h4><form hx-put=\"/api/library/scan-settings\" hx-target=\"#scan-result\" hx-swap=\"innerHTML\" hx-headers='{\"Authorization\": \"Bearer \" + localStorage.getItem(\"token\")}' class=\"flex space-x-3 max-w-md\"><select name=\"scan_frequency_minutes\" class=\"flex-1 px-3 py-2 border rounded\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)\"><option value=\"15\">Every 15 minutes</option> <option value=\"30\">Every 30 minutes</option> <option value=\"60\">Every hour</option> <option value=\"120\">Every 2 hours</option> <option value=\"240\">Every 4 hours</option></select> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded\">Update</button></form><div id=\"scan-result\" class=\"mt-2\"></div></div><div><h4 class=\"text-lg font-medium mb-3\" style=\"color: var(--text-primary)\">Auto Scan</h4><form hx-put=\"/api/library/scan-settings\" hx-target=\"#auto-scan-result\" hx-swap=\"innerHTML\" hx-headers='{\"Authorization\": \"Bearer \" + localStorage.getItem(\"token\")}' class=\"flex items-center space-x-3\"><input type=\"checkbox\" name=\"auto_scan_enabled\" id=\"auto-scan\" class=\"w-4 h-4\" style=\"background-color: var(--bg-primary); border-color: var(--border)\"> <label for=\"auto-scan\" style=\"color: var(--text-secondary)\">Enable automatic library scanning</label> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded\">Update</button></form><div id=\"auto-scan-result\" class=\"mt-2\"></div></div></div></div></div></main></div><script>\n function loadFolders() {\n fetch('/api/auth/ebook-folders', {\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(folders => {\n const container = document.getElementById('folders-list');\n container.innerHTML = folders.map(folder =>\n '<div class=\"flex items-center justify-between p-3 border rounded\" style=\"background-color: var(--bg-primary); border-color: var(--border)\">' +\n '<span style=\"color: var(--text-primary)\">' + folder.folder_path + '</span>' +\n '<button onclick=\"removeFolder(\\'' + folder.id + '\\')\" class=\"text-red-500 hover:text-red-700\">Remove</button>' +\n '</div>'\n ).join('');\n })\n .catch(error => console.error('Error loading folders:', error));\n }\n\n function removeFolder(folderId) {\n if (confirm('Remove this folder from scanning?')) {\n fetch('/api/auth/ebook-folders/' + folderId, {\n method: 'DELETE',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => {\n if (response.ok) loadFolders();\n else alert('Error removing folder');\n })\n .catch(error => console.error('Error removing folder:', error));\n }\n }\n\n function logout() {\n localStorage.removeItem('token');\n localStorage.removeItem('user');\n window.location.href = '/';\n }\n\n document.addEventListener('DOMContentLoaded', function() {\n loadTheme();\n loadFolders();\n });\n </script></body></html>")
|
|
if templ_7745c5c3_Err != nil {
|
|
return templ_7745c5c3_Err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
var _ = templruntime.GeneratedTemplate
|