= !UseCase: Креирање на курс = == Актер == Експерт == Цел == Експертот сака да креира нов курс во системот. == Предуслов: табела `expert_course` == Шемата нема врска помеѓу `expert` и `course`, па нема каде да се запише кој експерт го креирал курсот. Оваа табела мора да се додаде во DDL пред овој случај на употреба да работи. {{{ CREATE TABLE expert_course ( expert_id bigint NOT NULL, course_id bigint NOT NULL, CONSTRAINT pk_expert_course PRIMARY KEY (expert_id, course_id), CONSTRAINT fk_ec_expert FOREIGN KEY (expert_id) REFERENCES expert (expert_id) ON DELETE CASCADE, CONSTRAINT fk_ec_course FOREIGN KEY (course_id) REFERENCES course (course_id) ON DELETE CASCADE ); }}} == Главен тек == * На експертот му се прикажува долга форма со полиња кои треба да ги пополни. [[Image(create_course_page_1.png, 800px)]] * Експертот ги пополнува сите полиња и кликнува на копчето Create Course [[Image(create_course_page_2.png, 800px)]] * Експертот е навигиран кон почетната страна за експерти. Во база се креира новиот курс. {{{ BEGIN; DROP TABLE IF EXISTS temp_course_id; DROP TABLE IF EXISTS temp_course_version_id; DROP TABLE IF EXISTS temp_course_content_id; DROP TABLE IF EXISTS temp_course_lecture_id; DROP TABLE IF EXISTS temp_tag_id; CREATE TEMP TABLE temp_course_id (course_id BIGINT); CREATE TEMP TABLE temp_course_version_id (course_version_id BIGINT); CREATE TEMP TABLE temp_course_content_id (course_content_id BIGINT); CREATE TEMP TABLE temp_course_lecture_id (course_lecture_id BIGINT); CREATE TEMP TABLE temp_tag_id (tag_id BIGINT, tag_type tag_type); -- 1. Курс WITH new_course AS ( INSERT INTO course (image_url, difficulty, duration_minutes, price, color) VALUES ('https://example.com/image.png', 'intermediate', 120, 50.00, '#008CC2') RETURNING course_id ) INSERT INTO temp_course_id (course_id) SELECT course_id FROM new_course; -- 2. Верзија на курс WITH new_course_version AS ( INSERT INTO course_version (version_number, created_at, is_active, course_id) SELECT 1, CURRENT_DATE, true, course_id FROM temp_course_id RETURNING course_version_id ) INSERT INTO temp_course_version_id (course_version_id) SELECT course_version_id FROM new_course_version; -- 3. Преводи на курсот -- what_will_be_learned е text[] колона, па се внесува заедно со преводот. INSERT INTO course_translate (language_id, title_short, title, description_short, description, description_long, what_will_be_learned, course_id) SELECT (SELECT id FROM language WHERE value = 'en'), 'Advanced Sales', 'Advanced Sales For Senior Salesman', 'Short desc', 'Normal desc', 'Long desc', ARRAY['Advanced sales techniques', 'How to successfully close deals'], course_id FROM temp_course_id; INSERT INTO course_translate (language_id, title_short, title, description_short, description, description_long, what_will_be_learned, course_id) SELECT (SELECT id FROM language WHERE value = 'mk'), 'Напредна Продажба', 'Напредна Продажба за Сениор Продавачи', 'Кратка дескрипција', 'Нормална дескрипција', 'Долга дескрипција', ARRAY['Напредни техники на продажба', 'Како до успешно затворање на продажби'], course_id FROM temp_course_id; -- 4. Модул WITH new_course_content AS ( INSERT INTO course_content (position, course_version_id) SELECT 1, course_version_id FROM temp_course_version_id RETURNING course_content_id ) INSERT INTO temp_course_content_id (course_content_id) SELECT course_content_id FROM new_course_content; INSERT INTO course_content_translate (title, language_id, course_content_id) SELECT 'Module 1: Sales Strategies', (SELECT id FROM language WHERE value = 'en'), course_content_id FROM temp_course_content_id; INSERT INTO course_content_translate (title, language_id, course_content_id) SELECT 'Модул 1: Продажни Стратегии', (SELECT id FROM language WHERE value = 'mk'), course_content_id FROM temp_course_content_id; -- 5. Лекција WITH new_course_lecture AS ( INSERT INTO course_lecture (duration_minutes, position, content_type, course_content_id) SELECT 30, 1, 'video', course_content_id FROM temp_course_content_id RETURNING course_lecture_id ) INSERT INTO temp_course_lecture_id (course_lecture_id) SELECT course_lecture_id FROM new_course_lecture; INSERT INTO course_lecture_translate (title, language_id, content_file_name, description, content_text, course_lecture_id) SELECT 'Lecture 1: Intro', (SELECT id FROM language WHERE value = 'en'), 'lecture1.mp4', 'Introduction to advanced sales', NULL, -- content_type = 'video' -> нема текстуална содржина course_lecture_id FROM temp_course_lecture_id; INSERT INTO course_lecture_translate (title, language_id, content_file_name, description, content_text, course_lecture_id) SELECT 'Предавање 1: Вовед', (SELECT id FROM language WHERE value = 'mk'), 'lecture1.mp4', 'Вовед во напредна продажба', NULL, course_lecture_id FROM temp_course_lecture_id; -- 6. Тагови -- ВНИМАНИЕ: ова креира НОВИ тагови при секое креирање на курс. -- Во продукција формата треба да нуди постоечки тагови, па овој блок -- се заменува со SELECT врз tag/tag_translate по избраните вредности. WITH new_tags AS ( INSERT INTO tag (tag_type) VALUES ('skill'), ('topic') RETURNING tag_id, tag_type ) INSERT INTO temp_tag_id (tag_id, tag_type) SELECT tag_id, tag_type FROM new_tags; INSERT INTO tag_translate (language_id, value, tag_id) SELECT (SELECT id FROM language WHERE value = 'en'), CASE tag_type WHEN 'skill' THEN 'Skill' WHEN 'topic' THEN 'Topic' END, tag_id FROM temp_tag_id; INSERT INTO tag_translate (language_id, value, tag_id) SELECT (SELECT id FROM language WHERE value = 'mk'), CASE tag_type WHEN 'skill' THEN 'Вештина' WHEN 'topic' THEN 'Тема' END, tag_id FROM temp_tag_id; INSERT INTO course_tag (tag_id, course_id) SELECT t.tag_id, c.course_id FROM temp_tag_id t CROSS JOIN temp_course_id c; -- 7. Врска експерт <-> курс INSERT INTO expert_course (course_id, expert_id) SELECT course_id, :expert_id FROM temp_course_id; COMMIT; }}} == Алтернативен тек == * Доколку експертот не пополнил некои од полињата, се покажува порака за грешка. Валидацијата се прави на ниво на апликација пред да се почне трансакцијата. Базата ги штити следниве полиња преку `NOT NULL`, но грешката тогаш доаѓа како `23502` откако курсот е делумно креиран: || '''Табела''' || '''Задолжителни полиња''' || || course || image_url, color, difficulty, duration_minutes, price || || course_translate || title_short, title, description_short, description, description_long, what_will_be_learned, language_id || || course_content || position || || course_content_translate || title, language_id || || course_lecture || position, duration_minutes, content_type || || course_lecture_translate || title, description, language_id || Дополнителни ограничувања кои можат да фрлат грешка: - `ck_course_duration_positive` и `ck_course_price_non_negative` — времетраењето мора да е поголемо од нула, цената не смее да е негативна. - `uq_course_content_position` и `uq_course_lecture_position` — две модули или две лекции не смеат да имаат иста позиција. - `uq_course_translate` — не смее да има два превода на ист јазик за ист курс. == Забелешки == - Сите вредности за `difficulty`, `content_type` и `tag_type` мора да се со мали букви — тоа се лабели на PostgreSQL enum типови и се осетливи на голема/мала буква. - `tag_type` има само две вредности: `skill` и `topic`. Вредноста `interest` не постои и предизвикува грешка, а не празен резултат. - Целиот блок мора да биде во една трансакција. Ако некој од чекорите падне, курсот не смее да остане делумно креиран. - `uq_course_version_one_active` дозволува само една активна верзија по курс. При креирање на нова верзија, старата мора прво да се постави на `is_active = false`.