Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@
!/app/assets/builds/.keep

.idea/
doc/
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ bundle exec cucumber --tags "not @wip"
bundle exec cucumber features/sistema_login.feature
```

## Documentação

```bash
# Gerar documentação RDoc
rdoc app/controllers app/models app/services --output doc

# Abrir no navegador
firefox doc/index.html
```
6 changes: 5 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Controlador base para todos os controladores da aplicação
# Inclui autenticação e compatibilidade de navegador
class ApplicationController < ActionController::Base
include Authentication
include Authenticatable
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
# Apenas permite navegadores modernos
allow_browser versions: :modern

# Ação padrão do index
# @return [void]
def index
end
end
25 changes: 15 additions & 10 deletions app/controllers/avaliacoes_controller.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# Gerencia avaliações de turmas
# Listagem, criação e visualização de resultados
class AvaliacoesController < ApplicationController
# Requer autenticação para todas as actions

# Lista avaliações ou turmas do aluno
# @return [void] Renderiza index ou redireciona para login
def index
# Se for admin, mostrar todas as avaliações
# Se for aluno, mostrar todas as turmas matriculadas
@turmas = [] # Inicializa como array vazio por padrão
@turmas = []

if current_user&.eh_admin?
@avaliacoes = Avaliacao.all
elsif current_user
# Alunos veem suas turmas matriculadas
@turmas = current_user.turmas.includes(:avaliacoes)
else
# Não logado - redireciona para login
redirect_to new_session_path
end
end

# View de gestão de envios (admin)
# @return [void] Renderiza view de gestão
def gestao_envios
@turmas = Turma.all
end

# Cria nova avaliação para turma
# @return [void] Redireciona com mensagem de sucesso/erro
# @efeito_colateral Cria registro Avaliacao no banco
def create
turma_id = params[:turma_id]
turma = Turma.find_by(id: turma_id)
Expand Down Expand Up @@ -51,9 +54,10 @@ def create
end
end

# Exibe resultados com estatísticas
# @return [void] Renderiza HTML ou download CSV
def resultados
@avaliacao = Avaliacao.find(params[:id])
# Pré-carrega dependências para evitar N+1.
begin
@submissoes = @avaliacao.submissoes.includes(:aluno, :respostas)
@perguntas = @avaliacao.modelo.perguntas.order(:id)
Expand All @@ -76,22 +80,23 @@ def resultados

private

# Constrói hash de estatísticas por pergunta
# @param avaliacao [Avaliacao] Avaliação para analisar
# @return [Hash] Estatísticas por ID da pergunta
def build_question_statistics(avaliacao)
avaliacao.modelo.perguntas.each_with_object({}) do |pergunta, stats|
respostas = Resposta.joins(:submissao)
.where(submissoes: { avaliacao_id: avaliacao.id })
.where(questao_id: pergunta.id)

if [ "multipla_escolha", "checkbox", "escala" ].include?(pergunta.tipo)
# Conta cada opção escolhida
stats[pergunta.id] = {
type: pergunta.tipo,
data: respostas.group(:conteudo).count,
total: respostas.count,
responses: []
}
else
# Para texto, inclui as respostas para exibição
text_responses = respostas.pluck(:conteudo).compact.reject(&:blank?)
stats[pergunta.id] = {
type: pergunta.tipo,
Expand Down
9 changes: 8 additions & 1 deletion app/controllers/concerns/authenticatable.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
# app/controllers/concerns/authenticatable.rb
# Módulo com helpers de autenticação
# Fornece current_user e user_signed_in?
module Authenticatable
extend ActiveSupport::Concern

included do
helper_method :current_user, :user_signed_in?
end

# Requer login ou redireciona
# @return [void]
def authenticate_user!
redirect_to new_session_path, alert: "É necessário fazer login." unless user_signed_in?
end

# Retorna usuário atual logado
# @return [User, nil]
def current_user
Current.session&.user
end

# Verifica se há usuário logado
# @return [Boolean]
def user_signed_in?
current_user.present?
end
Expand Down
80 changes: 52 additions & 28 deletions app/controllers/concerns/authentication.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Módulo de autenticação para sessões
# Gerencia login, logout e verificação de autenticação
module Authentication
extend ActiveSupport::Concern

Expand All @@ -7,46 +9,68 @@ module Authentication
end

class_methods do
# Permite acesso sem autenticação
# @param options [Hash] Opções para skip_before_action
def allow_unauthenticated_access(**options)
skip_before_action :require_authentication, **options
end
end

private
def authenticated?
resume_session
end

def require_authentication
resume_session || request_authentication
end
# Verifica se usuário está autenticado
# @return [Boolean]
def authenticated?
resume_session
end

def resume_session
Current.session ||= find_session_by_cookie
end
# Requer autenticação ou redireciona
# @return [Session, nil]
def require_authentication
resume_session || request_authentication
end

def find_session_by_cookie
Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
end
# Retoma sessão existente
# @return [Session, nil]
def resume_session
Current.session ||= find_session_by_cookie
end

def request_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_session_path
end
# Encontra sessão pelo cookie
# @return [Session, nil]
def find_session_by_cookie
Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
end

def after_authentication_url
session.delete(:return_to_after_authenticating) || root_url
end
# Redireciona para login
# @return [void]
def request_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_session_path
end

def start_new_session_for(user)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
Current.session = session
cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end
end
# URL para redirecionar após login
# @return [String]
def after_authentication_url
session.delete(:return_to_after_authenticating) || root_url
end

def terminate_session
Current.session.destroy
cookies.delete(:session_id)
# Inicia nova sessão para usuário
# @param user [User] Usuário para autenticar
# @return [Session] Sessão criada
# @efeito_colateral Cria Session e define cookie
def start_new_session_for(user)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
Current.session = session
cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end
end

# Encerra sessão atual
# @return [void]
# @efeito_colateral Destrói Session e remove cookie
def terminate_session
Current.session.destroy
cookies.delete(:session_id)
end
end
3 changes: 3 additions & 0 deletions app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Controlador legado (redirecionamentos tratados por PagesController)
class HomeController < ApplicationController
# Ação home padrão
# @return [void]
def index
end
end
40 changes: 29 additions & 11 deletions app/controllers/modelos_controller.rb
Original file line number Diff line number Diff line change
@@ -1,42 +1,50 @@
# app/controllers/modelos_controller.rb
# Gerencia templates de avaliação com perguntas
# Acesso restrito a administradores
class ModelosController < ApplicationController
before_action :require_admin
before_action :set_modelo, only: [ :show, :edit, :update, :destroy, :clone ]

# GET /modelos
# Lista todos os templates
# @return [void] Renderiza index com modelos
def index
@modelos = Modelo.includes(:perguntas).order(created_at: :desc)
end

# GET /modelos/1
# Exibe detalhes do template
# @return [void] Renderiza view show
def show
end

# GET /modelos/new
# Formulário para novo template
# @return [void] Renderiza form com 3 perguntas em branco
def new
@modelo = Modelo.new
3.times { @modelo.perguntas.build } # Cria 3 perguntas em branco por padrão
3.times { @modelo.perguntas.build }
end

# GET /modelos/1/edit
# Formulário para editar template
# @return [void] Renderiza form de edição
def edit
@modelo.perguntas.build if @modelo.perguntas.empty?
end

# POST /modelos
# Cria novo template
# @return [void] Redireciona para show ou renderiza form com erros
# @efeito_colateral Cria registros Modelo e Pergunta
def create
@modelo = Modelo.new(modelo_params)

if @modelo.save
redirect_to @modelo, notice: "Modelo criado com sucesso."
else
# Garante que tenha pelo menos uma pergunta para mostrar no formulário
@modelo.perguntas.build if @modelo.perguntas.empty?
render :new, status: :unprocessable_entity
end
end

# PATCH/PUT /modelos/1
# Atualiza template existente
# @return [void] Redireciona para show ou renderiza form com erros
# @efeito_colateral Atualiza registros Modelo e Pergunta
def update
if @modelo.update(modelo_params)
redirect_to @modelo, notice: "Modelo atualizado com sucesso."
Expand All @@ -45,7 +53,9 @@ def update
end
end

# DELETE /modelos/1
# Exclui template se não estiver em uso
# @return [void] Redireciona para index com mensagem
# @efeito_colateral Destrói registro Modelo se não em uso
def destroy
if @modelo.em_uso?
redirect_to modelos_url, alert: "Não é possível excluir um modelo que está em uso."
Expand All @@ -55,7 +65,9 @@ def destroy
end
end

# POST /modelos/1/clone
# Duplica template com todas as perguntas
# @return [void] Redireciona para editar template clonado
# @efeito_colateral Cria novo Modelo com Perguntas copiadas
def clone
novo_titulo = "#{@modelo.titulo} (Cópia)"
novo_modelo = @modelo.clonar_com_perguntas(novo_titulo)
Expand All @@ -70,10 +82,14 @@ def clone

private

# Encontra modelo por ID
# @return [Modelo]
def set_modelo
@modelo = Modelo.find(params[:id])
end

# Parâmetros permitidos para modelo
# @return [ActionController::Parameters]
def modelo_params
params.require(:modelo).permit(
:titulo,
Expand All @@ -88,6 +104,8 @@ def modelo_params
)
end

# Verifica se usuário é admin
# @return [void] Redireciona não-admins
def require_admin
unless Current.session&.user&.eh_admin?
redirect_to root_path, alert: "Acesso restrito a administradores."
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Controlador principal para views de dashboard
class PagesController < ApplicationController
layout "application"

# Exibe dashboard principal
# @return [void] Redireciona alunos para avaliações, renderiza dashboard para admins
def index
# Feature 109: Redirecionar alunos para ver suas turmas
if Current.session&.user && !Current.session.user.eh_admin?
redirect_to avaliacoes_path
nil
end

# Admins veem dashboard admin
end
end
Loading