diff --git a/pages/[slug].vue b/pages/[slug].vue
index eeb1d9d..c3f1ee5 100644
--- a/pages/[slug].vue
+++ b/pages/[slug].vue
@@ -95,6 +95,41 @@
+
+
+ My Notes
+
+
+
+
+
+ {{ saveStatus }}
+
+
Notes are automatically saved
+
+
+
+
+
Want to take notes?
+
Log in or create an account to save your sermon notes!
+
+ Log In / Sign Up
+
+
+
+
+
authData.value?.authenticated || false)
// Font size state
const fontSize = ref('medium')
+// Notes state
+const notes = ref('')
+const saveStatus = ref('')
+let saveTimeout: NodeJS.Timeout | null = null
+
+// Load notes when sermon is loaded and user is authenticated
+watch([sermon, isAuthenticated], async ([sermonData, authenticated]) => {
+ if (sermonData && authenticated) {
+ try {
+ const response = await $fetch(`/api/notes/${sermonData.id}`)
+ notes.value = response.notes || ''
+ } catch (error) {
+ console.error('Failed to load notes:', error)
+ }
+ }
+}, { immediate: true })
+
+function handleNotesChange() {
+ saveStatus.value = 'Saving...'
+
+ // Clear existing timeout
+ if (saveTimeout) {
+ clearTimeout(saveTimeout)
+ }
+
+ // Set new timeout to save after 1 second of no typing
+ saveTimeout = setTimeout(async () => {
+ try {
+ await $fetch(`/api/notes/${sermon.value!.id}`, {
+ method: 'POST',
+ body: { notes: notes.value }
+ })
+ saveStatus.value = 'Saved'
+ setTimeout(() => {
+ saveStatus.value = ''
+ }, 2000)
+ } catch (error) {
+ saveStatus.value = 'Failed to save'
+ console.error('Failed to save notes:', error)
+ }
+ }, 1000)
+}
+
const bibleReferences = computed(() => {
if (!sermon.value?.bible_references) return []
try {
diff --git a/server/api/notes/[sermonId].get.ts b/server/api/notes/[sermonId].get.ts
new file mode 100644
index 0000000..cecce4e
--- /dev/null
+++ b/server/api/notes/[sermonId].get.ts
@@ -0,0 +1,37 @@
+import { getSermonNote, getUserByUsername } from '~/server/utils/database'
+import { getAuthCookie } from '~/server/utils/auth'
+
+export default defineEventHandler(async (event) => {
+ const username = getAuthCookie(event)
+
+ if (!username) {
+ throw createError({
+ statusCode: 401,
+ message: 'Unauthorized'
+ })
+ }
+
+ const user = getUserByUsername(username)
+
+ if (!user) {
+ throw createError({
+ statusCode: 401,
+ message: 'User not found'
+ })
+ }
+
+ const sermonId = parseInt(event.context.params?.sermonId || '')
+
+ if (isNaN(sermonId)) {
+ throw createError({
+ statusCode: 400,
+ message: 'Invalid sermon ID'
+ })
+ }
+
+ const note = getSermonNote(user.id!, sermonId)
+
+ return {
+ notes: note?.notes || ''
+ }
+})
diff --git a/server/api/notes/[sermonId].post.ts b/server/api/notes/[sermonId].post.ts
new file mode 100644
index 0000000..6f1008e
--- /dev/null
+++ b/server/api/notes/[sermonId].post.ts
@@ -0,0 +1,50 @@
+import { saveSermonNote, getUserByUsername } from '~/server/utils/database'
+import { getAuthCookie } from '~/server/utils/auth'
+
+export default defineEventHandler(async (event) => {
+ const username = getAuthCookie(event)
+
+ if (!username) {
+ throw createError({
+ statusCode: 401,
+ message: 'Unauthorized'
+ })
+ }
+
+ const user = getUserByUsername(username)
+
+ if (!user) {
+ throw createError({
+ statusCode: 401,
+ message: 'User not found'
+ })
+ }
+
+ const sermonId = parseInt(event.context.params?.sermonId || '')
+ const body = await readBody(event)
+ const { notes } = body
+
+ if (isNaN(sermonId)) {
+ throw createError({
+ statusCode: 400,
+ message: 'Invalid sermon ID'
+ })
+ }
+
+ if (typeof notes !== 'string') {
+ throw createError({
+ statusCode: 400,
+ message: 'Notes must be a string'
+ })
+ }
+
+ try {
+ saveSermonNote(user.id!, sermonId, notes)
+ return { success: true }
+ } catch (error) {
+ throw createError({
+ statusCode: 500,
+ message: 'Failed to save notes'
+ })
+ }
+})
diff --git a/server/utils/database.ts b/server/utils/database.ts
index d08edac..3d53aaf 100644
--- a/server/utils/database.ts
+++ b/server/utils/database.ts
@@ -24,6 +24,15 @@ export interface User {
is_admin: number
}
+export interface SermonNote {
+ id?: number
+ user_id: number
+ sermon_id: number
+ notes: string
+ created_at?: string
+ updated_at?: string
+}
+
export function getDatabase() {
if (!db) {
const dbPath = join(process.cwd(), 'data', 'sermons.db')
@@ -55,6 +64,20 @@ export function getDatabase() {
)
`)
+ db.exec(`
+ CREATE TABLE IF NOT EXISTS sermon_notes (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ sermon_id INTEGER NOT NULL,
+ notes TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (sermon_id) REFERENCES sermons(id) ON DELETE CASCADE,
+ UNIQUE(user_id, sermon_id)
+ )
+ `)
+
// Insert default admin user from environment variables with hashed password
const config = useRuntimeConfig()
const adminUsername = config.adminUsername
@@ -148,3 +171,24 @@ export function resetUserPassword(id: number, newPassword: string) {
const hashedPassword = bcrypt.hashSync(newPassword, saltRounds)
return db.prepare('UPDATE users SET password = ? WHERE id = ?').run(hashedPassword, id)
}
+
+export function getSermonNote(userId: number, sermonId: number) {
+ const db = getDatabase()
+ return db.prepare('SELECT * FROM sermon_notes WHERE user_id = ? AND sermon_id = ?').get(userId, sermonId) as SermonNote | undefined
+}
+
+export function saveSermonNote(userId: number, sermonId: number, notes: string) {
+ const db = getDatabase()
+ const existing = getSermonNote(userId, sermonId)
+
+ if (existing) {
+ return db.prepare('UPDATE sermon_notes SET notes = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND sermon_id = ?').run(notes, userId, sermonId)
+ } else {
+ return db.prepare('INSERT INTO sermon_notes (user_id, sermon_id, notes) VALUES (?, ?, ?)').run(userId, sermonId, notes)
+ }
+}
+
+export function deleteSermonNote(userId: number, sermonId: number) {
+ const db = getDatabase()
+ return db.prepare('DELETE FROM sermon_notes WHERE user_id = ? AND sermon_id = ?').run(userId, sermonId)
+}