Add OpenID Connect authentication and SQLite database with user preferences and favorites system

This commit is contained in:
Ryderjj89
2025-09-13 16:50:26 -04:00
parent 9beff1e610
commit a804430536
5 changed files with 631 additions and 71 deletions

View File

@@ -3,6 +3,8 @@ const cors = require('cors');
const helmet = require('helmet');
const path = require('path');
const fs = require('fs').promises;
const { configureAuth, requireAuth, optionalAuth } = require('./auth');
const { preferencesOps, favoritesOps } = require('./database');
const app = express();
const PORT = process.env.PORT || 3000;
@@ -14,9 +16,15 @@ app.use(helmet({
crossOriginEmbedderPolicy: false,
originAgentCluster: false
}));
app.use(cors());
app.use(cors({
origin: process.env.FRONTEND_URL || true,
credentials: true
}));
app.use(express.json());
// Configure authentication
configureAuth(app);
// Serve static files from the React build
app.use(express.static(path.join(__dirname, '../../frontend/build')));
@@ -120,6 +128,107 @@ app.get('/books/:book/:chapter', async (req, res) => {
}
});
// User preferences routes
app.get('/api/preferences', requireAuth, (req, res) => {
preferencesOps.getPreferences(req.user.id, (err, preferences) => {
if (err) {
return res.status(500).json({ error: 'Failed to get preferences' });
}
res.json({
font_size: preferences?.font_size || 'medium',
dark_mode: !!preferences?.dark_mode
});
});
});
app.put('/api/preferences', requireAuth, (req, res) => {
const { font_size, dark_mode } = req.body;
// Validate font_size
if (font_size && !['small', 'medium', 'large'].includes(font_size)) {
return res.status(400).json({ error: 'Invalid font_size' });
}
preferencesOps.updatePreferences(req.user.id, { font_size, dark_mode }, (err) => {
if (err) {
return res.status(500).json({ error: 'Failed to update preferences' });
}
res.json({ message: 'Preferences updated successfully' });
});
});
// Favorites routes
app.get('/api/favorites', requireAuth, (req, res) => {
favoritesOps.getFavorites(req.user.id, (err, favorites) => {
if (err) {
return res.status(500).json({ error: 'Failed to get favorites' });
}
res.json({ favorites });
});
});
app.post('/api/favorites', requireAuth, (req, res) => {
const { book, chapter, verse_start, verse_end, note } = req.body;
if (!book || !chapter) {
return res.status(400).json({ error: 'Book and chapter are required' });
}
const favorite = {
book,
chapter,
verse_start: verse_start || null,
verse_end: verse_end || null,
note: note || null
};
favoritesOps.addFavorite(req.user.id, favorite, function(err) {
if (err) {
if (err.code === 'SQLITE_CONSTRAINT') {
return res.status(409).json({ error: 'Favorite already exists' });
}
return res.status(500).json({ error: 'Failed to add favorite' });
}
res.json({
message: 'Favorite added successfully',
id: this.lastID
});
});
});
app.delete('/api/favorites/:id', requireAuth, (req, res) => {
const favoriteId = req.params.id;
favoritesOps.removeFavorite(req.user.id, favoriteId, (err) => {
if (err) {
return res.status(500).json({ error: 'Failed to remove favorite' });
}
res.json({ message: 'Favorite removed successfully' });
});
});
app.get('/api/favorites/check', requireAuth, (req, res) => {
const { book, chapter, verse_start, verse_end } = req.query;
if (!book || !chapter) {
return res.status(400).json({ error: 'Book and chapter are required' });
}
favoritesOps.isFavorited(
req.user.id,
book,
chapter,
verse_start || null,
verse_end || null,
(err, isFavorited) => {
if (err) {
return res.status(500).json({ error: 'Failed to check favorite status' });
}
res.json({ isFavorited });
}
);
});
// Catch-all handler: send back React's index.html for client-side routing
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../../frontend/build/index.html'));