Complete sermon management system with Nuxt 4, authentication, SQLite database, QR codes, and Docker deployment
This commit is contained in:
37
server/api/auth/login.post.ts
Normal file
37
server/api/auth/login.post.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { authenticateUser, createJWT } 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,
|
||||
statusMessage: 'Username and password are required'
|
||||
})
|
||||
}
|
||||
|
||||
const user = await authenticateUser(username, password)
|
||||
if (!user) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid credentials'
|
||||
})
|
||||
}
|
||||
|
||||
const token = await createJWT(user)
|
||||
|
||||
setCookie(event, 'auth_token', token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
maxAge: 7 * 24 * 60 * 60 // 7 days
|
||||
})
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username
|
||||
}
|
||||
}
|
||||
})
|
||||
6
server/api/auth/logout.post.ts
Normal file
6
server/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Clear the auth cookie
|
||||
deleteCookie(event, 'auth_token')
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
27
server/api/auth/verify.get.ts
Normal file
27
server/api/auth/verify.get.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { verifyJWT } from '~/server/utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const token = getCookie(event, 'auth_token')
|
||||
|
||||
if (!token) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'No authentication token provided'
|
||||
})
|
||||
}
|
||||
|
||||
const payload = await verifyJWT(token)
|
||||
if (!payload) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid authentication token'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: payload.userId,
|
||||
username: payload.username
|
||||
}
|
||||
}
|
||||
})
|
||||
28
server/api/sermons/[slug].get.ts
Normal file
28
server/api/sermons/[slug].get.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { getDatabase } from '~/server/utils/database'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = await getDatabase()
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
|
||||
if (!slug) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Sermon slug is required'
|
||||
})
|
||||
}
|
||||
|
||||
const sermon = db.prepare('SELECT * FROM sermons WHERE slug = ?').get(slug) as any
|
||||
|
||||
if (!sermon) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Sermon not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Parse JSON fields
|
||||
return {
|
||||
...sermon,
|
||||
bibleReferences: sermon.bible_references ? JSON.parse(sermon.bible_references) : []
|
||||
}
|
||||
})
|
||||
39
server/api/sermons/index.get.ts
Normal file
39
server/api/sermons/index.get.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { getDatabase } from '~/server/utils/database'
|
||||
import { verifyJWT } from '~/server/utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = await getDatabase()
|
||||
|
||||
// Check for time filter
|
||||
const query = getQuery(event)
|
||||
const timeFilter = query.time as string || '3months'
|
||||
|
||||
let dateFilter = ''
|
||||
const now = new Date()
|
||||
|
||||
switch (timeFilter) {
|
||||
case '3months':
|
||||
const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())
|
||||
dateFilter = threeMonthsAgo.toISOString()
|
||||
break
|
||||
case '6months':
|
||||
const sixMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 6, now.getDate())
|
||||
dateFilter = sixMonthsAgo.toISOString()
|
||||
break
|
||||
case '1year':
|
||||
const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate())
|
||||
dateFilter = oneYearAgo.toISOString()
|
||||
break
|
||||
case 'all':
|
||||
dateFilter = '1970-01-01'
|
||||
break
|
||||
}
|
||||
|
||||
const sermons = db.prepare(`
|
||||
SELECT * FROM sermons
|
||||
WHERE date >= ?
|
||||
ORDER BY date DESC
|
||||
`).all(dateFilter) as any[]
|
||||
|
||||
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 { getDatabase } from '~/server/utils/database'
|
||||
import { verifyJWT, generateSlug } from '~/server/utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = await getDatabase()
|
||||
|
||||
const body = await readBody(event)
|
||||
const { title, date, bibleReferences, personalApplication, pastorChallenge } = body
|
||||
|
||||
if (!title || !date) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Title and date are required'
|
||||
})
|
||||
}
|
||||
|
||||
const slug = generateSlug(title, date)
|
||||
|
||||
try {
|
||||
const result = db.prepare(`
|
||||
INSERT INTO sermons (title, date, slug, bible_references, personal_application, pastor_challenge)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(title, date, slug, JSON.stringify(bibleReferences || []), personalApplication || '', pastorChallenge || '')
|
||||
|
||||
return {
|
||||
id: result.lastInsertRowid,
|
||||
title,
|
||||
date,
|
||||
slug,
|
||||
bibleReferences: bibleReferences || [],
|
||||
personalApplication: personalApplication || '',
|
||||
pastorChallenge: pastorChallenge || ''
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
|
||||
throw createError({
|
||||
statusCode: 409,
|
||||
statusMessage: 'A sermon with this date already exists'
|
||||
})
|
||||
}
|
||||
throw error
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user