notes: private Obsidian vault tracker at /trackers/notes
All checks were successful
Deploy production / deploy (push) Successful in 1m19s
All checks were successful
Deploy production / deploy (push) Successful in 1m19s
Vault files stay the source of truth (bind-mounted read-write at /notes): atomic writes chowned to the vault owner, hash-checked saves (409 on a newer file), deletes to .trash. notes.* is a full-text/link index kept current by a recursive watcher plus a 60s rescan; live updates over the owner-only /ws/notes channel (paths and hashes only). Wikilinks, embeds, backlinks, tags, callouts, image paste. Owner-only everywhere (404).
This commit is contained in:
@@ -32,3 +32,4 @@ export * from './ai';
|
||||
export * from './movies';
|
||||
export * from './worldtodo';
|
||||
export * from './osu-goals';
|
||||
export * from './notes';
|
||||
|
||||
70
src/lib/server/db/schema/notes.ts
Normal file
70
src/lib/server/db/schema/notes.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import {
|
||||
bigint,
|
||||
boolean,
|
||||
customType,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
primaryKey,
|
||||
text
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { notesSchema } from './schemas';
|
||||
import { timestamptz } from './helpers';
|
||||
|
||||
// Notes tracker (/trackers/notes, owner-only). The Obsidian vault (markdown files on the host,
|
||||
// Syncthing-synced, bind-mounted at NOTES_ROOT) is the source of truth; these tables are only
|
||||
// a search and link index that src/lib/server/notes/ rebuilds from the files (file watcher plus a
|
||||
// periodic rescan). Dropping and rebuilding them loses nothing.
|
||||
const tsvector = customType<{ data: string }>({ dataType: () => 'tsvector' });
|
||||
|
||||
export const notesFiles = notesSchema.table(
|
||||
'files',
|
||||
{
|
||||
/** Vault-relative posix path, e.g. "Cars/Toyota/Supra.md". */
|
||||
path: text('path').primaryKey(),
|
||||
/** note (.md), attachment (images, pdf, canvas…) or conflict (*.sync-conflict-*, never indexed as a note). */
|
||||
kind: text('kind').notNull(),
|
||||
folder: text('folder').notNull(),
|
||||
title: text('title').notNull(),
|
||||
ext: text('ext').notNull(),
|
||||
size: bigint('size', { mode: 'number' }).notNull(),
|
||||
mtime: timestamptz('mtime').notNull(),
|
||||
/** sha256 of the file bytes; the editor's save-conflict check compares against it. */
|
||||
hash: text('hash').notNull(),
|
||||
tags: text('tags')
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`'{}'::text[]`),
|
||||
frontmatter: jsonb('frontmatter').$type<Record<string, unknown>>(),
|
||||
/** Note text without frontmatter (notes only), for snippets. */
|
||||
body: text('body'),
|
||||
search: tsvector('search'),
|
||||
indexedAt: timestamptz('indexed_at').notNull().defaultNow()
|
||||
},
|
||||
(t) => [
|
||||
index('files_search_idx').using('gin', t.search),
|
||||
index('files_tags_idx').using('gin', t.tags),
|
||||
index('files_folder_idx').on(t.folder),
|
||||
index('files_kind_idx').on(t.kind)
|
||||
]
|
||||
);
|
||||
|
||||
export const notesLinks = notesSchema.table(
|
||||
'links',
|
||||
{
|
||||
srcPath: text('src_path')
|
||||
.notNull()
|
||||
.references(() => notesFiles.path, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
seq: integer('seq').notNull(),
|
||||
/** Target as written ("Folder/Note", "image.png"), without #heading or |alias. */
|
||||
target: text('target').notNull(),
|
||||
/** Lowercased last segment without .md; backlinks match on it, then resolve exactly. */
|
||||
targetKey: text('target_key').notNull(),
|
||||
embed: boolean('embed').notNull().default(false)
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.srcPath, t.seq] }),
|
||||
index('links_target_key_idx').on(t.targetKey)
|
||||
]
|
||||
);
|
||||
@@ -28,3 +28,4 @@ export const statsSchema = pgSchema('stats');
|
||||
export const aiSchema = pgSchema('ai');
|
||||
export const moviesSchema = pgSchema('movies');
|
||||
export const worldtodoSchema = pgSchema('worldtodo');
|
||||
export const notesSchema = pgSchema('notes');
|
||||
|
||||
Reference in New Issue
Block a user