/
unluckattempt
/
Ruby_practice
Обзор
Документация
Войти
/
unluckattempt
/
Ruby_practice
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
server.rb
583 строки
18 KB
Rodion
PZ & video
08 июн 2026, 01:48
08 июн 2026, 01:48
8520832
Код
Авторство
О чём код?
require 'webrick' require 'erb' require 'json' require 'cgi' require 'stringio' # --- Константы --- STUDENTS_FILE = 'students.json' GROUPS_FILE = 'groups.json' SKILLS_FILE = 'skills.json' # --- Слой данных для НАВЫКОВ --- module SkillStorage def self.read_all return [] unless File.exist?(SKILLS_FILE) content = File.read(SKILLS_FILE) return [] if content.strip.empty? begin JSON.parse(content) rescue JSON::ParserError [] end end def self.save_all(skills) File.write(SKILLS_FILE, JSON.pretty_generate(skills)) end # Найти навык или создать новый, если его нет def self.find_or_create(name) name = name.strip return nil if name.empty? skills = read_all existing = skills.find { |s| s['name'].downcase == name.downcase } return existing if existing new_id = skills.empty? ? 1 : skills.map { |s| s['id'].to_i }.max + 1 new_skill = { 'id' => new_id, 'name' => name } save_all(skills << new_skill) new_skill end end # --- Слой данных для ГРУПП --- module GroupStorage def self.read_all return [] unless File.exist?(GROUPS_FILE) content = File.read(GROUPS_FILE) return [] if content.strip.empty? begin JSON.parse(content) rescue JSON::ParserError [] end end def self.save_all(groups) File.write(GROUPS_FILE, JSON.pretty_generate(groups)) end def self.find_by_id(id) read_all.find { |g| g['id'].to_s == id.to_s } end def self.create(group_params) groups = read_all new_id = groups.empty? ? 1 : groups.map { |g| g['id'].to_i }.max + 1 new_group = { 'id' => new_id, 'name' => group_params['name'], 'description' => group_params['description'] || '', 'created_at' => Time.now.strftime("%Y-%m-%d %H:%M") } groups << new_group save_all(groups) new_group end def self.update(id, group_params) groups = read_all index = groups.index { |g| g['id'].to_s == id.to_s } return nil unless index groups[index]['name'] = group_params['name'] groups[index]['description'] = group_params['description'] || '' save_all(groups) groups[index] end def self.delete(id) students = StudentStorage.read_all if students.any? { |s| s['group_id'].to_s == id.to_s } return :has_students end groups = read_all filtered = groups.reject { |g| g['id'].to_s == id.to_s } save_all(filtered) :success end end # --- Слой данных для СТУДЕНТОВ --- module StudentStorage def self.read_all return [] unless File.exist?(STUDENTS_FILE) content = File.read(STUDENTS_FILE) return [] if content.strip.empty? begin JSON.parse(content) rescue JSON::ParserError [] end end def self.save_all(students) File.write(STUDENTS_FILE, JSON.pretty_generate(students)) end def self.find_by_id(id) read_all.find { |s| s['id'].to_s == id.to_s } end def self.create(student_params) students = read_all new_id = students.empty? ? 1 : students.map { |s| s['id'].to_i }.max + 1 # Обработка навыков из текстового поля (через запятую) raw_skills_input = student_params['skills_input'] || "" skill_ids = [] unless raw_skills_input.strip.empty? raw_skills_input.split(',').each do |skill_name| name = skill_name.strip next if name.empty? skill = SkillStorage.find_or_create(name) skill_ids << skill['id'] if skill end end new_student = { 'id' => new_id, 'full_name' => student_params['full_name'], 'group_id' => student_params['group_id'].to_i, 'email' => student_params['email'], 'skill_ids' => skill_ids, 'created_at' => Time.now.strftime("%Y-%m-%d %H:%M") } students << new_student save_all(students) new_student end def self.update(id, student_params) students = read_all index = students.index { |s| s['id'].to_s == id.to_s } return nil unless index # Обработка навыков при обновлении raw_skills_input = student_params['skills_input'] || "" skill_ids = [] unless raw_skills_input.strip.empty? raw_skills_input.split(',').each do |skill_name| name = skill_name.strip next if name.empty? skill = SkillStorage.find_or_create(name) skill_ids << skill['id'] if skill end end students[index]['full_name'] = student_params['full_name'] students[index]['group_id'] = student_params['group_id'].to_i students[index]['email'] = student_params['email'] students[index]['skill_ids'] = skill_ids save_all(students) students[index] end def self.delete(id) students = read_all filtered = students.reject { |s| s['id'].to_s == id.to_s } save_all(filtered) end def self.find_by_group_id(group_id) read_all.select { |s| s['group_id'].to_s == group_id.to_s } end end # --- Rack Application --- class JournalApp def call(env) request_method = env['REQUEST_METHOD'] path_info = env['PATH_INFO'] # Парсинг параметров params = {} if request_method == 'POST' || request_method == 'PUT' content_length = env['CONTENT_LENGTH'].to_i if content_length > 0 input = env['rack.input'] body = input.is_a?(String) ? input : input.read(content_length) params = CGI.parse(body).transform_values { |v| v.first } end elsif request_method == 'GET' query_string = env['QUERY_STRING'] if query_string && !query_string.empty? params = CGI.parse(query_string).transform_values { |v| v.first } end end # Маршрутизация для ГРУПП if path_info.start_with?('/groups') return handle_groups_routing(request_method, path_info, params) end # Маршрутизация для СТУДЕНТОВ if request_method == 'GET' && path_info == '/' return handle_index(params) end if request_method == 'GET' && path_info == '/new' return handle_new_form(params) end if request_method == 'POST' && path_info == '/create' return handle_create(params) end if request_method == 'GET' && path_info.match?(%r{^/edit/\d+$}) id = path_info.split('/').last return handle_edit_form(id) end if request_method == 'POST' && path_info.match?(%r{^/update/\d+$}) id = path_info.split('/').last return handle_update(params, id) end if request_method == 'GET' && path_info.match?(%r{^/delete/\d+$}) id = path_info.split('/').last return handle_delete(id) end if request_method == 'POST' && path_info.match?(%r{^/delete/\d+$}) id = path_info.split('/').last return handle_delete(id) end if request_method == 'GET' && path_info.match?(%r{^/show/\d+$}) id = path_info.split('/').last return handle_show(id) end if request_method == 'GET' && path_info == '/account' return handle_account end not_found end private # --- Маршрутизация для групп --- def handle_groups_routing(method, path, params) if method == 'GET' && path == '/groups' return handle_groups_index(params) end if method == 'GET' && path == '/groups/new' return handle_group_new_form end if method == 'POST' && path == '/groups/create' return handle_group_create(params) end if method == 'GET' && path.match?(%r{^/groups/\d+$}) match = path.match(%r{^/groups/(\d+)$}) return handle_group_show(match[1], params) end if method == 'GET' && path.match?(%r{^/groups/\d+/edit$}) match = path.match(%r{^/groups/(\d+)/edit$}) return handle_group_edit_form(match[1]) end if method == 'POST' && path.match?(%r{^/groups/\d+/update$}) match = path.match(%r{^/groups/(\d+)/update$}) return handle_group_update(params, match[1]) end if method == 'POST' && path.match?(%r{^/groups/\d+/delete$}) match = path.match(%r{^/groups/(\d+)/delete$}) return handle_group_delete(match[1]) end # Добавление существующего студента в группу if method == 'GET' && path.match?(%r{^/groups/\d+/add_student$}) match = path.match(%r{^/groups/(\d+)/add_student$}) return handle_add_student_form(match[1]) end if method == 'POST' && path.match?(%r{^/groups/\d+/add_student$}) match = path.match(%r{^/groups/(\d+)/add_student$}) return handle_add_student_to_group(params, match[1]) end not_found end # --- Обработчики для ГРУПП --- def handle_groups_index(params) groups = GroupStorage.read_all filter_name = params['filter_name'] if filter_name && !filter_name.strip.empty? groups = groups.select { |g| g['name'].downcase.include?(filter_name.downcase) } end template = load_template('views/groups/index.erb') html = render(template, { groups: groups, current_filter: filter_name }) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_group_new_form template = load_template('views/groups/new.erb') html = render(template, {}) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_group_create(params) if params['name'].nil? || params['name'].strip.empty? return [400, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Ошибка: Название группы обязательно"]] end GroupStorage.create(params) [302, { 'Location' => '/groups' }, []] end def handle_group_show(id, params) group = GroupStorage.find_by_id(id) unless group return [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Группа не найдена"]] end students = StudentStorage.find_by_group_id(id) template = load_template('views/groups/show.erb') html = render(template, { group: group, students: students }) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_group_edit_form(id) group = GroupStorage.find_by_id(id) unless group return [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Группа не найдена"]] end template = load_template('views/groups/edit.erb') html = render(template, { group: group }) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_group_update(params, id) if params['name'].nil? || params['name'].strip.empty? return [400, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Ошибка: Название группы обязательно"]] end updated = GroupStorage.update(id, params) if updated [302, { 'Location' => '/groups' }, []] else [404, { 'Content-Type' => 'text/plain' }, ["Ошибка обновления"]] end end def handle_group_delete(id) result = GroupStorage.delete(id) if result == :has_students [302, { 'Location' => "/groups/#{id}?error=cannot_delete_has_students" }, []] else [302, { 'Location' => '/groups' }, []] end end def handle_add_student_form(group_id) group = GroupStorage.find_by_id(group_id) unless group return [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Группа не найдена"]] end students_in_group = StudentStorage.find_by_group_id(group_id).map { |s| s['id'] } all_students = StudentStorage.read_all available_students = all_students.reject { |s| students_in_group.include?(s['id']) } template = load_template('views/groups/add_student.erb') html = render(template, { group: group, available_students: available_students }) [200, { 'Content-Type' => 'text/html; charset=utf-8' }, [html]] end def handle_add_student_to_group(params, group_id) student_id = params['student_id'] if student_id.nil? || student_id.empty? return [400, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Выберите студента"]] end student = StudentStorage.find_by_id(student_id) if student updated_params = { 'full_name' => student['full_name'], 'email' => student['email'], 'group_id' => group_id.to_i } StudentStorage.update(student_id, updated_params) [302, { 'Location' => "/groups/#{group_id}" }, []] else [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Студент не найден"]] end end # --- Обработчики для СТУДЕНТОВ --- def handle_index(params) students = StudentStorage.read_all groups = GroupStorage.read_all all_skills = SkillStorage.read_all filter_group_id = params['filter_group_id'] if filter_group_id && !filter_group_id.empty? students = students.select { |s| s['group_id'].to_s == filter_group_id.to_s } end template = load_template('views/students/index.erb') html = render(template, { students: students, groups: groups, current_filter: filter_group_id, all_skills: all_skills }) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_new_form(params = {}) groups = GroupStorage.read_all selected_group_id = params['group_id'] || '' template = load_template('views/students/new.erb') html = render(template, { groups: groups, selected_group_id: selected_group_id }) [200, { 'Content-Type' => 'text/html; charset=utf-8' }, [html]] end def handle_create(params) if params['full_name'].nil? || params['full_name'].strip.empty? || params['group_id'].nil? || params['group_id'].empty? return [400, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Ошибка: Заполните все поля и выберите группу"]] end StudentStorage.create(params) [302, { 'Location' => '/' }, []] end def handle_edit_form(id) student = StudentStorage.find_by_id(id) unless student return [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Студент не найден"]] end groups = GroupStorage.read_all template = load_template('views/students/edit.erb') html = render(template, { student: student, groups: groups }) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_update(params, id) updated = StudentStorage.update(id, params) if updated [302, { 'Location' => '/' }, []] else [404, { 'Content-Type' => 'text/plain' }, ["Ошибка обновления"]] end end def handle_delete(id) StudentStorage.delete(id) [302, { 'Location' => '/' }, []] end def handle_show(id) student = StudentStorage.find_by_id(id) unless student return [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["Студент не найден"]] end group = GroupStorage.find_by_id(student['group_id']) student_skills = [] if student['skill_ids'] && student['skill_ids'].is_a?(Array) && !student['skill_ids'].empty? all_skills = SkillStorage.read_all student_skills = all_skills.select { |s| student['skill_ids'].include?(s['id']) } end template = load_template('views/students/show.erb') html = render(template, { student: student, group: group, student_skills: student_skills }) [200, { 'Content-Type' => 'text/html' }, [html]] end def handle_account template = load_template('views/account/index.erb') html = render(template, {}) [200, { 'Content-Type' => 'text/html' }, [html]] end def not_found [404, { 'Content-Type' => 'text/plain; charset=utf-8' }, ["404 Not Found"]] end # --- Helpers --- def load_template(path) File.read(path) end def render(template_string, locals) erb = ERB.new(template_string) context = Object.new locals.each { |k, v| context.instance_variable_set("@#{k}", v) } locals.each do |k, v| context.define_singleton_method(k) { instance_variable_get("@#{k}") } end erb.result(context.instance_eval { binding }) end end # --- Server Setup (WEBrick) --- server = WEBrick::HTTPServer.new( Port: 8080, DocumentRoot: './public', AccessLog: [], Logger: WEBrick::Log.new(File::NULL) ) server.mount('/style_account.css', WEBrick::HTTPServlet::FileHandler, './public/style_account.css') server.mount('/style_groups_edit.css', WEBrick::HTTPServlet::FileHandler, './public/style_groups_edit.css') server.mount('/style_groups_index.css', WEBrick::HTTPServlet::FileHandler, './public/style_groups_index.css') server.mount('/style_groups_new.css', WEBrick::HTTPServlet::FileHandler, './public/style_groups_new.css') server.mount('/style_groups_show.css', WEBrick::HTTPServlet::FileHandler, './public/style_groups_show.css') server.mount('/style_students_edit.css', WEBrick::HTTPServlet::FileHandler, './public/style_students_edit.css') server.mount('/style_students_index.css', WEBrick::HTTPServlet::FileHandler, './public/style_students_index.css') server.mount('/style_students_new.css', WEBrick::HTTPServlet::FileHandler, './public/style_students_new.css') server.mount('/style_students_show.css', WEBrick::HTTPServlet::FileHandler, './public/style_students_show.css') server.mount('/main.js', WEBrick::HTTPServlet::FileHandler, './public/main.js') server.mount_proc('/') do |req, res| app = JournalApp.new env = req.meta_vars env['rack.url_scheme'] = 'http' env['rack.input'] = req.body env['rack.errors'] = $stderr status, headers, body = app.call(env) res.status = status headers.each { |k, v| res[k] = v } res.body = "" body.each { |part| res.body << part } end trap('INT') { server.shutdown } puts ">>> Журнал студентов запущен: http://localhost:8080" puts ">>> Управление группами: http://localhost:8080/groups" server.start