feat: FAQ management from admin, public /faq, homepage section, llms.txt
- Backend: faq_questions table (schema + migration), CRUD + reorder API, Swagger docs - Admin: FAQ page with create/edit, enable/disable, show on homepage, drag reorder - Public /faq page fetches enabled FAQs from API; layout builds dynamic JSON-LD - Homepage: FAQ section under Stay updated (homepage-enabled only) with See full FAQ link - llms.txt: FAQ section uses homepage FAQs from API - i18n: home.faq title/seeFull, admin FAQ nav Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import paymentOptionsRoutes from './routes/payment-options.js';
|
||||
import dashboardRoutes from './routes/dashboard.js';
|
||||
import siteSettingsRoutes from './routes/site-settings.js';
|
||||
import legalPagesRoutes from './routes/legal-pages.js';
|
||||
import faqRoutes from './routes/faq.js';
|
||||
import emailService from './lib/email.js';
|
||||
|
||||
const app = new Hono();
|
||||
@@ -84,6 +85,7 @@ const openApiSpec = {
|
||||
{ name: 'Media', description: 'File uploads and media management' },
|
||||
{ name: 'Lightning', description: 'Lightning/Bitcoin payments via LNBits' },
|
||||
{ name: 'Admin', description: 'Admin dashboard and analytics' },
|
||||
{ name: 'FAQ', description: 'FAQ questions (public and admin)' },
|
||||
],
|
||||
paths: {
|
||||
// ==================== Auth Endpoints ====================
|
||||
@@ -1587,6 +1589,144 @@ const openApiSpec = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ==================== FAQ Endpoints ====================
|
||||
'/api/faq': {
|
||||
get: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Get FAQ list (public)',
|
||||
description: 'Returns enabled FAQ questions, ordered by rank. Use ?homepage=true to get only questions enabled for homepage.',
|
||||
parameters: [
|
||||
{ name: 'homepage', in: 'query', schema: { type: 'boolean' }, description: 'If true, only return questions with showOnHomepage' },
|
||||
],
|
||||
responses: {
|
||||
200: { description: 'List of FAQ items (id, question, questionEs, answer, answerEs, rank)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/faq/admin/list': {
|
||||
get: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Get all FAQ questions (admin)',
|
||||
description: 'Returns all FAQ questions for management, ordered by rank.',
|
||||
security: [{ bearerAuth: [] }],
|
||||
responses: {
|
||||
200: { description: 'List of all FAQ questions' },
|
||||
401: { description: 'Unauthorized' },
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/faq/admin/:id': {
|
||||
get: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Get FAQ by ID (admin)',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{ name: 'id', in: 'path', required: true, schema: { type: 'string' } },
|
||||
],
|
||||
responses: {
|
||||
200: { description: 'FAQ details' },
|
||||
404: { description: 'FAQ not found' },
|
||||
},
|
||||
},
|
||||
put: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Update FAQ (admin)',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{ name: 'id', in: 'path', required: true, schema: { type: 'string' } },
|
||||
],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question: { type: 'string' },
|
||||
questionEs: { type: 'string' },
|
||||
answer: { type: 'string' },
|
||||
answerEs: { type: 'string' },
|
||||
enabled: { type: 'boolean' },
|
||||
showOnHomepage: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: { description: 'FAQ updated' },
|
||||
404: { description: 'FAQ not found' },
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Delete FAQ (admin)',
|
||||
security: [{ bearerAuth: [] }],
|
||||
parameters: [
|
||||
{ name: 'id', in: 'path', required: true, schema: { type: 'string' } },
|
||||
],
|
||||
responses: {
|
||||
200: { description: 'FAQ deleted' },
|
||||
404: { description: 'FAQ not found' },
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/faq/admin': {
|
||||
post: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Create FAQ (admin)',
|
||||
security: [{ bearerAuth: [] }],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['question', 'answer'],
|
||||
properties: {
|
||||
question: { type: 'string' },
|
||||
questionEs: { type: 'string' },
|
||||
answer: { type: 'string' },
|
||||
answerEs: { type: 'string' },
|
||||
enabled: { type: 'boolean', default: true },
|
||||
showOnHomepage: { type: 'boolean', default: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
201: { description: 'FAQ created' },
|
||||
400: { description: 'Validation error' },
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/faq/admin/reorder': {
|
||||
post: {
|
||||
tags: ['FAQ'],
|
||||
summary: 'Reorder FAQ questions (admin)',
|
||||
description: 'Set order by sending an ordered array of FAQ ids.',
|
||||
security: [{ bearerAuth: [] }],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['ids'],
|
||||
properties: {
|
||||
ids: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: { description: 'Order updated, returns full FAQ list' },
|
||||
400: { description: 'ids array required' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
@@ -1716,6 +1856,7 @@ app.route('/api/payment-options', paymentOptionsRoutes);
|
||||
app.route('/api/dashboard', dashboardRoutes);
|
||||
app.route('/api/site-settings', siteSettingsRoutes);
|
||||
app.route('/api/legal-pages', legalPagesRoutes);
|
||||
app.route('/api/faq', faqRoutes);
|
||||
|
||||
// 404 handler
|
||||
app.notFound((c) => {
|
||||
|
||||
Reference in New Issue
Block a user