diff --git a/backend/src/database/db.js b/backend/src/database/db.js new file mode 100644 index 0000000..0862445 --- /dev/null +++ b/backend/src/database/db.js @@ -0,0 +1,85 @@ +const knex = require('knex'); +const path = require('path'); + +const db = knex({ + client: 'sqlite3', + connection: { + filename: path.join(__dirname, '../../data/photo_sharing.db') + }, + useNullAsDefault: true +}); + +async function initializeDatabase() { + // Events table + await db.schema.createTableIfNotExists('events', (table) => { + table.increments('id').primary(); + table.string('slug').unique().notNullable(); + table.string('event_type').notNullable(); + table.string('event_name').notNullable(); + table.date('event_date').notNullable(); + table.string('host_email').notNullable(); + table.string('admin_email').notNullable(); + table.string('password_hash').notNullable(); + table.text('welcome_message'); + table.string('color_theme'); + table.string('share_link').unique().notNullable(); + table.datetime('created_at').defaultTo(db.fn.now()); + table.datetime('expires_at').notNullable(); + table.boolean('is_active').defaultTo(true); + table.boolean('is_archived').defaultTo(false); + table.string('archive_path'); + table.datetime('archived_at'); + }); + + // Photo metadata table + await db.schema.createTableIfNotExists('photos', (table) => { + table.increments('id').primary(); + table.integer('event_id').references('id').inTable('events').onDelete('CASCADE'); + table.string('filename').notNullable(); + table.string('path').notNullable(); + table.string('thumbnail_path'); + table.string('type').notNullable(); // 'collage' or 'individual' + table.integer('size_bytes'); + table.datetime('uploaded_at').defaultTo(db.fn.now()); + table.integer('view_count').defaultTo(0); + table.integer('download_count').defaultTo(0); + }); + + // Access logs table + await db.schema.createTableIfNotExists('access_logs', (table) => { + table.increments('id').primary(); + table.integer('event_id').references('id').inTable('events'); + table.string('ip_address'); + table.string('user_agent'); + table.string('action'); // 'view', 'download', 'login_success', 'login_fail' + table.string('photo_id'); + table.datetime('timestamp').defaultTo(db.fn.now()); + }); + + // Email queue table + await db.schema.createTableIfNotExists('email_queue', (table) => { + table.increments('id').primary(); + table.integer('event_id').references('id').inTable('events'); + table.string('recipient_email').notNullable(); + table.string('email_type').notNullable(); // 'creation', 'warning', 'expiration', 'archive_complete' + table.json('email_data'); + table.string('status').defaultTo('pending'); // 'pending', 'sent', 'failed' + table.datetime('scheduled_at').defaultTo(db.fn.now()); + table.datetime('sent_at'); + table.text('error_message'); + table.integer('retry_count').defaultTo(0); + }); + + // Admin users table + await db.schema.createTableIfNotExists('admin_users', (table) => { + table.increments('id').primary(); + table.string('username').unique().notNullable(); + table.string('email').unique().notNullable(); + table.string('password_hash').notNullable(); + table.boolean('is_active').defaultTo(true); + table.datetime('created_at').defaultTo(db.fn.now()); + table.datetime('last_login'); + }); +} + +module.exports = { db, initializeDatabase };