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,71 @@
#!/usr/bin/env tsx
import fs from 'fs'
import path from 'path'
const ABBR = (process.env.EN_ABBR || 'BSB').toUpperCase()
const ROOT = process.env.INPUT_DIR || path.join('data', 'en_bible', ABBR)
const OT_ORDER = [
'Genesis','Exodus','Leviticus','Numbers','Deuteronomy','Joshua','Judges','Ruth','1 Samuel','2 Samuel','1 Kings','2 Kings','1 Chronicles','2 Chronicles','Ezra','Nehemiah','Esther','Job','Psalms','Proverbs','Ecclesiastes','Song of Songs','Isaiah','Jeremiah','Lamentations','Ezekiel','Daniel','Hosea','Joel','Amos','Obadiah','Jonah','Micah','Nahum','Habakkuk','Zephaniah','Haggai','Zechariah','Malachi'
]
const NT_ORDER = [
'Matthew','Mark','Luke','John','Acts','Romans','1 Corinthians','2 Corinthians','Galatians','Ephesians','Philippians','Colossians','1 Thessalonians','2 Thessalonians','1 Timothy','2 Timothy','Titus','Philemon','Hebrews','James','1 Peter','2 Peter','1 John','2 John','3 John','Jude','Revelation'
]
function titleFromAbbr(abbr: string): string {
const map: Record<string,string> = { GEN:'Genesis', EXO:'Exodus', LEV:'Leviticus', NUM:'Numbers', DEU:'Deuteronomy', JOS:'Joshua', JDG:'Judges', RUT:'Ruth', '1SA':'1 Samuel', '2SA':'2 Samuel', '1KI':'1 Kings', '2KI':'2 Kings', '1CH':'1 Chronicles', '2CH':'2 Chronicles', EZR:'Ezra', NEH:'Nehemiah', EST:'Esther', JOB:'Job', PSA:'Psalms', PRO:'Proverbs', ECC:'Ecclesiastes', SNG:'Song of Songs', ISA:'Isaiah', JER:'Jeremiah', LAM:'Lamentations', EZK:'Ezekiel', DAN:'Daniel', HOS:'Hosea', JOL:'Joel', AMO:'Amos', OBA:'Obadiah', JON:'Jonah', MIC:'Micah', NAM:'Nahum', HAB:'Habakkuk', ZEP:'Zephaniah', HAG:'Haggai', ZEC:'Zechariah', MAL:'Malachi', MAT:'Matthew', MRK:'Mark', LUK:'Luke', JHN:'John', ACT:'Acts', ROM:'Romans', '1CO':'1 Corinthians', '2CO':'2 Corinthians', GAL:'Galatians', EPH:'Ephesians', PHP:'Philippians', COL:'Colossians', '1TH':'1 Thessalonians', '2TH':'2 Thessalonians', '1TI':'1 Timothy', '2TI':'2 Timothy', TIT:'Titus', PHM:'Philemon', HEB:'Hebrews', JAS:'James', '1PE':'1 Peter', '2PE':'2 Peter', '1JN':'1 John', '2JN':'2 John', '3JN':'3 John', JUD:'Jude', REV:'Revelation' }
return map[abbr] || abbr
}
function detectBooks(dir: string): string[] {
if (!fs.existsSync(dir)) return []
return fs.readdirSync(dir).filter(d => fs.statSync(path.join(dir, d)).isDirectory())
}
function readChapters(bookDir: string) {
const files = fs.readdirSync(bookDir).filter(f => f.startsWith('chapter-') && f.endsWith('.json') && !f.includes('intro'))
const chapters: any[] = []
for (const f of files) {
const obj = JSON.parse(fs.readFileSync(path.join(bookDir, f), 'utf-8'))
chapters.push(obj)
}
chapters.sort((a,b) => a.chapterNum - b.chapterNum)
return chapters
}
function assemble() {
const bookDirs = detectBooks(ROOT)
const books: { name: string; chapters: any[] }[] = []
for (const d of bookDirs) {
// d is likely an abbreviation; try to infer common titles for GEN/EXO/LEV etc.
let name = d
if (d.toUpperCase() === d) name = titleFromAbbr(d.toUpperCase())
const chapters = readChapters(path.join(ROOT, d))
if (chapters.length > 0) books.push({ name, chapters })
}
const ot = books.filter(b => OT_ORDER.includes(b.name)).sort((a,b) => OT_ORDER.indexOf(a.name) - OT_ORDER.indexOf(b.name))
const nt = books.filter(b => NT_ORDER.includes(b.name)).sort((a,b) => NT_ORDER.indexOf(a.name) - NT_ORDER.indexOf(b.name))
// Verify completeness: all 66 books must be present
const have = new Set(books.map(b => b.name))
const missing = [...OT_ORDER, ...NT_ORDER].filter(n => !have.has(n))
if (missing.length > 0 && process.env.ALLOW_PARTIAL !== '1') {
console.error(`Missing ${missing.length} books. Full EN Bible not downloaded yet.`)
console.error('First few missing:', missing.slice(0, 10).join(', ') + (missing.length > 10 ? '...' : ''))
process.exit(1)
}
const otObj = { testament: 'Old Testament', books: ot }
const ntObj = { testament: 'New Testament', books: nt }
const otFile = path.join(ROOT, 'old_testament.json')
const ntFile = path.join(ROOT, 'new_testament.json')
fs.mkdirSync(ROOT, { recursive: true })
fs.writeFileSync(otFile, JSON.stringify(otObj, null, 2), 'utf-8')
fs.writeFileSync(ntFile, JSON.stringify(ntObj, null, 2), 'utf-8')
console.log('Assembled:', otFile)
console.log('Assembled:', ntFile)
}
assemble()