Complete sermon itinerary application with Nuxt 3, SQLite, authentication, and Docker deployment

This commit is contained in:
2025-10-01 22:15:01 -04:00
parent dacaea6fa4
commit 1b282c05fe
26 changed files with 1245 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
import { getUserByUsername } from '~/server/utils/database'
import { setAuthCookie } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const { username, password } = body
if (!username || !password) {
throw createError({
statusCode: 400,
message: 'Username and password are required'
})
}
const user = getUserByUsername(username)
if (!user || user.password !== password) {
throw createError({
statusCode: 401,
message: 'Invalid credentials'
})
}
setAuthCookie(event, username)
return {
success: true,
username: user.username
}
})

View File

@@ -0,0 +1,9 @@
import { clearAuthCookie } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
clearAuthCookie(event)
return {
success: true
}
})

View File

@@ -0,0 +1,9 @@
import { isAuthenticated } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
const authenticated = isAuthenticated(event)
return {
authenticated
}
})

View File

@@ -0,0 +1,23 @@
import { getSermonBySlug } from '~/server/utils/database'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Slug is required'
})
}
const sermon = getSermonBySlug(slug)
if (!sermon) {
throw createError({
statusCode: 404,
message: 'Sermon not found'
})
}
return sermon
})

View File

@@ -0,0 +1,10 @@
import { getAllSermons } from '~/server/utils/database'
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const limit = query.limit ? parseInt(query.limit as string) : undefined
const sermons = getAllSermons(limit)
return sermons
})

View File

@@ -0,0 +1,43 @@
import { createSermon } from '~/server/utils/database'
import { isAuthenticated } from '~/server/utils/auth'
export default defineEventHandler(async (event) => {
// Check authentication
if (!isAuthenticated(event)) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const body = await readBody(event)
const { slug, title, date, bible_references, personal_appliance, pastors_challenge } = body
if (!slug || !title || !date || !bible_references || !personal_appliance || !pastors_challenge) {
throw createError({
statusCode: 400,
message: 'All fields are required'
})
}
try {
createSermon({
slug,
title,
date,
bible_references,
personal_appliance,
pastors_challenge
})
return {
success: true,
message: 'Sermon created successfully'
}
} catch (error: any) {
throw createError({
statusCode: 500,
message: error.message || 'Failed to create sermon'
})
}
})

23
server/utils/auth.ts Normal file
View File

@@ -0,0 +1,23 @@
import { H3Event } from 'h3'
export function setAuthCookie(event: H3Event, username: string) {
setCookie(event, 'auth', username, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // 7 days
path: '/'
})
}
export function getAuthCookie(event: H3Event) {
return getCookie(event, 'auth')
}
export function clearAuthCookie(event: H3Event) {
deleteCookie(event, 'auth')
}
export function isAuthenticated(event: H3Event): boolean {
const auth = getAuthCookie(event)
return !!auth
}

93
server/utils/database.ts Normal file
View File

@@ -0,0 +1,93 @@
import Database from 'better-sqlite3'
import { join } from 'path'
let db: Database.Database | null = null
export interface Sermon {
id?: number
slug: string
title: string
date: string
bible_references: string
personal_appliance: string
pastors_challenge: string
created_at?: string
}
export interface User {
id?: number
username: string
password: string
}
export function getDatabase() {
if (!db) {
const dbPath = join(process.cwd(), 'data', 'sermons.db')
db = new Database(dbPath)
// Create tables if they don't exist
db.exec(`
CREATE TABLE IF NOT EXISTS sermons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
date TEXT NOT NULL,
bible_references TEXT NOT NULL,
personal_appliance TEXT NOT NULL,
pastors_challenge TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)
`)
// Insert default admin user (password: admin123)
// In production, this should be hashed properly
const userExists = db.prepare('SELECT COUNT(*) as count FROM users WHERE username = ?').get('admin') as { count: number }
if (userExists.count === 0) {
db.prepare('INSERT INTO users (username, password) VALUES (?, ?)').run('admin', 'admin123')
}
}
return db
}
export function getAllSermons(limit?: number) {
const db = getDatabase()
if (limit) {
return db.prepare('SELECT * FROM sermons ORDER BY date DESC LIMIT ?').all(limit) as Sermon[]
}
return db.prepare('SELECT * FROM sermons ORDER BY date DESC').all() as Sermon[]
}
export function getSermonBySlug(slug: string) {
const db = getDatabase()
return db.prepare('SELECT * FROM sermons WHERE slug = ?').get(slug) as Sermon | undefined
}
export function createSermon(sermon: Sermon) {
const db = getDatabase()
const stmt = db.prepare(`
INSERT INTO sermons (slug, title, date, bible_references, personal_appliance, pastors_challenge)
VALUES (?, ?, ?, ?, ?, ?)
`)
return stmt.run(
sermon.slug,
sermon.title,
sermon.date,
sermon.bible_references,
sermon.personal_appliance,
sermon.pastors_challenge
)
}
export function getUserByUsername(username: string) {
const db = getDatabase()
return db.prepare('SELECT * FROM users WHERE username = ?').get(username) as User | undefined
}