fix: slide image upload - clear image_url when image_id set, fix slides.image_url column type to TEXT, add bed amenities (Queen, 2 Queen)

This commit is contained in:
2026-06-29 21:38:27 -05:00
parent 91fce014fd
commit 2dc949553e
13 changed files with 1169 additions and 58 deletions
+21 -5
View File
@@ -17,22 +17,38 @@ export async function verifyPassword(password: string, hash: string) {
return bcrypt.compareSync(password, hash);
}
export async function createUser(username: string, password: string, role: 'admin' | 'user') {
export async function createUser(
username: string,
password: string,
role: 'admin' | 'user',
profile?: { first_name?: string; last_name?: string; email?: string; phone?: string; country?: string; preferred_language?: string }
) {
const hash = await hashPassword(password);
const { rows } = await db.query(
'INSERT INTO users (username, password_hash, role) VALUES ($1, $2, $3) ON CONFLICT (username) DO UPDATE SET password_hash = EXCLUDED.password_hash, role = EXCLUDED.role RETURNING id, username, role',
[username, hash, role]
`INSERT INTO users (username, password_hash, role, first_name, last_name, email, phone, country, preferred_language)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (username) DO UPDATE SET password_hash = EXCLUDED.password_hash, role = EXCLUDED.role
RETURNING id, username, role, first_name, last_name, email, phone, country, preferred_language`,
[username, hash, role, profile?.first_name || null, profile?.last_name || null, profile?.email || null, profile?.phone || null, profile?.country || null, profile?.preferred_language || 'es']
);
return rows[0];
}
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]);
const { rows } = await db.query('SELECT id, username, password_hash, role, preferred_language, terms_accepted_at, first_name, last_name 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 };
return {
id: user.id,
username: user.username,
role: user.role,
preferred_language: user.preferred_language || 'es',
terms_accepted_at: user.terms_accepted_at,
first_name: user.first_name,
last_name: user.last_name
};
}
export async function createSession(user: { id: number; username: string; role: string }) {
+34
View File
@@ -222,6 +222,38 @@ export async function initSchema() {
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_name VARCHAR(100);
`);
await db.query(`
ALTER TABLE users ADD COLUMN IF NOT EXISTS preferred_language VARCHAR(10) DEFAULT 'es';
`);
await db.query(`
ALTER TABLE users ADD COLUMN IF NOT EXISTS terms_accepted_at TIMESTAMP;
`);
await db.query(`
ALTER TABLE users ADD COLUMN IF NOT EXISTS email VARCHAR(255);
`);
await db.query(`
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(50);
`);
await db.query(`
ALTER TABLE users ADD COLUMN IF NOT EXISTS country VARCHAR(100);
`);
await db.query(`
CREATE TABLE IF NOT EXISTS terms_content (
id SERIAL PRIMARY KEY,
language VARCHAR(10) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
version VARCHAR(20) NOT NULL DEFAULT '1.0',
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(language, version)
);
`);
await db.query(`
CREATE TABLE IF NOT EXISTS availability (
id SERIAL PRIMARY KEY,
@@ -397,6 +429,8 @@ export async function seed() {
['Heating', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"/></svg>', 9],
['Balcony', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="10" rx="2"/><line x1="3" y1="17" x2="21" y2="17"/><line x1="7" y1="11" x2="7" y2="21"/><line x1="12" y1="11" x2="12" y2="21"/><line x1="17" y1="11" x2="17" y2="21"/></svg>', 10],
['King Bed', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 18v-4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4"/><path d="M2 14v-2a2 2 0 0 1 2-2h1"/><path d="M22 14v-2a2 2 0 0 0-2-2h-1"/><path d="M5 4h14a2 2 0 0 1 2 2v2H3V6a2 2 0 0 1 2-2z"/><rect x="3" y="10" width="18" height="4" rx="1"/></svg>', 11],
['Queen Bed', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 18v-3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3"/><path d="M2 13v-2a2 2 0 0 1 2-2h1"/><path d="M18 13v-2a2 2 0 0 0-2-2h-1"/><path d="M4 5h10a2 2 0 0 1 2 2v1H2V7a2 2 0 0 1 2-2z"/><rect x="2" y="9" width="14" height="3" rx="1"/></svg>', 13],
['2 Queen Beds', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 15v-2a1.5 1.5 0 0 1 1.5-1.5H10a1.5 1.5 0 0 1 1.5 1.5v2"/><path d="M12.5 15v-2a1.5 1.5 0 0 1 1.5-1.5H21.5a1.5 1.5 0 0 1 1.5 1.5v2"/><path d="M1 11V9.5A1.5 1.5 0 0 1 2.5 8h8A1.5 1.5 0 0 1 12 9.5V11"/><path d="M12 11V9.5A1.5 1.5 0 0 1 13.5 8h8A1.5 1.5 0 0 1 23 9.5V11"/><rect x="1" y="8" width="11" height="2.5" rx="0.75"/><rect x="12" y="8" width="11" height="2.5" rx="0.75"/></svg>', 14],
['Mountain View', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m8 3 4 8 5-5 5 15H2L8 3z"/></svg>', 12],
];