Add database integration, auth API, admin/user portals, and deploy-db.sh
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { SignJWT, jwtVerify } from 'jose';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { db } from './db';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production';
|
||||
const secret = new TextEncoder().encode(JWT_SECRET);
|
||||
|
||||
export async function hashPassword(password: string) {
|
||||
return bcrypt.hashSync(password, 10);
|
||||
}
|
||||
|
||||
export async function verifyPassword(password: string, hash: string) {
|
||||
return bcrypt.compareSync(password, hash);
|
||||
}
|
||||
|
||||
export async function authenticateUser(username: string, password: string) {
|
||||
const { rows } = await db.query('SELECT id, username, password_hash, role FROM users WHERE username = $1', [username]);
|
||||
if (rows.length === 0) return null;
|
||||
const user = rows[0];
|
||||
const valid = await verifyPassword(password, user.password_hash);
|
||||
if (!valid) return null;
|
||||
return { id: user.id, username: user.username, role: user.role };
|
||||
}
|
||||
|
||||
export async function createSession(user: { id: number; username: string; role: string }) {
|
||||
const token = await new SignJWT({ id: user.id, username: user.username, role: user.role })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setExpirationTime('7d')
|
||||
.sign(secret);
|
||||
cookies().set('session', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 7 });
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function getSession() {
|
||||
const token = cookies().get('session')?.value;
|
||||
if (!token) return null;
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, secret);
|
||||
return payload as { id: number; username: string; role: string };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearSession() {
|
||||
cookies().delete('session');
|
||||
}
|
||||
|
||||
export async function requireRole(role: 'admin' | 'user') {
|
||||
const session = await getSession();
|
||||
if (!session || session.role !== role) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function requireAuth() {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
return session;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
|
||||
export const db = new Pool({ connectionString });
|
||||
|
||||
export async function query(text: string, params?: unknown[]) {
|
||||
const result = await db.query(text, params);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { db } from './db';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
export async function initSchema() {
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'user')),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS images (
|
||||
id SERIAL PRIMARY KEY,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
uploaded_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS reservations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
date DATE NOT NULL,
|
||||
time TIME NOT NULL,
|
||||
guests INTEGER NOT NULL,
|
||||
table_name VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS coupons (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
code VARCHAR(50) NOT NULL,
|
||||
discount VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS offers (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS benefits (
|
||||
id SERIAL PRIMARY KEY,
|
||||
text VARCHAR(500) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key VARCHAR(100) PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
export async function seed() {
|
||||
const { rows: adminRows } = await db.query(`SELECT id FROM users WHERE username = 'admin'`);
|
||||
if (adminRows.length === 0) {
|
||||
const adminHash = bcrypt.hashSync('admin', 10);
|
||||
const userHash = bcrypt.hashSync('user', 10);
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO users (username, password_hash, role) VALUES ($1, $2, $3), ($4, $5, $6)`,
|
||||
['admin', adminHash, 'admin', 'user', userHash, 'user']
|
||||
);
|
||||
}
|
||||
|
||||
const { rows: offerRows } = await db.query(`SELECT id FROM offers`);
|
||||
if (offerRows.length === 0) {
|
||||
await db.query(
|
||||
`INSERT INTO offers (title, description) VALUES
|
||||
($1, $2), ($3, $4)`,
|
||||
[
|
||||
'Summer Wine Weekend', 'Complimentary wine tasting with every two-night stay.',
|
||||
'Sunset Dinner', 'Three-course menu on the terrace every Friday.',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
const { rows: benefitRows } = await db.query(`SELECT id FROM benefits`);
|
||||
if (benefitRows.length === 0) {
|
||||
await db.query(
|
||||
`INSERT INTO benefits (text) VALUES
|
||||
($1), ($2), ($3), ($4)`,
|
||||
[
|
||||
'Priority booking for peak weekends',
|
||||
'Late checkout on availability',
|
||||
'Free welcome cocktail',
|
||||
'Member-only events and tastings',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
const { rows: configRows } = await db.query(`SELECT key FROM config WHERE key = 'wifi_password'`);
|
||||
if (configRows.length === 0) {
|
||||
await db.query(
|
||||
`INSERT INTO config (key, value) VALUES ($1, $2)`,
|
||||
['wifi_password', 'LaHuascaGuest2026']
|
||||
);
|
||||
}
|
||||
|
||||
const { rows: userResRows } = await db.query(`SELECT id FROM reservations WHERE user_id = (SELECT id FROM users WHERE username = 'user')`);
|
||||
if (userResRows.length === 0) {
|
||||
await db.query(
|
||||
`INSERT INTO reservations (user_id, date, time, guests, table_name)
|
||||
SELECT id, $2, $3, $4, $5 FROM users WHERE username = $1`,
|
||||
['user', '2026-07-12', '19:00', 4, 'Terrace 3']
|
||||
);
|
||||
await db.query(
|
||||
`INSERT INTO reservations (user_id, date, time, guests, table_name)
|
||||
SELECT id, $2, $3, $4, $5 FROM users WHERE username = $1`,
|
||||
['user', '2026-07-25', '20:30', 2, 'Barrel 7']
|
||||
);
|
||||
}
|
||||
|
||||
const { rows: userCouponRows } = await db.query(`SELECT id FROM coupons WHERE user_id = (SELECT id FROM users WHERE username = 'user')`);
|
||||
if (userCouponRows.length === 0) {
|
||||
await db.query(
|
||||
`INSERT INTO coupons (user_id, code, discount)
|
||||
SELECT id, $2, $3 FROM users WHERE username = $1`,
|
||||
['user', 'WELCOME20', '20% off your next stay']
|
||||
);
|
||||
await db.query(
|
||||
`INSERT INTO coupons (user_id, code, discount)
|
||||
SELECT id, $2, $3 FROM users WHERE username = $1`,
|
||||
['user', 'COFFEE5', '$5 off any coffee tasting']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetDatabase() {
|
||||
await db.query('DROP TABLE IF EXISTS config, benefits, offers, coupons, reservations, images, users CASCADE');
|
||||
await initSchema();
|
||||
await seed();
|
||||
}
|
||||
Reference in New Issue
Block a user