feat: harden bootstrap and add tests
This commit is contained in:
@@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireRole } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const dynamic = 'force-dynamic'; // Prevents static generation
|
||||
|
||||
// GET /api/admin/menu — returns ALL menu items (including inactive)
|
||||
export async function GET() {
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireRole } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const dynamic = 'force-dynamic'; // Prevents static generation
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await requireRole('admin');
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { createUser } from '@/lib/auth';
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const user = await createUser('mmendoza', 'W3canbeheroes*-*', 'admin');
|
||||
const username = process.env.BOOTSTRAP_ADMIN_USERNAME || 'mmendoza';
|
||||
const password = process.env.BOOTSTRAP_ADMIN_PASSWORD;
|
||||
|
||||
if (!password) {
|
||||
return NextResponse.json({ error: 'Bootstrap password is not configured' }, { status: 500 });
|
||||
}
|
||||
|
||||
const user = await createUser(username, password, 'admin');
|
||||
return NextResponse.json({ ok: true, user: { id: user.id, username: user.username, role: user.role } });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: err.message || 'Failed to create user' }, { status: 500 });
|
||||
return NextResponse.json({ error: err?.message || 'Failed to create bootstrap user' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json(
|
||||
{
|
||||
ok: true,
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'cache-control': 'no-store, max-age=0',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-dynamic'; // Prevents static generation
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const { rows } = await db.query(`
|
||||
|
||||
@@ -2,6 +2,8 @@ import { NextResponse } from 'next/server';
|
||||
import { requireRole } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export const dynamic = 'force-dynamic'; // Prevents static generation
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await requireRole('user');
|
||||
|
||||
+39
-7
@@ -3,26 +3,50 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Slideshow from '@/components/Slideshow';
|
||||
import CalendarStrip from '@/components/CalendarStrip';
|
||||
import MeasuredText from '@/components/MeasuredText';
|
||||
import WeatherWidget from '@/components/WeatherWidget';
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher';
|
||||
import { t, getLanguage } from '@/lib/i18n';
|
||||
import { t, useLanguage } from '@/lib/i18n';
|
||||
|
||||
const gold = '#E8A849';
|
||||
const navy = '#010D1E';
|
||||
const cream = '#f5f0e8';
|
||||
|
||||
const homeCopy = {
|
||||
en: {
|
||||
title: 'La Huasca',
|
||||
subtitle: 'A mountain retreat with warm hospitality and local flavor.',
|
||||
highlights: ['common.hotel', 'common.restaurant', 'common.andean_highlands'],
|
||||
},
|
||||
es: {
|
||||
title: 'La Huasca',
|
||||
subtitle: 'Un refugio de montaña con hospitalidad cálida y sabor local.',
|
||||
highlights: ['common.hotel', 'common.restaurant', 'common.andean_highlands'],
|
||||
},
|
||||
} as const;
|
||||
|
||||
const fallbackTaglineKey = 'common.home_tagline';
|
||||
|
||||
export default function HomePage() {
|
||||
const [tagline, setTagline] = useState('Hotel · Restaurant · Andean Highlands');
|
||||
const language = useLanguage();
|
||||
const [tagline, setTagline] = useState(t(fallbackTaglineKey, language));
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const fallbackTagline = t(fallbackTaglineKey, language);
|
||||
setTagline(fallbackTagline);
|
||||
fetch('/api/site-assets?key=tagline')
|
||||
.then((r) => r.json())
|
||||
.then((j) => {
|
||||
if (j.data) setTagline(j.data);
|
||||
if (!cancelled && j.data) setTagline(j.data);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [language]);
|
||||
|
||||
const copy = homeCopy[language];
|
||||
|
||||
return (
|
||||
<main style={{ minHeight: 'calc(100vh - 56px)', background: navy, color: cream }}>
|
||||
@@ -64,10 +88,18 @@ export default function HomePage() {
|
||||
color: cream,
|
||||
}}
|
||||
>
|
||||
La Huasca
|
||||
{copy.title}
|
||||
</h1>
|
||||
<p style={{ fontSize: '18px', lineHeight: 1.55, color: 'rgba(245,240,232,0.78)', maxWidth: '52ch', margin: '0 0 2rem' }}>
|
||||
{t('common.hotel', getLanguage())} · {t('common.restaurant', getLanguage())} · {t('common.andean_highlands', getLanguage())}
|
||||
{copy.highlights.map((key, index) => (
|
||||
<span key={key}>
|
||||
{t(key, language)}
|
||||
{index < copy.highlights.length - 1 ? ' · ' : ''}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
<p style={{ fontSize: '16px', lineHeight: 1.65, color: 'rgba(245,240,232,0.7)', maxWidth: '58ch', margin: 0 }}>
|
||||
{copy.subtitle}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user