package db import ( "database/sql" "fmt" "reservation-system/backend/internal/models" "time" _ "github.com/mattn/go-sqlite3" ) type DB struct { conn *sql.DB } func New(dbPath string) (*DB, error) { conn, err := sql.Open("sqlite3", dbPath) if err != nil { return nil, err } if err := conn.Ping(); err != nil { return nil, err } db := &DB{conn: conn} if err := db.Migrate(); err != nil { return nil, err } return db, nil } func (db *DB) Migrate() error { schema := ` CREATE TABLE IF NOT EXISTS rooms ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, capacity INTEGER NOT NULL DEFAULT 0, color TEXT DEFAULT '#3b82f6', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS tables ( id INTEGER PRIMARY KEY AUTOINCREMENT, room_id INTEGER NOT NULL, x INTEGER DEFAULT 0, y INTEGER DEFAULT 0, width INTEGER DEFAULT 100, height INTEGER DEFAULT 100, max_guests INTEGER NOT NULL, shape TEXT DEFAULT 'rect', status TEXT DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS reservations ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_id INTEGER NOT NULL, date TEXT NOT NULL, time_from TEXT NOT NULL, time_to TEXT NOT NULL, guests INTEGER NOT NULL, name TEXT NOT NULL, phone TEXT, email TEXT, source TEXT DEFAULT 'manual', notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (table_id) REFERENCES tables(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS room_bookings ( id INTEGER PRIMARY KEY AUTOINCREMENT, room_id INTEGER NOT NULL, date TEXT NOT NULL, time_from TEXT NOT NULL, time_to TEXT NOT NULL, guests INTEGER NOT NULL, name TEXT NOT NULL, phone TEXT, email TEXT, event_type TEXT, notes TEXT, status TEXT DEFAULT 'confirmed', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS table_merges ( id INTEGER PRIMARY KEY AUTOINCREMENT, parent_table_id INTEGER NOT NULL, child_table_id INTEGER NOT NULL, merged_name TEXT, active BOOLEAN DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (parent_table_id) REFERENCES tables(id) ON DELETE CASCADE, FOREIGN KEY (child_table_id) REFERENCES tables(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS email_config ( id INTEGER PRIMARY KEY AUTOINCREMENT, host TEXT NOT NULL, port INTEGER DEFAULT 993, user TEXT NOT NULL, password TEXT NOT NULL, ssl BOOLEAN DEFAULT 1, folder TEXT DEFAULT 'INBOX' ); CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, is_admin BOOLEAN DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_reservations_date ON reservations(date); CREATE INDEX IF NOT EXISTS idx_reservations_table ON reservations(table_id); CREATE INDEX IF NOT EXISTS idx_tables_room ON tables(room_id); CREATE INDEX IF NOT EXISTS idx_reservations_time ON reservations(time_from, time_to); CREATE INDEX IF NOT EXISTS idx_room_bookings_date ON room_bookings(date); CREATE INDEX IF NOT EXISTS idx_room_bookings_room ON room_bookings(room_id); CREATE INDEX IF NOT EXISTS idx_room_bookings_time ON room_bookings(time_from, time_to); ` _, err := db.conn.Exec(schema) return err } func (db *DB) Close() error { return db.conn.Close() } // Room Methods func (db *DB) CreateRoom(room *models.Room) error { result, err := db.conn.Exec( "INSERT INTO rooms (name, capacity, color) VALUES (?, ?, ?)", room.Name, room.Capacity, room.Color, ) if err != nil { return err } id, _ := result.LastInsertId() room.ID = int(id) room.CreatedAt = time.Now() return nil } func (db *DB) GetRooms() ([]models.Room, error) { rows, err := db.conn.Query("SELECT id, name, capacity, color, created_at FROM rooms ORDER BY name") if err != nil { return nil, err } defer rows.Close() var rooms []models.Room for rows.Next() { var r models.Room if err := rows.Scan(&r.ID, &r.Name, &r.Capacity, &r.Color, &r.CreatedAt); err != nil { return nil, err } rooms = append(rooms, r) } return rooms, nil } func (db *DB) GetRoom(id int) (*models.Room, error) { var r models.Room err := db.conn.QueryRow( "SELECT id, name, capacity, color, created_at FROM rooms WHERE id = ?", id, ).Scan(&r.ID, &r.Name, &r.Capacity, &r.Color, &r.CreatedAt) if err == sql.ErrNoRows { return nil, nil } return &r, err } func (db *DB) UpdateRoom(room *models.Room) error { _, err := db.conn.Exec( "UPDATE rooms SET name = ?, capacity = ?, color = ? WHERE id = ?", room.Name, room.Capacity, room.Color, room.ID, ) return err } func (db *DB) DeleteRoom(id int) error { _, err := db.conn.Exec("DELETE FROM rooms WHERE id = ?", id) return err } // Table Methods func (db *DB) CreateTable(table *models.Table) error { result, err := db.conn.Exec( "INSERT INTO tables (room_id, x, y, width, height, max_guests, shape, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", table.RoomID, table.X, table.Y, table.Width, table.Height, table.MaxGuests, table.Shape, table.Status, ) if err != nil { return err } id, _ := result.LastInsertId() table.ID = int(id) table.CreatedAt = time.Now() return nil } func (db *DB) GetTablesByRoom(roomID int) ([]models.Table, error) { rows, err := db.conn.Query(` SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name FROM tables t JOIN rooms r ON t.room_id = r.id WHERE t.room_id = ? AND t.status = 'active' ORDER BY t.id`, roomID) if err != nil { return nil, err } defer rows.Close() var tables []models.Table for rows.Next() { var t models.Table if err := rows.Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName); err != nil { return nil, err } tables = append(tables, t) } return tables, nil } func (db *DB) GetAllTables() ([]models.Table, error) { rows, err := db.conn.Query(` SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name FROM tables t JOIN rooms r ON t.room_id = r.id WHERE t.status = 'active' ORDER BY t.room_id, t.id`) if err != nil { return nil, err } defer rows.Close() var tables []models.Table for rows.Next() { var t models.Table if err := rows.Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName); err != nil { return nil, err } tables = append(tables, t) } return tables, nil } func (db *DB) GetTable(id int) (*models.Table, error) { var t models.Table err := db.conn.QueryRow(` SELECT t.id, t.room_id, t.x, t.y, t.width, t.height, t.max_guests, t.shape, t.status, t.created_at, r.name as room_name FROM tables t JOIN rooms r ON t.room_id = r.id WHERE t.id = ?`, id).Scan(&t.ID, &t.RoomID, &t.X, &t.Y, &t.Width, &t.Height, &t.MaxGuests, &t.Shape, &t.Status, &t.CreatedAt, &t.RoomName) if err == sql.ErrNoRows { return nil, nil } return &t, err } func (db *DB) UpdateTable(table *models.Table) error { _, err := db.conn.Exec( "UPDATE tables SET room_id = ?, x = ?, y = ?, width = ?, height = ?, max_guests = ?, shape = ? WHERE id = ?", table.RoomID, table.X, table.Y, table.Width, table.Height, table.MaxGuests, table.Shape, table.ID, ) return err } func (db *DB) DeleteTable(id int) error { _, err := db.conn.Exec("UPDATE tables SET status = 'deleted' WHERE id = ?", id) return err } // Reservation Methods func (db *DB) CreateReservation(r *models.Reservation) error { result, err := db.conn.Exec( "INSERT INTO reservations (table_id, date, time_from, time_to, guests, name, phone, email, source, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", r.TableID, r.Date, r.TimeFrom, r.TimeTo, r.Guests, r.Name, r.Phone, r.Email, r.Source, r.Notes, ) if err != nil { return err } id, _ := result.LastInsertId() r.ID = int(id) r.CreatedAt = time.Now() return nil } func (db *DB) GetReservationsByDate(date string) ([]models.Reservation, error) { rows, err := db.conn.Query(` SELECT r.id, r.table_id, r.date, r.time_from, r.time_to, r.guests, r.name, r.phone, r.email, r.source, r.notes, r.created_at, r.table_id as table_name FROM reservations r WHERE r.date = ? ORDER BY r.time_from`, date) if err != nil { return nil, err } defer rows.Close() var reservations []models.Reservation for rows.Next() { var res models.Reservation if err := rows.Scan(&res.ID, &res.TableID, &res.Date, &res.TimeFrom, &res.TimeTo, &res.Guests, &res.Name, &res.Phone, &res.Email, &res.Source, &res.Notes, &res.CreatedAt, &res.TableName); err != nil { return nil, err } reservations = append(reservations, res) } return reservations, nil } func (db *DB) GetReservationsByTable(tableID int, date string) ([]models.Reservation, error) { rows, err := db.conn.Query(` SELECT id, table_id, date, time_from, time_to, guests, name, phone, email, source, notes, created_at FROM reservations WHERE table_id = ? AND date = ? ORDER BY time_from`, tableID, date) if err != nil { return nil, err } defer rows.Close() var reservations []models.Reservation for rows.Next() { var res models.Reservation if err := rows.Scan(&res.ID, &res.TableID, &res.Date, &res.TimeFrom, &res.TimeTo, &res.Guests, &res.Name, &res.Phone, &res.Email, &res.Source, &res.Notes, &res.CreatedAt); err != nil { return nil, err } reservations = append(reservations, res) } return reservations, nil } func (db *DB) GetReservation(id int) (*models.Reservation, error) { var r models.Reservation err := db.conn.QueryRow(` SELECT id, table_id, date, time_from, time_to, guests, name, phone, email, source, notes, created_at FROM reservations WHERE id = ?`, id).Scan(&r.ID, &r.TableID, &r.Date, &r.TimeFrom, &r.TimeTo, &r.Guests, &r.Name, &r.Phone, &r.Email, &r.Source, &r.Notes, &r.CreatedAt) if err == sql.ErrNoRows { return nil, nil } return &r, err } func (db *DB) UpdateReservation(r *models.Reservation) error { _, err := db.conn.Exec( "UPDATE reservations SET table_id = ?, date = ?, time_from = ?, time_to = ?, guests = ?, name = ?, phone = ?, email = ?, notes = ? WHERE id = ?", r.TableID, r.Date, r.TimeFrom, r.TimeTo, r.Guests, r.Name, r.Phone, r.Email, r.Notes, r.ID, ) return err } func (db *DB) DeleteReservation(id int) error { _, err := db.conn.Exec("DELETE FROM reservations WHERE id = ?", id) return err } // Room Booking Methods (NEW) func (db *DB) CreateRoomBooking(rb *models.RoomBooking) error { result, err := db.conn.Exec( "INSERT INTO room_bookings (room_id, date, time_from, time_to, guests, name, phone, email, event_type, notes, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo, rb.Guests, rb.Name, rb.Phone, rb.Email, rb.EventType, rb.Notes, rb.Status, ) if err != nil { return err } id, _ := result.LastInsertId() rb.ID = int(id) rb.CreatedAt = time.Now() return nil } func (db *DB) GetRoomBookingsByRoom(roomID int) ([]models.RoomBooking, error) { rows, err := db.conn.Query(` SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name FROM room_bookings rb JOIN rooms r ON rb.room_id = r.id WHERE rb.room_id = ? ORDER BY rb.date DESC, rb.time_from`, roomID) if err != nil { return nil, err } defer rows.Close() var bookings []models.RoomBooking for rows.Next() { var b models.RoomBooking if err := rows.Scan(&b.ID, &b.RoomID, &b.Date, &b.TimeFrom, &b.TimeTo, &b.Guests, &b.Name, &b.Phone, &b.Email, &b.EventType, &b.Notes, &b.Status, &b.CreatedAt, &b.RoomName); err != nil { return nil, err } bookings = append(bookings, b) } return bookings, nil } func (db *DB) GetRoomBookingsByDate(date string) ([]models.RoomBooking, error) { rows, err := db.conn.Query(` SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name FROM room_bookings rb JOIN rooms r ON rb.room_id = r.id WHERE rb.date = ? ORDER BY rb.time_from`, date) if err != nil { return nil, err } defer rows.Close() var bookings []models.RoomBooking for rows.Next() { var b models.RoomBooking if err := rows.Scan(&b.ID, &b.RoomID, &b.Date, &b.TimeFrom, &b.TimeTo, &b.Guests, &b.Name, &b.Phone, &b.Email, &b.EventType, &b.Notes, &b.Status, &b.CreatedAt, &b.RoomName); err != nil { return nil, err } bookings = append(bookings, b) } return bookings, nil } func (db *DB) GetRoomBooking(id int) (*models.RoomBooking, error) { var rb models.RoomBooking err := db.conn.QueryRow(` SELECT rb.id, rb.room_id, rb.date, rb.time_from, rb.time_to, rb.guests, rb.name, rb.phone, rb.email, rb.event_type, rb.notes, rb.status, rb.created_at, r.name as room_name FROM room_bookings rb JOIN rooms r ON rb.room_id = r.id WHERE rb.id = ?`, id).Scan(&rb.ID, &rb.RoomID, &rb.Date, &rb.TimeFrom, &rb.TimeTo, &rb.Guests, &rb.Name, &rb.Phone, &rb.Email, &rb.EventType, &rb.Notes, &rb.Status, &rb.CreatedAt, &rb.RoomName) if err == sql.ErrNoRows { return nil, nil } return &rb, err } func (db *DB) UpdateRoomBooking(rb *models.RoomBooking) error { _, err := db.conn.Exec( "UPDATE room_bookings SET room_id = ?, date = ?, time_from = ?, time_to = ?, guests = ?, name = ?, phone = ?, email = ?, event_type = ?, notes = ?, status = ? WHERE id = ?", rb.RoomID, rb.Date, rb.TimeFrom, rb.TimeTo, rb.Guests, rb.Name, rb.Phone, rb.Email, rb.EventType, rb.Notes, rb.Status, rb.ID, ) return err } func (db *DB) DeleteRoomBooking(id int) error { _, err := db.conn.Exec("DELETE FROM room_bookings WHERE id = ?", id) return err } // Availability Checks func (db *DB) CheckAvailability(tableID int, date, timeFrom, timeTo string) (bool, error) { var count int err := db.conn.QueryRow(` SELECT COUNT(*) FROM reservations WHERE table_id = ? AND date = ? AND ( (time_from < ? AND time_to > ?) OR (time_from < ? AND time_to > ?) OR (time_from >= ? AND time_to <= ?) )`, tableID, date, timeTo, timeFrom, timeTo, timeFrom, timeFrom, timeTo).Scan(&count) if err != nil { return false, err } return count == 0, nil } func (db *DB) CheckRoomAvailability(roomID int, date, timeFrom, timeTo string) (bool, error) { var count int err := db.conn.QueryRow(` SELECT COUNT(*) FROM room_bookings WHERE room_id = ? AND date = ? AND status = 'confirmed' AND ( (time_from < ? AND time_to > ?) OR (time_from < ? AND time_to > ?) OR (time_from >= ? AND time_to <= ?) )`, roomID, date, timeTo, timeFrom, timeTo, timeFrom, timeFrom, timeTo).Scan(&count) if err != nil { return false, err } return count == 0, nil } func (db *DB) GetTableMerges(parentTableID int) ([]models.TableMerge, error) { rows, err := db.conn.Query(` SELECT id, parent_table_id, child_table_id, merged_name, active, created_at FROM table_merges WHERE parent_table_id = ? AND active = 1`, parentTableID) if err != nil { return nil, err } defer rows.Close() var merges []models.TableMerge for rows.Next() { var m models.TableMerge if err := rows.Scan(&m.ID, &m.ParentTableID, &m.ChildTableID, &m.MergedName, &m.Active, &m.CreatedAt); err != nil { return nil, err } merges = append(merges, m) } return merges, nil } func (db *DB) UnmergeTables(mergeID int) error { var childID int err := db.conn.QueryRow("SELECT child_table_id FROM table_merges WHERE id = ?", mergeID).Scan(&childID) if err != nil { return err } _, err = db.conn.Exec("UPDATE table_merges SET active = 0 WHERE id = ?", mergeID) if err != nil { return err } _, err = db.conn.Exec("UPDATE tables SET status = 'active' WHERE id = ?", childID) return err } func (db *DB) GetEmailConfig() (*models.EmailConfig, error) { var cfg models.EmailConfig err := db.conn.QueryRow("SELECT id, host, port, user, password, ssl, folder FROM email_config LIMIT 1").Scan( &cfg.ID, &cfg.Host, &cfg.Port, &cfg.Username, &cfg.Password, &cfg.SSL, &cfg.Folder, ) if err == sql.ErrNoRows { return nil, nil } return &cfg, err } func (db *DB) SaveEmailConfig(cfg *models.EmailConfig) error { if cfg.ID > 0 { _, err := db.conn.Exec( "UPDATE email_config SET host = ?, port = ?, user = ?, password = ?, ssl = ?, folder = ? WHERE id = ?", cfg.Host, cfg.Port, &cfg.Username, cfg.Password, cfg.SSL, cfg.Folder, cfg.ID, ) return err } result, err := db.conn.Exec( "INSERT INTO email_config (host, port, user, password, ssl, folder) VALUES (?, ?, ?, ?, ?, ?)", cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.SSL, cfg.Folder, ) if err != nil { return err } id, _ := result.LastInsertId() cfg.ID = int(id) return nil } func (db *DB) GetUserByUsername(username string) (*models.User, error) { var u models.User err := db.conn.QueryRow("SELECT id, username, password, is_admin FROM users WHERE username = ?", username).Scan( &u.ID, &u.Username, &u.Password, &u.IsAdmin, ) if err == sql.ErrNoRows { return nil, nil } return &u, err } func (db *DB) CreateUser(user *models.User) error { result, err := db.conn.Exec( "INSERT INTO users (username, password, is_admin) VALUES (?, ?, ?)", user.Username, user.Password, user.IsAdmin, ) if err != nil { return err } id, _ := result.LastInsertId() user.ID = int(id) return nil } func (db *DB) GetMergedTablesFor(parentTableID int) ([]int, error) { rows, err := db.conn.Query("SELECT child_table_id FROM table_merges WHERE parent_table_id = ? AND active = 1", parentTableID) if err != nil { return nil, err } defer rows.Close() var ids []int for rows.Next() { var id int if err := rows.Scan(&id); err != nil { return nil, err } ids = append(ids, id) } return ids, nil } func (db *DB) GetTotalGuestsForDate(date string) (int, error) { var total int err := db.conn.QueryRow("SELECT COALESCE(SUM(guests), 0) FROM reservations WHERE date = ?", date).Scan(&total) return total, err } func (db *DB) CreateTableMerge(merge *models.TableMerge) error { result, err := db.conn.Exec( "INSERT INTO table_merges (parent_table_id, child_table_id, merged_name, active) VALUES (?, ?, ?, ?)", merge.ParentTableID, merge.ChildTableID, merge.MergedName, merge.Active, ) if err != nil { return err } id, _ := result.LastInsertId() merge.ID = int(id) merge.CreatedAt = time.Now() // Mark child table as merged _, err = db.conn.Exec("UPDATE tables SET status = 'merged' WHERE id = ?", merge.ChildTableID) return err }