From 3fd4f02b752db3c64466b5be7795ff1c058c1826 Mon Sep 17 00:00:00 2001 From: rodley82 Date: Tue, 24 Feb 2026 13:07:44 +1300 Subject: [PATCH 1/2] now using json resume standard structure --- app/admin/users_admin.rb | 125 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/app/admin/users_admin.rb b/app/admin/users_admin.rb index d8960d0..186a12c 100644 --- a/app/admin/users_admin.rb +++ b/app/admin/users_admin.rb @@ -44,10 +44,129 @@ Trestle.resource(:users) do end def generate_json + generate_json_resume + end + + def generate_json_resume user = admin.find_instance(params) - ac = ActionController::Base.new() - json = ac.render_to_string(layout: false, template: "users/show", locals: { "@user": user } ) - render json: json, layout: false + + split_list = lambda do |value| + value.to_s.split(/[;,]/).map { |item| item.strip }.reject(&:blank?) + end + + parse_period = lambda do |period| + years = period.to_s.scan(/\b(19\d{2}|20\d{2})\b/).flatten + return [nil, nil] if years.empty? + [years[0], years[1]] + end + + normalize_url = lambda do |value| + return nil if value.blank? + value.match?(/\Ahttps?:\/\//) ? value : "https://#{value}" + end + + profile_from = lambda do |value, network, base_url| + return nil if value.blank? + + if value.include?("/") || value.include?(".") + url = normalize_url.call(value) + username = url.to_s.split("/").last&.delete_prefix("@") + else + username = value.delete_prefix("@") + url = base_url.present? ? "#{base_url}/#{username}" : nil + end + + profile = { "network" => network } + profile["username"] = username if username.present? + profile["url"] = url if url.present? + profile + end + + basics = { + "name" => user.name, + "label" => user.title, + "email" => user.email, + "phone" => user.phone, + "summary" => user.objective + }.compact + + if user.address.present? + basics["location"] = { "address" => user.address } + end + + primary_url = user.other_page_url.presence || user.linkedin.presence || user.github.presence + basics["url"] = normalize_url.call(primary_url) if primary_url.present? + + profiles = [] + profiles << profile_from.call(user.linkedin, "LinkedIn", "https://www.linkedin.com/in") + profiles << profile_from.call(user.github, "GitHub", "https://github.com") + profiles << profile_from.call(user.other_page_url, "Website", nil) + profiles.compact! + basics["profiles"] = profiles if profiles.any? + + work = user.work_experiences.active.map do |work_entry| + start_date, end_date = parse_period.call(work_entry.period) + highlights = split_list.call(work_entry.items_csv) + technologies = split_list.call(work_entry.technologies) + highlights << "Technologies: #{technologies.join(', ')}" if technologies.any? + + summary = work_entry.achivements.presence + summary = work_entry.period.presence if summary.blank? && start_date.blank? && end_date.blank? + + entry = { + "name" => work_entry.employer, + "position" => work_entry.title + }.compact + entry["summary"] = summary if summary.present? + entry["highlights"] = highlights if highlights.any? + entry["startDate"] = start_date if start_date.present? + entry["endDate"] = end_date if end_date.present? + if entry["startDate"].blank? && entry["endDate"].blank? && work_entry.reference_date.present? + entry["endDate"] = work_entry.reference_date.iso8601 + end + entry + end + + education = user.education_entries.active.map do |education_entry| + start_date, end_date = parse_period.call(education_entry.period) + + study_type = education_entry.title + area = nil + if study_type.present? && study_type.match?(/\s+in\s+/i) + study_type, area = study_type.split(/\s+in\s+/i, 2) + end + + courses = split_list.call(education_entry.items_csv) + + entry = { "institution" => education_entry.institution }.compact + entry["studyType"] = study_type if study_type.present? + entry["area"] = area if area.present? + entry["courses"] = courses if courses.any? + entry["startDate"] = start_date if start_date.present? + entry["endDate"] = end_date if end_date.present? + if entry["startDate"].blank? && entry["endDate"].blank? && education_entry.reference_date.present? + entry["endDate"] = education_entry.reference_date.iso8601 + end + entry + end + + skills = user.skills.active.map do |skill| + keywords = split_list.call(skill.detail) + entry = { "name" => skill.title }.compact + if keywords.any? + entry["keywords"] = keywords + elsif skill.detail.present? + entry["level"] = skill.detail + end + entry + end + + resume = { "basics" => basics } + resume["work"] = work if work.any? + resume["education"] = education if education.any? + resume["skills"] = skills if skills.any? + + render json: resume, layout: false end def generate_tex(template: "resume") -- 2.30.2 From 7aa69808fb9021e4a769d009d4e4d626b0a17937 Mon Sep 17 00:00:00 2001 From: rodley82 Date: Tue, 24 Feb 2026 13:12:04 +1300 Subject: [PATCH 2/2] moved the json rendering onto jbuilder --- app/admin/users_admin.rb | 121 +-------------------------- app/views/users/show.json.jbuilder | 129 +++++++++++++++++++++++------ 2 files changed, 107 insertions(+), 143 deletions(-) diff --git a/app/admin/users_admin.rb b/app/admin/users_admin.rb index 186a12c..8441608 100644 --- a/app/admin/users_admin.rb +++ b/app/admin/users_admin.rb @@ -48,125 +48,8 @@ Trestle.resource(:users) do end def generate_json_resume - user = admin.find_instance(params) - - split_list = lambda do |value| - value.to_s.split(/[;,]/).map { |item| item.strip }.reject(&:blank?) - end - - parse_period = lambda do |period| - years = period.to_s.scan(/\b(19\d{2}|20\d{2})\b/).flatten - return [nil, nil] if years.empty? - [years[0], years[1]] - end - - normalize_url = lambda do |value| - return nil if value.blank? - value.match?(/\Ahttps?:\/\//) ? value : "https://#{value}" - end - - profile_from = lambda do |value, network, base_url| - return nil if value.blank? - - if value.include?("/") || value.include?(".") - url = normalize_url.call(value) - username = url.to_s.split("/").last&.delete_prefix("@") - else - username = value.delete_prefix("@") - url = base_url.present? ? "#{base_url}/#{username}" : nil - end - - profile = { "network" => network } - profile["username"] = username if username.present? - profile["url"] = url if url.present? - profile - end - - basics = { - "name" => user.name, - "label" => user.title, - "email" => user.email, - "phone" => user.phone, - "summary" => user.objective - }.compact - - if user.address.present? - basics["location"] = { "address" => user.address } - end - - primary_url = user.other_page_url.presence || user.linkedin.presence || user.github.presence - basics["url"] = normalize_url.call(primary_url) if primary_url.present? - - profiles = [] - profiles << profile_from.call(user.linkedin, "LinkedIn", "https://www.linkedin.com/in") - profiles << profile_from.call(user.github, "GitHub", "https://github.com") - profiles << profile_from.call(user.other_page_url, "Website", nil) - profiles.compact! - basics["profiles"] = profiles if profiles.any? - - work = user.work_experiences.active.map do |work_entry| - start_date, end_date = parse_period.call(work_entry.period) - highlights = split_list.call(work_entry.items_csv) - technologies = split_list.call(work_entry.technologies) - highlights << "Technologies: #{technologies.join(', ')}" if technologies.any? - - summary = work_entry.achivements.presence - summary = work_entry.period.presence if summary.blank? && start_date.blank? && end_date.blank? - - entry = { - "name" => work_entry.employer, - "position" => work_entry.title - }.compact - entry["summary"] = summary if summary.present? - entry["highlights"] = highlights if highlights.any? - entry["startDate"] = start_date if start_date.present? - entry["endDate"] = end_date if end_date.present? - if entry["startDate"].blank? && entry["endDate"].blank? && work_entry.reference_date.present? - entry["endDate"] = work_entry.reference_date.iso8601 - end - entry - end - - education = user.education_entries.active.map do |education_entry| - start_date, end_date = parse_period.call(education_entry.period) - - study_type = education_entry.title - area = nil - if study_type.present? && study_type.match?(/\s+in\s+/i) - study_type, area = study_type.split(/\s+in\s+/i, 2) - end - - courses = split_list.call(education_entry.items_csv) - - entry = { "institution" => education_entry.institution }.compact - entry["studyType"] = study_type if study_type.present? - entry["area"] = area if area.present? - entry["courses"] = courses if courses.any? - entry["startDate"] = start_date if start_date.present? - entry["endDate"] = end_date if end_date.present? - if entry["startDate"].blank? && entry["endDate"].blank? && education_entry.reference_date.present? - entry["endDate"] = education_entry.reference_date.iso8601 - end - entry - end - - skills = user.skills.active.map do |skill| - keywords = split_list.call(skill.detail) - entry = { "name" => skill.title }.compact - if keywords.any? - entry["keywords"] = keywords - elsif skill.detail.present? - entry["level"] = skill.detail - end - entry - end - - resume = { "basics" => basics } - resume["work"] = work if work.any? - resume["education"] = education if education.any? - resume["skills"] = skills if skills.any? - - render json: resume, layout: false + @user = admin.find_instance(params) + render template: "users/show", formats: [:json], layout: false end def generate_tex(template: "resume") diff --git a/app/views/users/show.json.jbuilder b/app/views/users/show.json.jbuilder index 14f32eb..997b879 100644 --- a/app/views/users/show.json.jbuilder +++ b/app/views/users/show.json.jbuilder @@ -1,37 +1,118 @@ -json.user do - json.id @user.id - json.name @user.name - json.title @user.title +user = @user +split_list = lambda do |value| + value.to_s.split(/[;,]/).map { |item| item.strip }.reject(&:blank?) +end + +parse_period = lambda do |period| + years = period.to_s.scan(/\b(19\d{2}|20\d{2})\b/).flatten + return [nil, nil] if years.empty? + [years[0], years[1]] +end - json.work_experiences @user.work_experiences.active do |work| - # json.id work.id - json.title work.title +normalize_url = lambda do |value| + return nil if value.blank? + value.match?(/\Ahttps?:\/\//) ? value : "https://#{value}" +end - json.organization work.employer +profile_from = lambda do |value, network, base_url| + return nil if value.blank? - if work.technologies.present? - json.technologies work.technologies + if value.include?("/") || value.include?(".") + url = normalize_url.call(value) + username = url.to_s.split("/").last&.delete_prefix("@") + else + username = value.delete_prefix("@") + url = base_url.present? ? "#{base_url}/#{username}" : nil + end + + profile = { "network" => network } + profile["username"] = username if username.present? + profile["url"] = url if url.present? + profile +end + +json.basics do + json.name user.name if user.name.present? + json.label user.title if user.title.present? + json.email user.email if user.email.present? + json.phone user.phone if user.phone.present? + json.summary user.objective if user.objective.present? + + if user.address.present? + json.location do + json.address user.address end - json.period work.period + end + + primary_url = user.other_page_url.presence || user.linkedin.presence || user.github.presence + url = normalize_url.call(primary_url) + json.url url if url.present? + + profiles = [] + profiles << profile_from.call(user.linkedin, "LinkedIn", "https://www.linkedin.com/in") + profiles << profile_from.call(user.github, "GitHub", "https://github.com") + profiles << profile_from.call(user.other_page_url, "Website", nil) + profiles.compact! + json.profiles profiles if profiles.any? +end - json.responsibilities do - json.array! work.items_csv&.split(";") +if user.work_experiences.active.any? + json.work user.work_experiences.active do |work_entry| + start_date, end_date = parse_period.call(work_entry.period) + highlights = split_list.call(work_entry.items_csv) + technologies = split_list.call(work_entry.technologies) + highlights << "Technologies: #{technologies.join(', ')}" if technologies.any? + + summary = work_entry.achivements.presence + summary = work_entry.period.presence if summary.blank? && start_date.blank? && end_date.blank? + + json.name work_entry.employer if work_entry.employer.present? + json.position work_entry.title if work_entry.title.present? + json.summary summary if summary.present? + json.highlights highlights if highlights.any? + json.startDate start_date if start_date.present? + json.endDate end_date if end_date.present? + + if start_date.blank? && end_date.blank? && work_entry.reference_date.present? + json.endDate work_entry.reference_date.iso8601 end end +end + +if user.education_entries.active.any? + json.education user.education_entries.active do |education_entry| + start_date, end_date = parse_period.call(education_entry.period) + + study_type = education_entry.title + area = nil + if study_type.present? && study_type.match?(/\s+in\s+/i) + study_type, area = study_type.split(/\s+in\s+/i, 2) + end + + courses = split_list.call(education_entry.items_csv) + + json.institution education_entry.institution if education_entry.institution.present? + json.studyType study_type if study_type.present? + json.area area if area.present? + json.courses courses if courses.any? + json.startDate start_date if start_date.present? + json.endDate end_date if end_date.present? - json.skills @user.skills.active do |skill| - # json.id skill.id - json.title skill.title - json.detail skill.detail + if start_date.blank? && end_date.blank? && education_entry.reference_date.present? + json.endDate education_entry.reference_date.iso8601 + end end +end + +if user.skills.active.any? + json.skills user.skills.active do |skill| + keywords = split_list.call(skill.detail) - json.education_entries @user.education_entries.active do |edu| - json.institution edu.institution - json.title edu.title - json.location edu.location - json.period edu.period - json.items do - json.array! edu.items_csv&.split(";") + json.name skill.title if skill.title.present? + if keywords.any? + json.keywords keywords + elsif skill.detail.present? + json.level skill.detail end end end -- 2.30.2