Fix authentication state persistence and admin role display

- Implement complete authentication system with JWT token validation
- Add auth provider with persistent login state across page refreshes
- Create multilingual login/register forms with Material-UI components
- Fix token validation using raw SQL queries to bypass Prisma sync issues
- Add comprehensive error handling for expired/invalid tokens
- Create profile and settings pages with full i18n support
- Add proper user role management (admin/user) with database sync
- Implement secure middleware with CSRF protection and auth checks
- Add debug endpoints for troubleshooting authentication issues
- Fix Zustand store persistence for authentication state

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
andupetcu
2025-09-21 01:06:30 +03:00
parent 62ca73b2ac
commit 196ca00194
174 changed files with 181207 additions and 179 deletions

View File

@@ -0,0 +1,140 @@
import { PrismaClient } from '@prisma/client'
import fs from 'fs'
import path from 'path'
const prisma = new PrismaClient()
interface Verse { verseNum: number; text: string }
interface Chapter { chapterNum: number; verses: Verse[] }
interface Book { name: string; chapters: Chapter[] }
interface TestamentFile { testament: string; books: Book[] }
function loadJson(file: string): TestamentFile {
return JSON.parse(fs.readFileSync(file, 'utf-8'))
}
function getBookKeyEn(name: string): string {
const map: Record<string, string> = {
'Genesis': 'genesis', 'Exodus': 'exodus', 'Leviticus': 'leviticus', 'Numbers': 'numbers', 'Deuteronomy': 'deuteronomy',
'Joshua': 'joshua', 'Judges': 'judges', 'Ruth': 'ruth', '1 Samuel': '1_samuel', '2 Samuel': '2_samuel',
'1 Kings': '1_kings', '2 Kings': '2_kings', '1 Chronicles': '1_chronicles', '2 Chronicles': '2_chronicles',
'Ezra': 'ezra', 'Nehemiah': 'nehemiah', 'Esther': 'esther', 'Job': 'job', 'Psalms': 'psalms',
'Proverbs': 'proverbs', 'Ecclesiastes': 'ecclesiastes', 'Song of Songs': 'song_of_songs', 'Isaiah': 'isaiah',
'Jeremiah': 'jeremiah', 'Lamentations': 'lamentations', 'Ezekiel': 'ezekiel', 'Daniel': 'daniel',
'Hosea': 'hosea', 'Joel': 'joel', 'Amos': 'amos', 'Obadiah': 'obadiah', 'Jonah': 'jonah', 'Micah': 'micah',
'Nahum': 'nahum', 'Habakkuk': 'habakkuk', 'Zephaniah': 'zephaniah', 'Haggai': 'haggai', 'Zechariah': 'zechariah', 'Malachi': 'malachi',
'Matthew': 'matthew', 'Mark': 'mark', 'Luke': 'luke', 'John': 'john', 'Acts': 'acts', 'Romans': 'romans',
'1 Corinthians': '1_corinthians', '2 Corinthians': '2_corinthians', 'Galatians': 'galatians', 'Ephesians': 'ephesians', 'Philippians': 'philippians', 'Colossians': 'colossians',
'1 Thessalonians': '1_thessalonians', '2 Thessalonians': '2_thessalonians', '1 Timothy': '1_timothy', '2 Timothy': '2_timothy', 'Titus': 'titus', 'Philemon': 'philemon',
'Hebrews': 'hebrews', 'James': 'james', '1 Peter': '1_peter', '2 Peter': '2_peter', '1 John': '1_john', '2 John': '2_john', '3 John': '3_john', 'Jude': 'jude', 'Revelation': 'revelation'
}
return map[name] || name.toLowerCase().replace(/\s+/g, '_')
}
function getOrderFromList(name: string, list: string[]): number {
const idx = list.indexOf(name)
return idx >= 0 ? idx + 1 : 999
}
async function main() {
try {
const abbr = (process.env.EN_ABBR || 'BSB').toUpperCase()
const inputDir = process.env.INPUT_DIR || path.join('data', 'en_bible', abbr)
const lang = 'en'
const otPath = path.join(inputDir, 'old_testament.json')
const ntPath = path.join(inputDir, 'new_testament.json')
if (!fs.existsSync(otPath) || !fs.existsSync(ntPath)) {
throw new Error(`Missing OT/NT JSON at ${inputDir}. Run fetch-english-bible.ts first.`)
}
// Upsert English version
const englishVersion = await prisma.bibleVersion.upsert({
where: { abbreviation_language: { abbreviation: abbr, language: lang } },
update: {},
create: {
name: abbr,
abbreviation: abbr,
language: lang,
description: `English Bible (${abbr})`,
isDefault: true
}
})
const ot = loadJson(otPath)
const nt = loadJson(ntPath)
const canon = [...ot.books.map(b => b.name), ...nt.books.map(b => b.name)]
let importedBooks = 0
let importedChapters = 0
let importedVerses = 0
async function importTestament(test: TestamentFile) {
for (const book of test.books) {
const orderNum = getOrderFromList(book.name, canon)
const testament = test.testament
const bookKey = getBookKeyEn(book.name)
const createdBook = await prisma.bibleBook.upsert({
where: {
versionId_orderNum: {
versionId: englishVersion.id,
orderNum
}
},
update: {},
create: {
versionId: englishVersion.id,
name: book.name,
testament,
orderNum,
bookKey
}
})
importedBooks++
for (const chapter of book.chapters) {
const createdChapter = await prisma.bibleChapter.upsert({
where: {
bookId_chapterNum: {
bookId: createdBook.id,
chapterNum: chapter.chapterNum
}
},
update: {},
create: { bookId: createdBook.id, chapterNum: chapter.chapterNum }
})
importedChapters++
// Deduplicate verses by verseNum
const unique = new Map<number, string>()
for (const v of chapter.verses) {
if (!unique.has(v.verseNum)) unique.set(v.verseNum, v.text)
}
const versesData = Array.from(unique.entries()).map(([num, text]) => ({
chapterId: createdChapter.id,
verseNum: num,
text
}))
if (versesData.length > 0) {
await prisma.bibleVerse.createMany({ data: versesData, skipDuplicates: true })
importedVerses += versesData.length
}
}
}
}
await importTestament(ot)
await importTestament(nt)
console.log(`Imported ${importedBooks} books, ${importedChapters} chapters, ${importedVerses} verses for ${abbr}.`)
} catch (e) {
console.error('English JSON import failed:', e)
process.exit(1)
} finally {
await prisma.$disconnect()
}
}
main()