Initial commit

This commit is contained in:
user
2026-07-12 14:34:52 +04:00
commit 3abf6bd9c2
557 changed files with 68706 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
-- create indexes
-- genres
CREATE INDEX idx_genres_genre_name ON genres (genre_name);
-- movies
CREATE INDEX idx_movies_title ON movies (title);
CREATE INDEX idx_movies_genre_id ON movies (genre_id);
-- halls
CREATE INDEX idx_halls_hall_name ON halls (hall_name);
-- sessions
CREATE INDEX idx_sessions_movie_id ON sessions (movie_id);
CREATE INDEX idx_sessions_hall_id ON sessions (hall_id);
CREATE INDEX idx_sessions_begin_at ON sessions (begin_at);
CREATE INDEX idx_sessions_hall_id_begin_at ON sessions (hall_id, begin_at);
-- tickets
CREATE INDEX idx_tickets_session_id ON tickets (session_id);
CREATE INDEX idx_tickets_seat_number ON tickets (seat_number);
-- refunds
CREATE INDEX idx_refunds_ticket_id ON refunds (ticket_id);
-- roles
CREATE INDEX idx_roles_role_name ON roles (role_name);
CREATE INDEX idx_roles_access_level ON roles (access_level);
-- users
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_users_role_id ON users (role_id);
+83
View File
@@ -0,0 +1,83 @@
-- Create table "Genres" to store movie genres
CREATE TABLE genres (
genre_id INTEGER PRIMARY KEY AUTOINCREMENT,
genre_name TEXT NOT NULL UNIQUE CHECK(length(genre_name) > 0), -- Genre name cannot be empty
CONSTRAINT no_double_spaces_in_genre_name CHECK (genre_name NOT LIKE '% %') -- Genre name cannot contain double spaces
);
-- Create table "Movies" to store movie details
CREATE TABLE movies (
movie_id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL UNIQUE CHECK(length(title) > 0), -- Movie title cannot be empty
genre_id INTEGER,
duration INTEGER CHECK(duration > 0), -- Movie duration must be greater than 0
FOREIGN KEY (genre_id) REFERENCES genres (genre_id) ON DELETE CASCADE,
CONSTRAINT no_double_spaces_in_title CHECK (title NOT LIKE '% %') -- Movie title cannot contain double spaces
);
-- Create table "Halls" to store cinema hall details
CREATE TABLE halls (
hall_id INTEGER PRIMARY KEY AUTOINCREMENT,
hall_name TEXT NOT NULL UNIQUE CHECK(length(hall_name) > 0), -- Hall name cannot be empty
capacity INTEGER NOT NULL CHECK(capacity > 0), -- Hall capacity must be positive
CONSTRAINT no_double_spaces_in_hall_name CHECK (hall_name NOT LIKE '% %') -- Hall name cannot contain double spaces
);
-- Create table "Sessions" to store session details
CREATE TABLE sessions (
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER,
hall_id INTEGER,
begin_at TIMESTAMP NOT NULL, -- Date and time of the session
ticket_price REAL NOT NULL CHECK(ticket_price >= 0), -- Ticket price cannot be negative
FOREIGN KEY (movie_id) REFERENCES movies (movie_id) ON DELETE CASCADE,
FOREIGN KEY (hall_id) REFERENCES halls (hall_id) ON DELETE CASCADE,
CONSTRAINT unique_hall_time UNIQUE (hall_id, begin_at) -- Prevent duplicate sessions in the same hall at the same time
);
-- Create table "Tickets" to store sold tickets
CREATE TABLE tickets (
ticket_id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER,
seat_number INTEGER NOT NULL CHECK(seat_number > 0), -- Seat number must be positive
sold_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Sale time defaults to current time
FOREIGN KEY (session_id) REFERENCES sessions (session_id) ON DELETE CASCADE,
CONSTRAINT unique_session_seat UNIQUE(session_id, seat_number) -- Unique combination of session and seat number
);
-- Create table "Refunds" to store refunded tickets
CREATE TABLE refunds (
refund_id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL UNIQUE,
refund_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
refund_amount REAL NOT NULL CHECK (refund_amount >= 0), -- Refund amount cannot be negative
FOREIGN KEY (ticket_id) REFERENCES tickets(ticket_id) ON DELETE CASCADE
);
-- Create table "Roles" to store user roles
CREATE TABLE roles (
role_id INTEGER PRIMARY KEY AUTOINCREMENT,
role_name TEXT UNIQUE NOT NULL CHECK(length(role_name) > 0), -- Role name cannot be empty
access_level INTEGER UNIQUE NOT NULL CHECK(access_level > 0), -- Access level must be positive
CONSTRAINT no_spaces_in_role_name CHECK (role_name NOT LIKE '% %') -- Role name cannot contain spaces
);
-- Create table "Users" to store user details
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL CHECK(length(username) > 0), -- Username cannot be empty
password_hash TEXT NOT NULL CHECK(length(password_hash) > 0), -- Password hash cannot be empty
salt TEXT UNIQUE NOT NULL CHECK(length(salt) > 0), -- Salt cannot be empty
role_id INTEGER NOT NULL,
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
CONSTRAINT no_spaces_in_username CHECK (username NOT LIKE '% %') -- Username cannot contain spaces
);
+212
View File
@@ -0,0 +1,212 @@
-- WARNING: In SQLite DELIMITER doesn't work, so this file is handled in a special way
DELIMITER $$
-- INFO: ----- TICKET -----
-- Create trigger to validate seat number before inserting a ticket
-- INSERT
CREATE TRIGGER check_seat_number_before_insert
BEFORE INSERT ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions s
INNER JOIN halls h ON s.hall_id = h.hall_id
WHERE s.session_id = NEW.session_id
AND NEW.seat_number > h.capacity
)
BEGIN
SELECT RAISE(FAIL, 'Error 1001: Seat number exceeds hall capacity');
END$$
-- UPDATE
CREATE TRIGGER check_seat_number_before_update
BEFORE UPDATE ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions s
INNER JOIN halls h ON s.hall_id = h.hall_id
WHERE s.session_id = NEW.session_id
AND NEW.seat_number > h.capacity
)
BEGIN
SELECT RAISE(FAIL, 'Error 1001: Seat number exceeds hall capacity');
END$$
-- Check if the ticket purchase time is later than the session start time
-- INSERT
CREATE TRIGGER prevent_late_ticket_purchase_before_insert
BEFORE INSERT ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions
WHERE session_id = NEW.session_id
AND DATETIME(NEW.sold_at) >= DATETIME(begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1002: Cannot purchase ticket after the session has started');
END$$
-- UPDATE
CREATE TRIGGER prevent_late_ticket_purchase_before_update
BEFORE UPDATE ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions
WHERE session_id = NEW.session_id
AND DATETIME(NEW.sold_at) >= DATETIME(begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1002: Cannot purchase ticket after the session has started');
END$$
-- INFO: ----- REFUND -----
-- Create trigger to prevent selling a ticket after the session has started
-- INSERT
CREATE TRIGGER prevent_late_refunds_before_insert
BEFORE INSERT ON refunds
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
AND t.ticket_id = NEW.ticket_id
WHERE DATETIME(NEW.refund_at) >= DATETIME(s.begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1003: Refunds cannot be made after the session has started');
END$$
-- UPDATE
CREATE TRIGGER prevent_late_refunds_before_update
BEFORE UPDATE ON refunds
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
AND t.ticket_id = NEW.ticket_id
WHERE DATETIME(NEW.refund_at) >= DATETIME(s.begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1003: Refunds cannot be made after the session has started');
END$$
-- Create a trigger to check if the refund amount is greater than the ticket sale amount
-- INSERT
CREATE TRIGGER validate_refund_amount_before_insert
BEFORE INSERT ON refunds
WHEN NEW.refund_amount > (
SELECT ticket_price
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
WHERE t.ticket_id = NEW.ticket_id
)
BEGIN
SELECT RAISE(FAIL, 'Error 1004: Refund amount cannot exceed the sale amount');
END$$
-- UPDATE
CREATE TRIGGER validate_refund_amount_before_update
BEFORE UPDATE ON refunds
WHEN NEW.refund_amount > (
SELECT ticket_price
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
WHERE t.ticket_id = NEW.ticket_id
)
BEGIN
SELECT RAISE(FAIL, 'Error 1004: Refund amount cannot exceed the sale amount');
END$$
-- Create a trigger to check if the refund date is earlier than the sale date
-- INSERT
CREATE TRIGGER validate_refund_date_before_insert
BEFORE INSERT ON refunds
WHEN EXISTS (
SELECT 1
FROM tickets
WHERE ticket_id = NEW.ticket_id
AND DATETIME(NEW.refund_at) <= DATETIME(sold_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1005: Refund date cannot be earlier than the sale date or equal to it');
END$$
-- UPDATE
CREATE TRIGGER validate_refund_date_before_update
BEFORE UPDATE ON refunds
WHEN EXISTS (
SELECT 1
FROM tickets
WHERE ticket_id = NEW.ticket_id
AND DATETIME(NEW.refund_at) <= DATETIME(sold_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1005: Refund date cannot be earlier than the sale date or equal to it');
END$$
-- INFO: ----- SESSION -----
-- Trigger to prevent overlapping sessions
--INSERT
CREATE TRIGGER prevent_overlapping_sessions_before_insert
BEFORE INSERT ON sessions
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN movies m_existing ON s.movie_id = m_existing.movie_id
JOIN movies m_new ON NEW.movie_id = m_new.movie_id
WHERE s.hall_id = NEW.hall_id
AND (
-- The new movie starts within an already occupied time slot
NEW.begin_at < DATETIME(s.begin_at, '+' || m_existing.duration || ' minutes')
-- Or the existing movie starts within the new movie's time slot
AND s.begin_at < DATETIME(NEW.begin_at, '+' || m_new.duration || ' minutes')
)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1006: The new session overlaps with an ongoing movie in the hall');
END$$
-- UPDATE
CREATE TRIGGER prevent_overlapping_sessions_before_update
BEFORE UPDATE ON sessions
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN movies m_existing ON s.movie_id = m_existing.movie_id
JOIN movies m_new ON NEW.movie_id = m_new.movie_id
WHERE s.hall_id = NEW.hall_id
AND (
-- The new movie starts within an already occupied time slot
NEW.begin_at < DATETIME(s.begin_at, '+' || m_existing.duration || ' minutes')
-- Or the existing movie starts within the new movie's time slot
AND s.begin_at < DATETIME(NEW.begin_at, '+' || m_new.duration || ' minutes')
)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1006: The new session overlaps with an ongoing movie in the hall');
END$$
DELIMITER ;
+32
View File
@@ -0,0 +1,32 @@
CREATE VIEW sessions_statistics AS
SELECT
s.session_id,
s.begin_at,
s.ticket_price,
h.hall_id,
h.hall_name,
h.capacity AS total_seats,
m.movie_id,
m.title AS movie_title,
m.duration,
g.genre_id,
g.genre_name,
COUNT(t.ticket_id) AS sold_tickets,
COUNT(r.refund_id) AS refunded_tickets,
SUM(CASE WHEN t.ticket_id IS NOT NULL THEN s.ticket_price ELSE 0 END) AS sales_revenue,
SUM(CASE WHEN r.refund_id IS NOT NULL THEN r.refund_amount ELSE 0 END) AS refund_expenses
FROM halls h
JOIN sessions s
ON s.hall_id = h.hall_id
JOIN movies m
ON s.movie_id = m.movie_id
JOIN genres g
ON m.genre_id = g.genre_id
LEFT JOIN tickets t
ON t.session_id = s.session_id
LEFT JOIN refunds r
ON r.ticket_id = t.ticket_id
GROUP BY
s.session_id;
+3
View File
@@ -0,0 +1,3 @@
-- Disable foreign keys
-- Necessary testing behavior
PRAGMA foreign_keys = OFF;
+3
View File
@@ -0,0 +1,3 @@
-- Enable foreign keys
-- The necessary behavior for the normal operation of the program
PRAGMA foreign_keys = ON;
+23
View File
@@ -0,0 +1,23 @@
-- Insert data into the genres table
INSERT INTO genres (genre_id, genre_name) VALUES
(1, 'Боевик'),
(2, 'Комедия'),
(3, 'Драма'),
(4, 'Фантастика'),
(5, 'Ужасы'),
(6, 'Триллер'),
(7, 'Мелодрама'),
(8, 'Приключения'),
(9, 'Анимация'),
(10, 'Детектив'),
(11, 'Фэнтези'),
(12, 'Исторический'),
(13, 'Военный'),
(14, 'Музыкальный'),
(15, 'Семейный'),
(16, 'Спорт'),
(17, 'Документальный'),
(18, 'Криминал'),
(19, 'Биография'),
(20, 'Вестерн');
+7
View File
@@ -0,0 +1,7 @@
-- Insert data into the halls table
INSERT INTO halls (hall_name, capacity) VALUES ('Hall 1', 100);
INSERT INTO halls (hall_name, capacity) VALUES ('Hall 2', 150);
INSERT INTO halls (hall_name, capacity) VALUES ('VIP Hall', 50);
INSERT INTO halls (hall_name, capacity) VALUES ('IMAX Hall', 200);
+100
View File
@@ -0,0 +1,100 @@
-- Insert data into the movies table
INSERT INTO movies (title, genre_id, duration) VALUES
('Титаник', 3, 195),
('Матрица', 4, 136),
('Крепкий орешек', 1, 132),
('Один дома', 2, 103),
('Пила', 5, 103),
('Начало', 6, 148),
('Титаник 2', 3, 120),
('Звездные войны: Эпизод IV', 4, 121),
('Терминатор', 1, 107),
('Мальчишник в Вегасе', 2, 100),
('Звонок', 5, 115),
('Семь', 6, 127),
('Форрест Гамп', 3, 142),
('Аватар', 4, 162),
('Рокки', 1, 119),
('Маска', 2, 101),
('Оно', 5, 135),
('Молчание ягнят', 6, 118),
('Красота по-американски', 3, 122),
('Интерстеллар', 4, 169),
('Терминатор 2', 1, 137),
('День сурка', 2, 101),
('Пила 2', 5, 93),
('Игра', 6, 129),
('Список Шиндлера', 3, 195),
('Чужой', 4, 117),
('Рэмбо', 1, 93),
('Брюс Всемогущий', 2, 101),
('Заклятие', 5, 112),
('Шестое чувство', 6, 107),
('Гладиатор', 3, 155),
('Звездные войны: Эпизод V', 4, 124),
('Терминатор 3', 1, 109),
('Мальчишник 2', 2, 105),
('Пила 3', 5, 108),
('Остров проклятых', 6, 138),
('Властелин колец: Братство кольца', 11, 178),
('Властелин колец: Две крепости', 11, 179),
('Властелин колец: Возвращение короля', 11, 201),
('Гарри Поттер и философский камень', 11, 152),
('Гарри Поттер и Тайная комната', 11, 161),
('Гарри Поттер и узник Азкабана', 11, 142),
('Гарри Поттер и Кубок огня', 11, 157),
('Гарри Поттер и Орден Феникса', 11, 138),
('Гарри Поттер и Принц-полукровка', 11, 153),
('Гарри Поттер и Дары Смерти: Часть 1', 11, 146),
('Гарри Поттер и Дары Смерти: Часть 2', 11, 130),
('Пираты Карибского моря: Проклятие Черной жемчужины', 8, 143),
('Пираты Карибского моря: Сундук мертвеца', 8, 151),
('Пираты Карибского моря: На краю света', 8, 169),
('Пираты Карибского моря: На странных берегах', 8, 136),
('Пираты Карибского моря: Мертвецы не рассказывают сказки', 8, 129),
('Хоббит: Нежданное путешествие', 11, 169),
('Хоббит: Пустошь Смауга', 11, 161),
('Хоббит: Битва пяти воинств', 11, 144),
('Король Лев', 9, 88),
('Холодное сердце', 9, 102),
('Холодное сердце 2', 9, 103),
('Зверополис', 9, 108),
('История игрушек', 9, 81),
('История игрушек 2', 9, 92),
('История игрушек 3', 9, 103),
('История игрушек 4', 9, 100),
('В поисках Немо', 9, 100),
('В поисках Дори', 9, 97),
('Шрек', 9, 90),
('Шрек 2', 9, 93),
('Шрек Третий', 9, 93),
('Шрек навсегда', 9, 93),
('Кунг-фу Панда', 9, 92),
('Кунг-фу Панда 2', 9, 90),
('Кунг-фу Панда 3', 9, 95),
('Как приручить дракона', 9, 98),
('Как приручить дракона 2', 9, 102),
('Как приручить дракона 3', 9, 104),
('Рататуй', 9, 111),
('Головоломка', 9, 94),
('Суперсемейка', 9, 115),
('Суперсемейка 2', 9, 118),
('Тачки', 9, 117),
('Тачки 2', 9, 106),
('Тачки 3', 9, 102),
('Корпорация монстров', 9, 92),
('Университет монстров', 9, 104),
('Валл-И', 9, 98),
('Вверх', 9, 96),
('Храбрая сердцем', 9, 93),
('Ральф', 9, 101),
('Ральф против интернета', 9, 112),
('Моана', 9, 107),
('Зверопой', 9, 108),
('Зверопой 2', 9, 110),
('Коко', 9, 105),
('Душа', 9, 100),
('Лука', 9, 95),
('Райя и последний дракон', 9, 107),
('Энканто', 9, 102);
+9
View File
@@ -0,0 +1,9 @@
-- Insert data into the roles table
INSERT INTO roles (role_name, access_level) VALUES ('Cashier', 10);
INSERT INTO roles (role_name, access_level) VALUES ('SalesManager', 28);
INSERT INTO roles (role_name, access_level) VALUES ('DataEditor', 46);
INSERT INTO roles (role_name, access_level) VALUES ('RepertoireManager', 64);
INSERT INTO roles (role_name, access_level) VALUES ('DataAdministrator', 82);
INSERT INTO roles (role_name, access_level) VALUES ('SuperAdministrator', 100);
+40
View File
@@ -0,0 +1,40 @@
-- Insert users with different roles
-- Cashiers (role_id = 1), password: cashier (cashier1 & cashier2), cashier_alt (cashier3)
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('cashier1', '43f4fded07f17b903867b8293ad429b0c826e5d2c8ac5a66ed58cf3cc511ee60', 'random_salt_1', 1);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('cashier2', '2a2aebce4cc19cfd384377617e3008d42caeb0172970b64e8d39778995950cfc', 'random_salt_2', 1);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('cashier3', 'a474eac28b808053cf8fddd7d53412d710c1adeb218c0bd7b91add5760b16885', 'random_salt_3', 1);
-- Sales Managers (role_id = 2), password: sales_manager1, sales_manager2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('sales_manager1', '3ac5657c3cc5b44e291aed9f3dcf4881af3c6c53b49b31ef5ab0e35ef04d32dc', 'random_salt_4', 2);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('sales_manager2', '40ad3df805e5173265f65206b1a9fa1c04b7f2ebbd30d5c4bc8ae496c01e89c3', 'random_salt_5', 2);
-- Data Editors (role_id = 3), password: data_editor1, data_editor2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_editor1', '53a6ad3f69d1ba0f16fab96a295340a70aa6dd4524a06a9e281b458ec1bffdeb', 'random_salt_6', 3);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_editor2', '074423b2b32d87ea875905962f10cabe0623b75532323f7a6d1f29ff51a556e0', 'random_salt_7', 3);
-- Repertoire Managers (role_id = 4), password: repertoire_manager1, repertoire_manager2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('repertoire_manager1', 'd57d697fe8df03559cbd7cb6c15d29fc6584556ba92291400db64a788a788bd5', 'random_salt_8', 4);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('repertoire_manager2', '74a0a0bceb65454779c8acb2705df36a905355e10b96fa0360a023aaddbac2cf', 'random_salt_9', 4);
-- Data Administrators (role_id = 5), password: data_admin1, data_admin2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_admin1', '1b755d9430baa5f6db977e3ed87fc9d2d0b6e89e35c0f04529ec7484ca57ab54', 'random_salt_10', 5);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_admin2', '465a2368e915fc5bbc8f1ff56fec3bb5fda92088cb64285b4419507012677bf3', 'random_salt_11', 5);
-- Super Administrators (role_id = 6), password: super_admin1, super_admin2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('super_admin1', 'b687b754afbbb073178a2b50f10dad03d0ff54019b771b1853c5ee3bfc6d93d2', 'random_salt_12', 6);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('super_admin2', 'cad48cc6068cfc8f164e16422f1c92e4cd52fa446d1ac2fa2a678406fa60c6b6', 'random_salt_13', 6);