Complete sermon management system with Nuxt 4, authentication, SQLite database, QR codes, and Docker deployment
This commit is contained in:
64
server/utils/auth.ts
Normal file
64
server/utils/auth.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import bcrypt from 'bcrypt'
|
||||
import { SignJWT, jwtVerify } from 'jose'
|
||||
import { getDatabase } from './database'
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
username: string
|
||||
password_hash: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export async function authenticateUser(username: string, password: string): Promise<User | null> {
|
||||
const db = await getDatabase()
|
||||
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username) as User | undefined
|
||||
|
||||
if (!user) return null
|
||||
|
||||
const isValid = await bcrypt.compare(password, user.password_hash)
|
||||
if (!isValid) return null
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export async function createJWT(user: User): Promise<string> {
|
||||
const config = useRuntimeConfig()
|
||||
const secret = new TextEncoder().encode(config.jwtSecret)
|
||||
|
||||
return await new SignJWT({ userId: user.id, username: user.username })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('7d')
|
||||
.sign(secret)
|
||||
}
|
||||
|
||||
export async function verifyJWT(token: string): Promise<{ userId: number; username: string } | null> {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const secret = new TextEncoder().encode(config.jwtSecret)
|
||||
|
||||
const { payload } = await jwtVerify(token, secret)
|
||||
return {
|
||||
userId: payload.userId as number,
|
||||
username: payload.username as string
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function generateSlug(title: string, date: string): string {
|
||||
const formattedTitle = title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.trim()
|
||||
|
||||
const dateObj = new Date(date)
|
||||
const month = String(dateObj.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(dateObj.getDate()).padStart(2, '0')
|
||||
const year = dateObj.getFullYear()
|
||||
|
||||
return `sermon-${month}${day}${year}`
|
||||
}
|
||||
60
server/utils/database.ts
Normal file
60
server/utils/database.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import Database from 'better-sqlite3'
|
||||
import { join } from 'path'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
import { dirname } from 'path'
|
||||
|
||||
let db: Database.Database
|
||||
|
||||
export async function getDatabase() {
|
||||
if (!db) {
|
||||
const dbPath = process.env.NODE_ENV === 'production'
|
||||
? '/data/sermons.db'
|
||||
: './data/sermons.db'
|
||||
|
||||
// Ensure directory exists
|
||||
const dir = dirname(dbPath)
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
|
||||
db = new Database(dbPath)
|
||||
await initializeDatabase(db)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
async function initializeDatabase(db: Database.Database) {
|
||||
// Create sermons table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS sermons (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
bible_references TEXT,
|
||||
personal_application TEXT,
|
||||
pastor_challenge TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
|
||||
// Create users table for authentication
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
|
||||
// Note: Admin user creation is handled in auth.ts to avoid circular dependencies
|
||||
}
|
||||
|
||||
export function closeDatabase() {
|
||||
if (db) {
|
||||
db.close()
|
||||
db = null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user