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

145
scripts/validate-bsb-md.ts Normal file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env tsx
import fs from 'fs'
import path from 'path'
const SRC = process.env.BSB_MD_PATH || path.join('bibles', 'bible-bsb.md')
type BookInfo = { name: string; variants: string[]; expectedChapters: number; testament: 'OT'|'NT' }
function canon(): BookInfo[] {
const OT: Array<[string, string[], number]> = [
['Genesis', ['Genesis'], 50],
['Exodus', ['Exodus'], 40],
['Leviticus', ['Leviticus'], 27],
['Numbers', ['Numbers'], 36],
['Deuteronomy', ['Deuteronomy'], 34],
['Joshua', ['Joshua'], 24],
['Judges', ['Judges'], 21],
['Ruth', ['Ruth'], 4],
['1 Samuel', ['1 Samuel','1 Samuel'], 31],
['2 Samuel', ['2 Samuel','2 Samuel'], 24],
['1 Kings', ['1 Kings','1 Kings'], 22],
['2 Kings', ['2 Kings','2 Kings'], 25],
['1 Chronicles', ['1 Chronicles','1 Chronicles'], 29],
['2 Chronicles', ['2 Chronicles','2 Chronicles','2 Chronicles'], 36],
['Ezra', ['Ezra'], 10],
['Nehemiah', ['Nehemiah'], 13],
['Esther', ['Esther'], 10],
['Job', ['Job'], 42],
['Psalms', ['Psalms','Psalm'], 150],
['Proverbs', ['Proverbs'], 31],
['Ecclesiastes', ['Ecclesiastes'], 12],
['Song of Songs', ['Song of Songs','Song of Solomon'], 8],
['Isaiah', ['Isaiah'], 66],
['Jeremiah', ['Jeremiah'], 52],
['Lamentations', ['Lamentations'], 5],
['Ezekiel', ['Ezekiel'], 48],
['Daniel', ['Daniel'], 12],
['Hosea', ['Hosea'], 14],
['Joel', ['Joel'], 3],
['Amos', ['Amos'], 9],
['Obadiah', ['Obadiah'], 1],
['Jonah', ['Jonah'], 4],
['Micah', ['Micah'], 7],
['Nahum', ['Nahum'], 3],
['Habakkuk', ['Habakkuk'], 3],
['Zephaniah', ['Zephaniah'], 3],
['Haggai', ['Haggai'], 2],
['Zechariah', ['Zechariah'], 14],
['Malachi', ['Malachi'], 4]
]
const NT: Array<[string, string[], number]> = [
['Matthew', ['Matthew'], 28],
['Mark', ['Mark'], 16],
['Luke', ['Luke'], 24],
['John', ['John'], 21],
['Acts', ['Acts'], 28],
['Romans', ['Romans'], 16],
['1 Corinthians', ['1 Corinthians','1 Corinthians'], 16],
['2 Corinthians', ['2 Corinthians','2 Corinthians'], 13],
['Galatians', ['Galatians'], 6],
['Ephesians', ['Ephesians'], 6],
['Philippians', ['Philippians'], 4],
['Colossians', ['Colossians'], 4],
['1 Thessalonians', ['1 Thessalonians','1 Thessalonians'], 5],
['2 Thessalonians', ['2 Thessalonians','2 Thessalonians'], 3],
['1 Timothy', ['1 Timothy','1 Timothy'], 6],
['2 Timothy', ['2 Timothy','2 Timothy'], 4],
['Titus', ['Titus'], 3],
['Philemon', ['Philemon'], 1],
['Hebrews', ['Hebrews'], 13],
['James', ['James'], 5],
['1 Peter', ['1 Peter','1 Peter'], 5],
['2 Peter', ['2 Peter','2 Peter'], 3],
['1 John', ['1 John','1 John'], 5],
['2 John', ['2 John','2 John'], 1],
['3 John', ['3 John','3 John'], 1],
['Jude', ['Jude'], 1],
['Revelation', ['Revelation'], 22]
]
return [
...OT.map(([n,v,c]) => ({ name:n, variants:v, expectedChapters:c, testament:'OT' as const })),
...NT.map(([n,v,c]) => ({ name:n, variants:v, expectedChapters:c, testament:'NT' as const })),
]
}
function escapeRegExp(s: string) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function main() {
if (!fs.existsSync(SRC)) {
console.error('Missing source file:', SRC)
process.exit(1)
}
const md = fs.readFileSync(SRC, 'utf-8')
const books = canon()
const report: any = { file: SRC, totals: { versesTagged: 0 }, books: [] as any[] }
for (const b of books) {
// Build a regex to find markers like: "... | BookName 12:34" or just "BookName 12:34"
// Allow flexible whitespace and the double-spaced variants in source.
const patterns = b.variants.map(v => v.replace(/\s+/g, '\\s+'))
const combined = patterns.map(p => `(?:^|[\n\r\f\s\|])${p}\\s+(\\d+):(\\d+)`).join('|')
const re = new RegExp(combined, 'gi')
const chapters = new Set<number>()
let m: RegExpExecArray | null
let verseCount = 0
while ((m = re.exec(md)) !== null) {
// Find first numeric capture among alternations
const nums = m.slice(1).filter(Boolean)
const ch = parseInt(nums[0] || '0', 10)
const vs = parseInt(nums[1] || '0', 10)
if (Number.isFinite(ch) && ch > 0) chapters.add(ch)
if (Number.isFinite(vs) && vs > 0) verseCount++
}
report.totals.versesTagged += verseCount
report.books.push({
name: b.name,
testament: b.testament,
expectedChapters: b.expectedChapters,
detectedChapters: [...chapters].sort((a,b)=>a-b),
detectedCount: chapters.size,
coverage: b.expectedChapters > 0 ? +(100 * chapters.size / b.expectedChapters).toFixed(2) : null,
verseMarkers: verseCount
})
}
const missingBooks = report.books.filter((x:any) => x.detectedCount === 0).map((x:any)=>x.name)
const partialBooks = report.books.filter((x:any) => x.detectedCount > 0 && x.detectedCount < x.expectedChapters).map((x:any)=>({name:x.name, det:x.detectedCount, exp:x.expectedChapters}))
console.log('Validation summary for', SRC)
console.log('Total verse markers found:', report.totals.versesTagged)
console.log('Books missing markers:', missingBooks.length ? missingBooks.join(', ') : 'None')
console.log('Books partially detected (chapters):', partialBooks.length ? partialBooks.slice(0,10) : 'None')
const outDir = path.join('data','en_bible','BSB_VALIDATION')
fs.mkdirSync(outDir, { recursive: true })
fs.writeFileSync(path.join(outDir,'report.json'), JSON.stringify(report, null, 2), 'utf-8')
console.log('Wrote detailed report to', path.join(outDir,'report.json'))
}
main()