Spaces:
Build error
Build error
Upload folder using huggingface_hub
Browse files- static/dashboard.html +263 -29
- supabase_notes.sql +61 -0
static/dashboard.html
CHANGED
|
@@ -650,17 +650,41 @@
|
|
| 650 |
<div class="pt-4 border-t border-gray-700">
|
| 651 |
<div class="flex items-center justify-between gap-2 mb-2">
|
| 652 |
<div class="font-semibold text-gray-200">Notes</div>
|
| 653 |
-
<
|
| 654 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
</div>
|
| 656 |
-
<div class="text-xs text-gray-400 mb-2">
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
</div>
|
| 665 |
</div>
|
| 666 |
</div>
|
|
@@ -2600,44 +2624,254 @@
|
|
| 2600 |
}
|
| 2601 |
}
|
| 2602 |
|
| 2603 |
-
|
| 2604 |
-
|
| 2605 |
-
|
|
|
|
|
|
|
|
|
|
| 2606 |
const status = document.getElementById('notes-status');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2607 |
if (!editor || !preview) return;
|
|
|
|
|
|
|
| 2608 |
|
| 2609 |
-
|
| 2610 |
-
|
| 2611 |
-
|
|
|
|
|
|
|
|
|
|
| 2612 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2614 |
try {
|
| 2615 |
const { data: { user } } = await supabase.auth.getUser();
|
| 2616 |
if (!user) return;
|
| 2617 |
-
const { data, error } = await supabase
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2618 |
if (error) throw error;
|
| 2619 |
-
|
| 2620 |
-
|
| 2621 |
-
if (status) status.textContent = data?.updated_at ? `Loaded (${new Date(data.updated_at).toLocaleString()})` : 'No notes yet';
|
| 2622 |
} catch (e) {
|
| 2623 |
-
|
| 2624 |
console.warn('Notes load failed', e);
|
| 2625 |
}
|
| 2626 |
}
|
| 2627 |
|
| 2628 |
-
|
| 2629 |
-
const editor =
|
| 2630 |
-
const
|
| 2631 |
-
if (!editor) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2632 |
try {
|
| 2633 |
const { data: { user } } = await supabase.auth.getUser();
|
| 2634 |
if (!user) return;
|
| 2635 |
-
const
|
| 2636 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2637 |
if (error) throw error;
|
| 2638 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2639 |
} catch (e) {
|
| 2640 |
-
|
| 2641 |
}
|
| 2642 |
}
|
| 2643 |
|
|
|
|
| 650 |
<div class="pt-4 border-t border-gray-700">
|
| 651 |
<div class="flex items-center justify-between gap-2 mb-2">
|
| 652 |
<div class="font-semibold text-gray-200">Notes</div>
|
| 653 |
+
<div class="flex gap-2">
|
| 654 |
+
<button onclick="createNotesFolder()"
|
| 655 |
+
class="bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded text-xs">+ Folder</button>
|
| 656 |
+
<button onclick="createNotesDoc()"
|
| 657 |
+
class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded text-xs">+ Note</button>
|
| 658 |
+
<button onclick="saveActiveNote()"
|
| 659 |
+
class="bg-green-600 hover:bg-green-700 text-white px-2 py-1 rounded text-xs">Save</button>
|
| 660 |
+
</div>
|
| 661 |
</div>
|
| 662 |
+
<div class="text-xs text-gray-400 mb-2">Markdown notes with folders (saved to Supabase).</div>
|
| 663 |
+
|
| 664 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
| 665 |
+
<div class="bg-gray-900/40 border border-gray-700 rounded-lg p-2 overflow-auto" style="max-height: 340px;">
|
| 666 |
+
<div class="flex items-center justify-between mb-2">
|
| 667 |
+
<div class="text-xs text-gray-400">Folders</div>
|
| 668 |
+
<button onclick="refreshNotesTree()" class="text-xs text-gray-300 hover:text-white">↻</button>
|
| 669 |
+
</div>
|
| 670 |
+
<div id="notes-tree" class="text-sm space-y-1"></div>
|
| 671 |
+
</div>
|
| 672 |
+
<div class="space-y-2">
|
| 673 |
+
<input id="notes-title" type="text" placeholder="Title"
|
| 674 |
+
class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500" />
|
| 675 |
+
<textarea id="notes-editor" rows="8"
|
| 676 |
+
class="w-full bg-gray-700 text-sm rounded border border-gray-600 p-2 text-white outline-none focus:border-blue-500 font-mono"
|
| 677 |
+
placeholder="# Notes..."></textarea>
|
| 678 |
+
<div class="text-xs text-gray-400">Preview</div>
|
| 679 |
+
<div id="notes-preview" class="prose prose-invert max-w-none bg-gray-900/40 border border-gray-700 rounded-lg p-3 overflow-auto" style="max-height: 180px;"></div>
|
| 680 |
+
<div class="flex items-center justify-between">
|
| 681 |
+
<div id="notes-status" class="text-xs text-gray-500"></div>
|
| 682 |
+
<div class="flex gap-2">
|
| 683 |
+
<button onclick="renameActiveNote()" class="text-xs text-gray-300 hover:text-white">Rename</button>
|
| 684 |
+
<button onclick="deleteActiveNote()" class="text-xs text-red-300 hover:text-red-200">Delete</button>
|
| 685 |
+
</div>
|
| 686 |
+
</div>
|
| 687 |
+
</div>
|
| 688 |
</div>
|
| 689 |
</div>
|
| 690 |
</div>
|
|
|
|
| 2624 |
}
|
| 2625 |
}
|
| 2626 |
|
| 2627 |
+
// --- Notes (folders + markdown docs) ---
|
| 2628 |
+
let notesItems = []; // flat items from DB
|
| 2629 |
+
let activeNoteId = null;
|
| 2630 |
+
let activeFolderId = null;
|
| 2631 |
+
|
| 2632 |
+
function setNotesStatus(msg) {
|
| 2633 |
const status = document.getElementById('notes-status');
|
| 2634 |
+
if (status) status.textContent = msg || '';
|
| 2635 |
+
}
|
| 2636 |
+
|
| 2637 |
+
function getNotesInputs() {
|
| 2638 |
+
return {
|
| 2639 |
+
title: document.getElementById('notes-title'),
|
| 2640 |
+
editor: document.getElementById('notes-editor'),
|
| 2641 |
+
preview: document.getElementById('notes-preview'),
|
| 2642 |
+
tree: document.getElementById('notes-tree'),
|
| 2643 |
+
};
|
| 2644 |
+
}
|
| 2645 |
+
|
| 2646 |
+
function renderNotesPreview() {
|
| 2647 |
+
const { editor, preview } = getNotesInputs();
|
| 2648 |
if (!editor || !preview) return;
|
| 2649 |
+
renderMarkdownInto(preview, editor.value || '');
|
| 2650 |
+
}
|
| 2651 |
|
| 2652 |
+
function buildNotesTree() {
|
| 2653 |
+
const byParent = new Map();
|
| 2654 |
+
notesItems.forEach((item) => {
|
| 2655 |
+
const key = item.parent_id || 'root';
|
| 2656 |
+
if (!byParent.has(key)) byParent.set(key, []);
|
| 2657 |
+
byParent.get(key).push(item);
|
| 2658 |
});
|
| 2659 |
+
byParent.forEach((arr) => arr.sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0) || (a.title || '').localeCompare(b.title || '')));
|
| 2660 |
+
return byParent;
|
| 2661 |
+
}
|
| 2662 |
+
|
| 2663 |
+
function renderNotesTree() {
|
| 2664 |
+
const { tree } = getNotesInputs();
|
| 2665 |
+
if (!tree) return;
|
| 2666 |
+
const byParent = buildNotesTree();
|
| 2667 |
+
|
| 2668 |
+
const makeNode = (item, depth) => {
|
| 2669 |
+
const row = document.createElement('div');
|
| 2670 |
+
row.className = 'flex items-center gap-2 py-1 px-2 rounded hover:bg-white/10';
|
| 2671 |
+
row.style.marginLeft = `${depth * 10}px`;
|
| 2672 |
+
|
| 2673 |
+
const icon = document.createElement('span');
|
| 2674 |
+
icon.className = 'text-gray-400';
|
| 2675 |
+
icon.textContent = item.kind === 'folder' ? '▸' : '📝';
|
| 2676 |
+
|
| 2677 |
+
const name = document.createElement('button');
|
| 2678 |
+
name.type = 'button';
|
| 2679 |
+
name.className = 'flex-grow text-left truncate';
|
| 2680 |
+
name.textContent = item.title || (item.kind === 'folder' ? 'Untitled folder' : 'Untitled note');
|
| 2681 |
+
name.onclick = () => {
|
| 2682 |
+
if (item.kind === 'folder') {
|
| 2683 |
+
activeFolderId = item.id;
|
| 2684 |
+
// Keep active note selection if inside folder; otherwise clear note selection.
|
| 2685 |
+
setNotesStatus(`Folder: ${item.title || 'Untitled'}`);
|
| 2686 |
+
renderNotesTree();
|
| 2687 |
+
} else {
|
| 2688 |
+
openNote(item.id);
|
| 2689 |
+
}
|
| 2690 |
+
};
|
| 2691 |
|
| 2692 |
+
const actions = document.createElement('div');
|
| 2693 |
+
actions.className = 'flex gap-1 shrink-0 opacity-0 hover:opacity-100';
|
| 2694 |
+
|
| 2695 |
+
const del = document.createElement('button');
|
| 2696 |
+
del.type = 'button';
|
| 2697 |
+
del.className = 'text-xs text-red-300 hover:text-red-200';
|
| 2698 |
+
del.textContent = 'Del';
|
| 2699 |
+
del.onclick = (e) => { e.stopPropagation(); deleteNoteItem(item.id); };
|
| 2700 |
+
|
| 2701 |
+
actions.appendChild(del);
|
| 2702 |
+
|
| 2703 |
+
row.appendChild(icon);
|
| 2704 |
+
row.appendChild(name);
|
| 2705 |
+
row.appendChild(actions);
|
| 2706 |
+
return row;
|
| 2707 |
+
};
|
| 2708 |
+
|
| 2709 |
+
const renderBranch = (parentKey, depth) => {
|
| 2710 |
+
const items = byParent.get(parentKey) || [];
|
| 2711 |
+
const frag = document.createDocumentFragment();
|
| 2712 |
+
items.forEach((item) => {
|
| 2713 |
+
const row = makeNode(item, depth);
|
| 2714 |
+
if (item.id === activeFolderId || item.id === activeNoteId) row.classList.add('bg-white/10');
|
| 2715 |
+
frag.appendChild(row);
|
| 2716 |
+
if (item.kind === 'folder') {
|
| 2717 |
+
// simple always-expanded tree
|
| 2718 |
+
frag.appendChild(renderBranch(item.id, depth + 1));
|
| 2719 |
+
}
|
| 2720 |
+
});
|
| 2721 |
+
return frag;
|
| 2722 |
+
};
|
| 2723 |
+
|
| 2724 |
+
tree.innerHTML = '';
|
| 2725 |
+
tree.appendChild(renderBranch('root', 0));
|
| 2726 |
+
}
|
| 2727 |
+
|
| 2728 |
+
async function refreshNotesTree() {
|
| 2729 |
try {
|
| 2730 |
const { data: { user } } = await supabase.auth.getUser();
|
| 2731 |
if (!user) return;
|
| 2732 |
+
const { data, error } = await supabase
|
| 2733 |
+
.from('note_items')
|
| 2734 |
+
.select('id,user_id,parent_id,kind,title,content,sort_order,updated_at')
|
| 2735 |
+
.eq('user_id', user.id)
|
| 2736 |
+
.order('sort_order', { ascending: true })
|
| 2737 |
+
.order('title', { ascending: true });
|
| 2738 |
if (error) throw error;
|
| 2739 |
+
notesItems = data || [];
|
| 2740 |
+
renderNotesTree();
|
|
|
|
| 2741 |
} catch (e) {
|
| 2742 |
+
setNotesStatus('Notes table not configured yet.');
|
| 2743 |
console.warn('Notes load failed', e);
|
| 2744 |
}
|
| 2745 |
}
|
| 2746 |
|
| 2747 |
+
function openNote(id) {
|
| 2748 |
+
const { title, editor } = getNotesInputs();
|
| 2749 |
+
const note = notesItems.find((n) => n.id === id && n.kind === 'note');
|
| 2750 |
+
if (!note || !title || !editor) return;
|
| 2751 |
+
activeNoteId = id;
|
| 2752 |
+
activeFolderId = note.parent_id || null;
|
| 2753 |
+
title.value = note.title || '';
|
| 2754 |
+
editor.value = note.content || '';
|
| 2755 |
+
renderNotesPreview();
|
| 2756 |
+
setNotesStatus(note.updated_at ? `Loaded (${new Date(note.updated_at).toLocaleString()})` : 'Loaded');
|
| 2757 |
+
renderNotesTree();
|
| 2758 |
+
}
|
| 2759 |
+
|
| 2760 |
+
async function initNotes() {
|
| 2761 |
+
const { editor, preview } = getNotesInputs();
|
| 2762 |
+
if (!editor || !preview) return;
|
| 2763 |
+
|
| 2764 |
+
editor.addEventListener('input', () => {
|
| 2765 |
+
renderNotesPreview();
|
| 2766 |
+
setNotesStatus('Not saved');
|
| 2767 |
+
});
|
| 2768 |
+
const titleEl = document.getElementById('notes-title');
|
| 2769 |
+
if (titleEl) {
|
| 2770 |
+
titleEl.addEventListener('input', () => setNotesStatus('Not saved'));
|
| 2771 |
+
}
|
| 2772 |
+
|
| 2773 |
+
await refreshNotesTree();
|
| 2774 |
+
// Auto-open most recently updated note if any
|
| 2775 |
+
const latest = notesItems.filter(i => i.kind === 'note').sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0];
|
| 2776 |
+
if (latest) openNote(latest.id);
|
| 2777 |
+
}
|
| 2778 |
+
|
| 2779 |
+
async function createNotesFolder() {
|
| 2780 |
+
const name = prompt('Folder name:');
|
| 2781 |
+
if (!name) return;
|
| 2782 |
+
try {
|
| 2783 |
+
const { data: { user } } = await supabase.auth.getUser();
|
| 2784 |
+
if (!user) return;
|
| 2785 |
+
const { error } = await supabase.from('note_items').insert({
|
| 2786 |
+
user_id: user.id,
|
| 2787 |
+
parent_id: activeFolderId || null,
|
| 2788 |
+
kind: 'folder',
|
| 2789 |
+
title: name,
|
| 2790 |
+
content: '',
|
| 2791 |
+
sort_order: 0
|
| 2792 |
+
});
|
| 2793 |
+
if (error) throw error;
|
| 2794 |
+
await refreshNotesTree();
|
| 2795 |
+
setNotesStatus('Folder created');
|
| 2796 |
+
} catch (e) {
|
| 2797 |
+
setNotesStatus(`Create failed: ${e?.message || e}`);
|
| 2798 |
+
}
|
| 2799 |
+
}
|
| 2800 |
+
|
| 2801 |
+
async function createNotesDoc() {
|
| 2802 |
+
const name = prompt('Note title:');
|
| 2803 |
+
if (!name) return;
|
| 2804 |
try {
|
| 2805 |
const { data: { user } } = await supabase.auth.getUser();
|
| 2806 |
if (!user) return;
|
| 2807 |
+
const { data, error } = await supabase.from('note_items').insert({
|
| 2808 |
+
user_id: user.id,
|
| 2809 |
+
parent_id: activeFolderId || null,
|
| 2810 |
+
kind: 'note',
|
| 2811 |
+
title: name,
|
| 2812 |
+
content: '',
|
| 2813 |
+
sort_order: 0
|
| 2814 |
+
}).select().single();
|
| 2815 |
+
if (error) throw error;
|
| 2816 |
+
await refreshNotesTree();
|
| 2817 |
+
if (data?.id) openNote(data.id);
|
| 2818 |
+
setNotesStatus('Note created');
|
| 2819 |
+
} catch (e) {
|
| 2820 |
+
setNotesStatus(`Create failed: ${e?.message || e}`);
|
| 2821 |
+
}
|
| 2822 |
+
}
|
| 2823 |
+
|
| 2824 |
+
async function saveActiveNote() {
|
| 2825 |
+
const { title, editor } = getNotesInputs();
|
| 2826 |
+
if (!title || !editor) return;
|
| 2827 |
+
if (!activeNoteId) {
|
| 2828 |
+
await createNotesDoc();
|
| 2829 |
+
return;
|
| 2830 |
+
}
|
| 2831 |
+
try {
|
| 2832 |
+
const { error } = await supabase.from('note_items').update({
|
| 2833 |
+
title: title.value || '',
|
| 2834 |
+
content: editor.value || ''
|
| 2835 |
+
}).eq('id', activeNoteId);
|
| 2836 |
+
if (error) throw error;
|
| 2837 |
+
await refreshNotesTree();
|
| 2838 |
+
setNotesStatus(`Saved (${new Date().toLocaleString()})`);
|
| 2839 |
+
} catch (e) {
|
| 2840 |
+
setNotesStatus(`Save failed: ${e?.message || e}`);
|
| 2841 |
+
}
|
| 2842 |
+
}
|
| 2843 |
+
|
| 2844 |
+
async function renameActiveNote() {
|
| 2845 |
+
if (!activeNoteId) return;
|
| 2846 |
+
const { title } = getNotesInputs();
|
| 2847 |
+
const name = prompt('New title:', title?.value || '');
|
| 2848 |
+
if (!name) return;
|
| 2849 |
+
if (title) title.value = name;
|
| 2850 |
+
await saveActiveNote();
|
| 2851 |
+
}
|
| 2852 |
+
|
| 2853 |
+
async function deleteActiveNote() {
|
| 2854 |
+
if (!activeNoteId) return;
|
| 2855 |
+
await deleteNoteItem(activeNoteId);
|
| 2856 |
+
}
|
| 2857 |
+
|
| 2858 |
+
async function deleteNoteItem(id) {
|
| 2859 |
+
const item = notesItems.find((n) => n.id === id);
|
| 2860 |
+
if (!item) return;
|
| 2861 |
+
if (!confirm(`Delete "${item.title || 'Untitled'}"? This deletes children too.`)) return;
|
| 2862 |
+
try {
|
| 2863 |
+
const { error } = await supabase.from('note_items').delete().eq('id', id);
|
| 2864 |
if (error) throw error;
|
| 2865 |
+
if (activeNoteId === id) activeNoteId = null;
|
| 2866 |
+
if (activeFolderId === id) activeFolderId = null;
|
| 2867 |
+
const { title, editor } = getNotesInputs();
|
| 2868 |
+
if (title) title.value = '';
|
| 2869 |
+
if (editor) editor.value = '';
|
| 2870 |
+
renderNotesPreview();
|
| 2871 |
+
await refreshNotesTree();
|
| 2872 |
+
setNotesStatus('Deleted');
|
| 2873 |
} catch (e) {
|
| 2874 |
+
setNotesStatus(`Delete failed: ${e?.message || e}`);
|
| 2875 |
}
|
| 2876 |
}
|
| 2877 |
|
supabase_notes.sql
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- Notes with folder tree + CRUD
|
| 2 |
+
-- Run this in Supabase SQL Editor.
|
| 3 |
+
|
| 4 |
+
create table if not exists public.note_items (
|
| 5 |
+
id uuid primary key default gen_random_uuid(),
|
| 6 |
+
user_id uuid not null references auth.users (id) on delete cascade,
|
| 7 |
+
parent_id uuid references public.note_items (id) on delete cascade,
|
| 8 |
+
kind text not null check (kind in ('folder','note')),
|
| 9 |
+
title text not null default '',
|
| 10 |
+
content text not null default '',
|
| 11 |
+
sort_order integer not null default 0,
|
| 12 |
+
created_at timestamptz not null default now(),
|
| 13 |
+
updated_at timestamptz not null default now()
|
| 14 |
+
);
|
| 15 |
+
|
| 16 |
+
create index if not exists note_items_user_id_idx on public.note_items (user_id);
|
| 17 |
+
create index if not exists note_items_parent_id_idx on public.note_items (parent_id);
|
| 18 |
+
|
| 19 |
+
create or replace function public.set_note_items_updated_at()
|
| 20 |
+
returns trigger
|
| 21 |
+
language plpgsql
|
| 22 |
+
as $$
|
| 23 |
+
begin
|
| 24 |
+
new.updated_at = now();
|
| 25 |
+
return new;
|
| 26 |
+
end;
|
| 27 |
+
$$;
|
| 28 |
+
|
| 29 |
+
drop trigger if exists note_items_set_updated_at on public.note_items;
|
| 30 |
+
create trigger note_items_set_updated_at
|
| 31 |
+
before update on public.note_items
|
| 32 |
+
for each row
|
| 33 |
+
execute procedure public.set_note_items_updated_at();
|
| 34 |
+
|
| 35 |
+
alter table public.note_items enable row level security;
|
| 36 |
+
|
| 37 |
+
drop policy if exists "note_items_select_own" on public.note_items;
|
| 38 |
+
create policy "note_items_select_own"
|
| 39 |
+
on public.note_items
|
| 40 |
+
for select
|
| 41 |
+
using (auth.uid() = user_id);
|
| 42 |
+
|
| 43 |
+
drop policy if exists "note_items_insert_own" on public.note_items;
|
| 44 |
+
create policy "note_items_insert_own"
|
| 45 |
+
on public.note_items
|
| 46 |
+
for insert
|
| 47 |
+
with check (auth.uid() = user_id);
|
| 48 |
+
|
| 49 |
+
drop policy if exists "note_items_update_own" on public.note_items;
|
| 50 |
+
create policy "note_items_update_own"
|
| 51 |
+
on public.note_items
|
| 52 |
+
for update
|
| 53 |
+
using (auth.uid() = user_id)
|
| 54 |
+
with check (auth.uid() = user_id);
|
| 55 |
+
|
| 56 |
+
drop policy if exists "note_items_delete_own" on public.note_items;
|
| 57 |
+
create policy "note_items_delete_own"
|
| 58 |
+
on public.note_items
|
| 59 |
+
for delete
|
| 60 |
+
using (auth.uid() = user_id);
|
| 61 |
+
|