theming, date, time localisation, additional features, seeding initial cleanup

This commit is contained in:
mikicvi
2025-09-26 21:12:59 +01:00
parent b89d91ade2
commit 22c462c61c
43 changed files with 2647 additions and 550 deletions
+40
View File
@@ -0,0 +1,40 @@
# Old Seed Scripts (Archived)
These are the original individual seed scripts that have been consolidated into the new unified database setup system.
## Consolidated Into:
- **setup-database.ts** - Unified setup script with all functionality
- **reset-db.ts** - Improved reset script with safety features
## Original Scripts:
- `seed-data.ts` - Sample bookings and activity logs
- `seed-announcements.ts` - Test announcements
- `seed-time-slots.ts` - Time slot configuration
- `init-admin-data.ts` - Admin dashboard initialization
- `reset-database.ts` - Original reset script
## Migration Notes:
All functionality from these scripts has been intelligently integrated into the new system:
### New Command Equivalents:
| Old Script | New Command |
|------------|-------------|
| `tsx scripts/seed-data.ts` | `npm run db:setup` |
| `tsx scripts/seed-announcements.ts` | Integrated into setup |
| `tsx scripts/seed-time-slots.ts` | Integrated into setup |
| `tsx scripts/init-admin-data.ts` | Integrated into setup |
| `tsx scripts/reset-database.ts` | `npm run db:reset-confirm` |
### Advantages of New System:
1. **Intelligent Setup** - Automatically handles dependencies and order
2. **Flexible Options** - Essential-only or full sample data modes
3. **Safety Features** - Reset confirmation and detailed logging
4. **Better Documentation** - Comprehensive help and summaries
5. **Single Source** - All database setup in one place
These old scripts are kept for reference but should not be used in new development.
+90
View File
@@ -0,0 +1,90 @@
import { db } from '../lib/db';
import { settings, metrics } from '../lib/db/schema';
import { randomUUID } from 'crypto';
import { eq } from 'drizzle-orm';
async function initializeAdminData() {
try {
console.log('Initializing admin dashboard data...');
const now = new Date();
// Initialize default settings
console.log('Setting up default settings...');
const defaultSettings = [
{
id: randomUUID(),
key: 'booking_window_days',
value: '14',
updatedAt: now,
},
{
id: randomUUID(),
key: 'max_booking_duration_hours',
value: '2',
updatedAt: now,
},
{
id: randomUUID(),
key: 'max_bookings_per_user_per_hour_per_day',
value: '1',
updatedAt: now,
},
{
id: randomUUID(),
key: 'allow_booking_modifications',
value: 'true',
updatedAt: now,
},
{
id: randomUUID(),
key: 'booking_modification_hours_before',
value: '2',
updatedAt: now,
},
];
// Insert settings if they don't exist
for (const setting of defaultSettings) {
const existingSetting = await db.select().from(settings).where(eq(settings.key, setting.key)).limit(1);
if (existingSetting.length === 0) {
await db.insert(settings).values(setting);
console.log(`✓ Setting '${setting.key}' initialized with value '${setting.value}'`);
} else {
console.log(`- Setting '${setting.key}' already exists`);
}
}
// Initialize current month's metrics
console.log('Initializing monthly metrics...');
const currentMonth = now.toISOString().substring(0, 7); // "2025-09"
const existingMetric = await db.select().from(metrics).where(eq(metrics.period, currentMonth)).limit(1);
if (existingMetric.length === 0) {
const monthlyMetric = {
id: randomUUID(),
metricType: 'monthly_bookings',
period: currentMonth,
value: 0,
createdAt: now,
updatedAt: now,
};
await db.insert(metrics).values(monthlyMetric);
console.log(`✓ Monthly metrics initialized for ${currentMonth}`);
} else {
console.log(`- Monthly metrics for ${currentMonth} already exist`);
}
console.log('Admin dashboard data initialization complete!');
} catch (error) {
console.error('Error initializing admin data:', error);
throw error;
}
}
initializeAdminData();
+263
View File
@@ -0,0 +1,263 @@
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from '../lib/db/schema';
import { sql } from 'drizzle-orm';
import { randomUUID } from 'crypto';
import bcrypt from 'bcryptjs';
const sqlite = new Database('./sqlite.db');
const db = drizzle(sqlite, { schema });
async function resetDatabase() {
console.log('Resetting database...');
// Drop all tables
const tables = [
'activity_logs',
'bookings',
'announcements',
'time_slots',
'settings',
'courts',
'users',
'__drizzle_migrations',
'__old_push_courts',
'__old_push_users',
];
for (const table of tables) {
try {
await db.run(sql.raw(`DROP TABLE IF EXISTS ${table}`));
console.log(`Dropped table: ${table}`);
} catch (error) {
console.log(`Table ${table} doesn't exist or error dropping:`, error);
}
}
// Create all tables with current schema
console.log('Creating tables...');
// Users table
await db.run(sql`
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
surname TEXT NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Courts table
await db.run(sql`
CREATE TABLE courts (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Settings table
await db.run(sql`
CREATE TABLE settings (
id TEXT PRIMARY KEY,
key TEXT NOT NULL UNIQUE,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Time slots table
await db.run(sql`
CREATE TABLE time_slots (
id TEXT PRIMARY KEY,
day_of_week INTEGER NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Bookings table
await db.run(sql`
CREATE TABLE bookings (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
court_id TEXT NOT NULL REFERENCES courts(id) ON DELETE CASCADE,
date TEXT NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'cancelled')),
notes TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Announcements table with all required columns
await db.run(sql`
CREATE TABLE announcements (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
priority TEXT NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high')),
expires_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Activity logs table
await db.run(sql`
CREATE TABLE activity_logs (
id TEXT PRIMARY KEY,
user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
action TEXT NOT NULL,
entity_type TEXT NOT NULL,
entity_id TEXT,
details TEXT,
ip_address TEXT,
user_agent TEXT,
created_at INTEGER NOT NULL
)
`);
console.log('All tables created successfully!');
// Insert seed data
console.log('Inserting seed data...');
const now = Date.now();
// Create admin user
const hashedPassword = await bcrypt.hash('admin123', 12);
await db.insert(schema.users).values({
id: randomUUID(),
email: 'admin@ttbooking.com',
name: 'Admin',
surname: 'User',
password: hashedPassword,
role: 'admin',
createdAt: new Date(now),
updatedAt: new Date(now),
});
// Create test user
const testPassword = await bcrypt.hash('password123', 12);
await db.insert(schema.users).values({
id: randomUUID(),
email: 'user@test.com',
name: 'Test',
surname: 'User',
password: testPassword,
role: 'user',
createdAt: new Date(now),
updatedAt: new Date(now),
});
// Create courts
const court1Id = randomUUID();
const court2Id = randomUUID();
await db.insert(schema.courts).values([
{
id: court1Id,
name: 'Court 1',
isActive: true,
createdAt: new Date(now),
updatedAt: new Date(now),
},
{
id: court2Id,
name: 'Court 2',
isActive: true,
createdAt: new Date(now),
updatedAt: new Date(now),
},
]);
// Insert default settings
await db.insert(schema.settings).values([
{
id: randomUUID(),
key: 'booking_window_days',
value: '7',
updatedAt: new Date(now),
},
{
id: randomUUID(),
key: 'max_booking_duration_hours',
value: '2',
updatedAt: new Date(now),
},
{
id: randomUUID(),
key: 'min_booking_duration_minutes',
value: '30',
updatedAt: new Date(now),
},
{
id: randomUUID(),
key: 'booking_start_time',
value: '08:00',
updatedAt: new Date(now),
},
{
id: randomUUID(),
key: 'booking_end_time',
value: '22:00',
updatedAt: new Date(now),
},
{
id: randomUUID(),
key: 'allow_weekend_bookings',
value: 'true',
updatedAt: new Date(now),
},
]);
// Create time slots for all days (8 AM to 10 PM)
const timeSlotData = [];
for (let day = 0; day < 7; day++) {
for (let hour = 8; hour < 22; hour += 2) {
timeSlotData.push({
id: randomUUID(),
dayOfWeek: day,
startTime: `${hour.toString().padStart(2, '0')}:00`,
endTime: `${(hour + 2).toString().padStart(2, '0')}:00`,
isActive: true,
createdAt: new Date(now),
updatedAt: new Date(now),
});
}
}
await db.insert(schema.timeSlots).values(timeSlotData);
// Create sample announcement
await db.insert(schema.announcements).values({
id: randomUUID(),
title: 'Welcome to Table Tennis Booking System',
content: 'Book your court times easily and manage your games efficiently.',
isActive: true,
priority: 'high',
expiresAt: null,
createdAt: new Date(now),
updatedAt: new Date(now),
});
console.log('Seed data inserted successfully!');
console.log('Database reset complete!');
sqlite.close();
}
resetDatabase().catch(console.error);
+50
View File
@@ -0,0 +1,50 @@
import { db } from '@/lib/db';
import { announcements } from '@/lib/db/schema';
import { randomUUID } from 'crypto';
async function seedAnnouncements() {
try {
const testAnnouncements = [
{
id: randomUUID(),
title: 'Welcome to the New Booking System!',
content:
'We have upgraded our table tennis booking system with new features including mobile support, partner booking, and booking management. Enjoy your games!',
priority: 'high' as const,
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: randomUUID(),
title: 'Court Maintenance Schedule',
content:
'Court 2 will be under maintenance this Friday from 2 PM to 4 PM. Please plan your bookings accordingly.',
priority: 'medium' as const,
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: randomUUID(),
title: 'New Partnership Feature',
content:
'You can now specify your playing partner when making a booking. This helps other players know who will be using the court.',
priority: 'low' as const,
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
];
for (const announcement of testAnnouncements) {
await db.insert(announcements).values(announcement);
}
console.log('Test announcements created successfully!');
} catch (error) {
console.error('Error creating test announcements:', error);
}
}
seedAnnouncements();
+203
View File
@@ -0,0 +1,203 @@
import { db } from '../lib/db';
import { users, courts as courtsTable, bookings, announcements, activityLogs } from '../lib/db/schema';
import { randomUUID } from 'crypto';
import bcrypt from 'bcryptjs';
async function seedData() {
try {
console.log('Starting data seeding...');
// Get existing users to add sample bookings and activities
const existingUsers = await db.select().from(users);
if (existingUsers.length < 2) {
console.log('Not enough users found. Please run the reset-database script first.');
return;
}
const adminUser = existingUsers.find((u) => u.role === 'admin');
const regularUser = existingUsers.find((u) => u.role === 'user');
const courts = await db.select().from(courtsTable);
if (!adminUser || !regularUser || courts.length === 0) {
console.log('Missing admin user, regular user, or courts. Please run reset-database first.');
return;
}
const now = new Date();
const today = now.toISOString().split('T')[0];
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString().split('T')[0];
// Add some sample bookings
console.log('Creating sample bookings...');
const sampleBookings = [
{
id: randomUUID(),
userId: regularUser.id,
courtId: courts[0].id,
date: today,
startTime: '19:00',
endTime: '20:00',
status: 'active' as const,
notes: 'Regular evening practice session',
createdAt: new Date(now.getTime() - 2 * 60 * 60 * 1000), // 2 hours ago
updatedAt: new Date(now.getTime() - 2 * 60 * 60 * 1000),
},
{
id: randomUUID(),
userId: regularUser.id,
courtId: courts[1] ? courts[1].id : courts[0].id,
date: tomorrow,
startTime: '20:00',
endTime: '21:00',
status: 'active' as const,
notes: 'Tournament preparation',
createdAt: new Date(now.getTime() - 1 * 60 * 60 * 1000), // 1 hour ago
updatedAt: new Date(now.getTime() - 1 * 60 * 60 * 1000),
},
];
await db.insert(bookings).values(sampleBookings);
// Add sample activity logs
console.log('Creating sample activity logs...');
const sampleLogs = [
{
id: randomUUID(),
userId: adminUser.id,
action: 'login',
entityType: 'user',
entityId: adminUser.id,
details: JSON.stringify({
email: adminUser.email,
role: adminUser.role,
loginMethod: 'password',
}),
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
createdAt: new Date(now.getTime() - 3 * 60 * 60 * 1000), // 3 hours ago
},
{
id: randomUUID(),
userId: regularUser.id,
action: 'create_booking',
entityType: 'booking',
entityId: sampleBookings[0].id,
details: JSON.stringify({
courtId: courts[0].id,
courtName: courts[0].name,
date: today,
startTime: '19:00',
endTime: '20:00',
}),
ipAddress: '192.168.1.101',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
createdAt: new Date(now.getTime() - 2 * 60 * 60 * 1000), // 2 hours ago
},
{
id: randomUUID(),
userId: regularUser.id,
action: 'login',
entityType: 'user',
entityId: regularUser.id,
details: JSON.stringify({
email: regularUser.email,
role: regularUser.role,
loginMethod: 'password',
}),
ipAddress: '192.168.1.101',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
createdAt: new Date(now.getTime() - 2.5 * 60 * 60 * 1000), // 2.5 hours ago
},
{
id: randomUUID(),
userId: adminUser.id,
action: 'create_announcement',
entityType: 'announcement',
entityId: null,
details: JSON.stringify({
title: 'System Maintenance',
priority: 'high',
action: 'created_via_seed',
}),
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
createdAt: new Date(now.getTime() - 1 * 60 * 60 * 1000), // 1 hour ago
},
{
id: randomUUID(),
userId: regularUser.id,
action: 'create_booking',
entityType: 'booking',
entityId: sampleBookings[1].id,
details: JSON.stringify({
courtId: courts[1] ? courts[1].id : courts[0].id,
courtName: courts[1] ? courts[1].name : courts[0].name,
date: tomorrow,
startTime: '20:00',
endTime: '21:00',
}),
ipAddress: '192.168.1.101',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
createdAt: new Date(now.getTime() - 1 * 60 * 60 * 1000), // 1 hour ago
},
{
id: randomUUID(),
userId: adminUser.id,
action: 'update_settings',
entityType: 'settings',
entityId: null,
details: JSON.stringify({
changedSettings: ['booking_window_days', 'max_booking_duration_hours'],
previousValues: { booking_window_days: '7', max_booking_duration_hours: '2' },
newValues: { booking_window_days: '14', max_booking_duration_hours: '3' },
}),
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
createdAt: new Date(now.getTime() - 30 * 60 * 1000), // 30 minutes ago
},
];
await db.insert(activityLogs).values(sampleLogs);
// Add more announcements for testing
console.log('Creating additional announcements...');
const additionalAnnouncements = [
{
id: randomUUID(),
title: 'New Court Rules',
content: 'Please remember to clean up after your sessions and respect the time limits.',
isActive: true,
priority: 'medium' as const,
expiresAt: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000), // 1 week from now
createdAt: new Date(now.getTime() - 4 * 60 * 60 * 1000), // 4 hours ago
updatedAt: new Date(now.getTime() - 4 * 60 * 60 * 1000),
},
{
id: randomUUID(),
title: 'Tournament Sign-ups Open',
content: 'The annual table tennis tournament sign-ups are now open! Register by the end of this month.',
isActive: true,
priority: 'high' as const,
expiresAt: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
createdAt: new Date(now.getTime() - 24 * 60 * 60 * 1000), // 1 day ago
updatedAt: new Date(now.getTime() - 24 * 60 * 60 * 1000),
},
];
await db.insert(announcements).values(additionalAnnouncements);
console.log('Sample data seeding completed successfully!');
console.log(`Created:
- ${sampleBookings.length} sample bookings
- ${sampleLogs.length} activity logs
- ${additionalAnnouncements.length} additional announcements`);
} catch (error) {
console.error('Error seeding data:', error);
}
}
seedData();
+55
View File
@@ -0,0 +1,55 @@
import { db } from '../lib/db';
import { timeSlots } from '../lib/db/schema';
async function seedTimeSlots() {
console.log('🌱 Seeding time slots...');
// Example time slots for different days
const timeSlotData = [
// Sunday: 12:00 - 17:00
{ dayOfWeek: 0, startTime: '12:00', endTime: '17:00' },
// Monday: 19:00 - 23:00
{ dayOfWeek: 1, startTime: '19:00', endTime: '23:00' },
// Tuesday: 19:00 - 23:00
{ dayOfWeek: 2, startTime: '19:00', endTime: '23:00' },
// Wednesday: NO SLOTS (facility closed)
// { dayOfWeek: 3, startTime: '18:00', endTime: '22:00' },
// Thursday: NO SLOTS (facility closed)
// { dayOfWeek: 4, startTime: '19:00', endTime: '23:00' },
// Friday: 18:00 - 22:00
{ dayOfWeek: 5, startTime: '18:00', endTime: '22:00' },
// Saturday: 10:00 - 18:00
{ dayOfWeek: 6, startTime: '10:00', endTime: '18:00' },
];
for (const slot of timeSlotData) {
await db.insert(timeSlots).values({
id: crypto.randomUUID(),
dayOfWeek: slot.dayOfWeek,
startTime: slot.startTime,
endTime: slot.endTime,
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
});
}
console.log('✅ Time slots seeding completed');
}
// Run the seeding function
seedTimeSlots()
.then(() => {
console.log('Time slots seeding process completed');
process.exit(0);
})
.catch((error) => {
console.error('Error during time slots seeding:', error);
process.exit(1);
});