identity-vault/app/controllers/docs_controller.rb
2025-12-02 12:51:16 -05:00

151 lines
4.4 KiB
Ruby

class DocsController < ApplicationController
layout "docs"
skip_before_action :authenticate_identity!, only: [ :index, :show ]
before_action :set_doc_path, only: :show
before_action :load_all_docs, only: [ :index, :show ]
def index
first_doc = @all_docs.first
return render plain: "No documentation found" if first_doc.nil?
redirect_to doc_path(slug: first_doc[:slug])
end
def show
raise ActionController::RoutingError, "Documentation not found" unless File.exist?(@doc_file_path)
@doc = parse_doc_file(@doc_file_path, params[:slug])
render :show
rescue StandardError => e
raise if e.is_a?(ActionController::RoutingError)
Rails.logger.error("Error parsing frontmatter for #{params[:slug]}: #{e.message}")
@doc = {
slug: params[:slug],
title: "Error",
content: "<p>Error parsing documentation.</p>"
}
render :show
end
private
def set_doc_path
slug = params[:slug] || "index"
# Sanitize the slug to prevent directory traversal
if slug.include?("..") || slug.include?("/")
raise ActionController::RoutingError, "Invalid documentation path"
end
docs_dir = Rails.root.join("app", "views", "docs")
erb_path = docs_dir.join("#{slug}.md.erb")
md_path = docs_dir.join("#{slug}.md")
@doc_file_path = File.exist?(erb_path) ? erb_path : md_path
end
def load_all_docs
@all_docs ||= begin
docs_dir = Rails.root.join("app", "views", "docs")
return [] unless Dir.exist?(docs_dir)
Dir.glob(docs_dir.join("*.md{,.erb}")).map do |file_path|
parsed = parse_frontmatter(file_path)
next unless parsed
slug = File.basename(file_path, ".*")
slug = File.basename(slug, ".*") if file_path.end_with?(".md.erb")
{
slug: slug,
title: parsed["title"] || slug.titleize,
category: parsed["category"],
order: parsed["order"] || 999,
hidden: parsed["hidden"] || false
}
rescue => e
Rails.logger.error("Error parsing doc #{file_path}: #{e.message}")
nil
end.compact.reject { |doc| doc[:hidden] }.sort_by { |doc| doc[:order] }
end
end
def parse_frontmatter(file_path)
cache_key = "doc_frontmatter:#{file_path}:#{File.mtime(file_path).to_i}"
Rails.cache.fetch(cache_key, expires_in: 1.hour) do
content = read_doc_content(file_path)
FrontMatterParser::Parser.new(:md).call(content)
end
end
def parse_doc_file(file_path, slug)
cache_key = "doc_content:#{file_path}:#{File.mtime(file_path).to_i}"
Rails.cache.fetch(cache_key, expires_in: 1.hour) do
content = File.read(file_path)
parsed = FrontMatterParser::Parser.new(:md).call(content)
# this is DISGUSTING, i'm sorry...
if file_path.to_s.end_with?(".erb")
erb_tags = []
content_with_placeholders = parsed.content.gsub(/<%=?.*?%>/m) do |match|
erb_tags << match
"ERB_PLACEHOLDER_#{erb_tags.length - 1}_PLACEHOLDER"
end
markdown_content = render_markdown(content_with_placeholders)
erb_tags.each_with_index do |erb_tag, index|
markdown_content = markdown_content.gsub("ERB_PLACEHOLDER_#{index}_PLACEHOLDER", erb_tag)
end
template = ERB.new(markdown_content)
final_content = template.result(view_context.instance_eval { binding })
else
final_content = render_markdown(parsed.content)
end
{
slug: slug,
title: parsed["title"] || "Untitled",
description: parsed["description"],
category: parsed["category"],
order: parsed["order"] || 999,
content: final_content.html_safe
}
end
end
def read_doc_content(file_path)
content = File.read(file_path)
if file_path.to_s.end_with?(".erb")
template = ERB.new(content)
template.result(view_context.instance_eval { binding })
else
content
end
end
def render_markdown(text)
renderer = Redcarpet::Render::HTML.new(
hard_wrap: true,
link_attributes: { target: "_blank", rel: "noopener noreferrer" }
)
markdown = Redcarpet::Markdown.new(
renderer,
autolink: false,
tables: true,
fenced_code_blocks: true,
strikethrough: true,
superscript: true,
highlight: true,
footnotes: true,
no_intra_emphasis: true,
disable_indented_code_blocks: true
)
markdown.render(text).html_safe
end
end