Complete sermon itinerary application with Nuxt 3, SQLite, authentication, and Docker deployment
This commit is contained in:
30
server/api/auth/login.post.ts
Normal file
30
server/api/auth/login.post.ts
Normal 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
|
||||
}
|
||||
})
|
||||
9
server/api/auth/logout.post.ts
Normal file
9
server/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { clearAuthCookie } from '~/server/utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
clearAuthCookie(event)
|
||||
|
||||
return {
|
||||
success: true
|
||||
}
|
||||
})
|
||||
9
server/api/auth/verify.get.ts
Normal file
9
server/api/auth/verify.get.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { isAuthenticated } from '~/server/utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const authenticated = isAuthenticated(event)
|
||||
|
||||
return {
|
||||
authenticated
|
||||
}
|
||||
})
|
||||
23
server/api/sermons/[slug].get.ts
Normal file
23
server/api/sermons/[slug].get.ts
Normal 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
|
||||
})
|
||||
10
server/api/sermons/index.get.ts
Normal file
10
server/api/sermons/index.get.ts
Normal 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
|
||||
})
|
||||
43
server/api/sermons/index.post.ts
Normal file
43
server/api/sermons/index.post.ts
Normal 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
23
server/utils/auth.ts
Normal 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
93
server/utils/database.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user