diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..ba794603f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +CAMAAR/*.rbc +CAMAAR/capybara-*.html +CAMAAR/.rspec +CAMAAR/db/*.sqlite3 +CAMAAR/db/*.sqlite3-journal +CAMAAR/db/*.sqlite3-[0-9]* +CAMAAR/public/system +CAMAAR/coverage/ +CAMAAR/spec/tmp +CAMAAR/*.orig +CAMAAR/rerun.txt +CAMAAR/pickle-email-*.html + +# Ignore all logfiles and tempfiles. +CAMAAR/log/* +CAMAAR/tmp/* +CAMAAR/!/log/.keep +CAMAAR/!/tmp/.keep + +# TODO Comment out this rule if you are OK with secrets being uploaded to the repo +CAMAAR/config/initializers/secret_token.rb +CAMAAR/config/master.key + +# Only include if you have production secrets in this file, which is no longer a Rails default +# config/secrets.yml + +# dotenv, dotenv-rails +# TODO Comment out these rules if environment variables can be committed +.env +.env*.local + +## Environment normalization: +CAMAAR/.bundle +CAMAAR/vendor/bundle + +# these should all be checked in to normalize the environment: +# Gemfile.lock, .ruby-version, .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +CAMAAR/.rvmrc + +# if using bower-rails ignore default bower_components path bower.json files +CAMAAR/vendor/assets/bower_components +CAMAAR/*.bowerrc +CAMAAR/bower.json + +# Ignore pow environment settings +CAMAAR/.powenv + +# Ignore Byebug command history file. +CAMAAR/.byebug_history + +# Ignore node_modules +CAMAAR/node_modules/ + +# Ignore precompiled javascript packs +CAMAAR/public/packs +CAMAAR/public/packs-test +CAMAAR/public/assets + +# Ignore yarn files +CAMAAR/yarn-error.log +CAMAAR/yarn-debug.log* +CAMAAR/.yarn-integrity + +# Ignore uploaded files in development +CAMAAR/storage/* +CAMAAR/!/storage/.keep +CAMAAR/public/uploads \ No newline at end of file diff --git a/CAMAAR/.rspec b/CAMAAR/.rspec new file mode 100644 index 0000000000..c99d2e7396 --- /dev/null +++ b/CAMAAR/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/CAMAAR/.rubocop.yml b/CAMAAR/.rubocop.yml new file mode 100644 index 0000000000..f9d86d4a54 --- /dev/null +++ b/CAMAAR/.rubocop.yml @@ -0,0 +1,8 @@ +# Omakase Ruby styling for Rails +inherit_gem: { rubocop-rails-omakase: rubocop.yml } + +# Overwrite or add rules to create your own house style +# +# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` +# Layout/SpaceInsideArrayLiteralBrackets: +# Enabled: false diff --git a/CAMAAR/.ruby-version b/CAMAAR/.ruby-version new file mode 100644 index 0000000000..06eda28ac7 --- /dev/null +++ b/CAMAAR/.ruby-version @@ -0,0 +1 @@ +3.2.3 \ No newline at end of file diff --git a/CAMAAR/Gemfile b/CAMAAR/Gemfile new file mode 100644 index 0000000000..41b5c889f0 --- /dev/null +++ b/CAMAAR/Gemfile @@ -0,0 +1,57 @@ +# Gemfile +source "https://rubygems.org" + +# Core Rails +gem 'rails', '~> 8.0.0' +gem "propshaft" +gem "sqlite3" +gem "puma" +gem "importmap-rails" +gem "turbo-rails" +gem "stimulus-rails" +gem "jbuilder" +gem "bcrypt" +gem "tzinfo-data", platforms: %i[windows jruby] + +# Caching & Performance +gem "solid_cache" +gem "solid_queue" +gem "solid_cable" +gem "bootsnap", require: false +gem "thruster", require: false + +# Testing (TDD) +gem "rspec-rails" +gem "factory_bot_rails" +gem "faker" +gem "shoulda-matchers" + +#Verificações +gem 'simplecov', require: false, group: :test +gem "rubycritic", require: false +gem "rdoc", require: false +gem "metric_fu-Saikuro", require: false +gem "flog" + +group :development, :test do + gem "debug", platforms: %i[mri windows], require: "debug/prelude" + gem "brakeman", require: false + gem "rubocop-rails-omakase", require: false +end + +group :development do + gem "web-console" +end + +group :test do + # BDD - Cucumber + gem "cucumber-rails", require: false + + # Test dependencies + gem "capybara" + gem "selenium-webdriver" + gem "webdrivers" + gem "database_cleaner" + gem "simplecov", require: false + gem "rails-controller-testing" +end \ No newline at end of file diff --git a/CAMAAR/Gemfile.lock b/CAMAAR/Gemfile.lock new file mode 100644 index 0000000000..c09da51cfe --- /dev/null +++ b/CAMAAR/Gemfile.lock @@ -0,0 +1,515 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (8.0.4) + actionpack (= 8.0.4) + activesupport (= 8.0.4) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.4) + actionpack (= 8.0.4) + activejob (= 8.0.4) + activerecord (= 8.0.4) + activestorage (= 8.0.4) + activesupport (= 8.0.4) + mail (>= 2.8.0) + actionmailer (8.0.4) + actionpack (= 8.0.4) + actionview (= 8.0.4) + activejob (= 8.0.4) + activesupport (= 8.0.4) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.0.4) + actionview (= 8.0.4) + activesupport (= 8.0.4) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.0.4) + actionpack (= 8.0.4) + activerecord (= 8.0.4) + activestorage (= 8.0.4) + activesupport (= 8.0.4) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (8.0.4) + activesupport (= 8.0.4) + builder (~> 3.1) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.0.4) + activesupport (= 8.0.4) + globalid (>= 0.3.6) + activemodel (8.0.4) + activesupport (= 8.0.4) + activerecord (8.0.4) + activemodel (= 8.0.4) + activesupport (= 8.0.4) + timeout (>= 0.4.0) + activestorage (8.0.4) + actionpack (= 8.0.4) + activejob (= 8.0.4) + activerecord (= 8.0.4) + activesupport (= 8.0.4) + marcel (~> 1.0) + activesupport (8.0.4) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) + base64 (0.3.0) + bcrypt (3.1.20) + benchmark (0.5.0) + bigdecimal (3.3.1) + bindex (0.8.1) + bootsnap (1.19.0) + msgpack (~> 1.2) + brakeman (7.1.1) + racc + builder (3.3.0) + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + cgi (0.5.1) + childprocess (4.1.0) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + cucumber (10.1.1) + base64 (~> 0.2) + builder (~> 3.2) + cucumber-ci-environment (> 9, < 11) + cucumber-core (> 15, < 17) + cucumber-cucumber-expressions (> 17, < 19) + cucumber-html-formatter (> 20.3, < 22) + diff-lcs (~> 1.5) + logger (~> 1.6) + mini_mime (~> 1.1) + multi_test (~> 1.1) + sys-uname (~> 1.3) + cucumber-ci-environment (10.0.1) + cucumber-core (15.3.0) + cucumber-gherkin (> 27, < 35) + cucumber-messages (> 26, < 30) + cucumber-tag-expressions (> 5, < 9) + cucumber-cucumber-expressions (18.0.1) + bigdecimal + cucumber-gherkin (31.0.0) + cucumber-messages (> 25, < 28) + cucumber-html-formatter (21.15.1) + cucumber-messages (> 19, < 28) + cucumber-messages (27.2.0) + cucumber-rails (1.3.0) + capybara (>= 1.1.2) + cucumber (>= 1.1.8) + nokogiri (>= 1.5.0) + cucumber-tag-expressions (8.1.0) + database_cleaner (2.1.0) + database_cleaner-active_record (>= 2, < 3) + database_cleaner-active_record (2.2.2) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0) + database_cleaner-core (2.0.1) + date (3.5.1) + debug (1.11.0) + irb (~> 1.10) + reline (>= 0.3.8) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + diff-lcs (1.6.2) + docile (1.4.1) + drb (2.2.3) + dry-configurable (1.3.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-core (1.1.0) + concurrent-ruby (~> 1.0) + logger + zeitwerk (~> 2.6) + dry-inflector (1.2.0) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal + concurrent-ruby (~> 1.0) + dry-core (~> 1.1) + zeitwerk (~> 2.6) + dry-schema (1.14.1) + concurrent-ruby (~> 1.0) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) + zeitwerk (~> 2.6) + dry-types (1.8.3) + bigdecimal (~> 3.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + erb (4.0.4) + cgi (>= 0.3.3) + erubi (1.13.1) + et-orbi (1.4.0) + tzinfo + factory_bot (6.5.6) + activesupport (>= 6.1.0) + factory_bot_rails (6.5.1) + factory_bot (~> 6.5) + railties (>= 6.1.0) + faker (3.5.3) + i18n (>= 1.8.11, < 2) + ffi (1.17.2-x86_64-linux-gnu) + flay (2.14.0) + erubi (~> 1.10) + path_expander (~> 2.0) + prism (~> 1.5) + sexp_processor (~> 4.0) + flog (4.9.0) + path_expander (~> 2.0) + prism (~> 1.5) + sexp_processor (~> 4.8) + fugit (1.12.1) + et-orbi (~> 1.4) + raabro (~> 1.4) + globalid (1.3.0) + activesupport (>= 6.1) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + ice_nine (0.11.2) + importmap-rails (2.2.2) + actionpack (>= 6.0.0) + activesupport (>= 6.0.0) + railties (>= 6.0.0) + io-console (0.8.1) + irb (1.15.3) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + jbuilder (2.14.1) + actionview (>= 7.0.0) + activesupport (>= 7.0.0) + json (2.17.1) + language_server-protocol (3.17.0.5) + launchy (2.5.2) + addressable (~> 2.8) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.0) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.1.0) + matrix (0.4.3) + memoist3 (1.0.0) + metric_fu-Saikuro (1.1.3) + mini_mime (1.1.5) + minitest (5.27.0) + msgpack (1.8.0) + multi_test (1.1.0) + net-imap (0.6.0) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + path_expander (2.0.0) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.6.0) + propshaft (1.3.1) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack + psych (5.2.6) + date + stringio + public_suffix (6.0.2) + puma (7.1.0) + nio4r (~> 2.0) + raabro (1.4.0) + racc (1.8.1) + rack (3.2.4) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (8.0.4) + actioncable (= 8.0.4) + actionmailbox (= 8.0.4) + actionmailer (= 8.0.4) + actionpack (= 8.0.4) + actiontext (= 8.0.4) + actionview (= 8.0.4) + activejob (= 8.0.4) + activemodel (= 8.0.4) + activerecord (= 8.0.4) + activestorage (= 8.0.4) + activesupport (= 8.0.4) + bundler (>= 1.15.0) + railties (= 8.0.4) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.0.4) + actionpack (= 8.0.4) + activesupport (= 8.0.4) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.3.1) + rdoc (6.16.1) + erb + psych (>= 4.0.0) + tsort + reek (6.5.0) + dry-schema (~> 1.13) + logger (~> 1.6) + parser (~> 3.3.0) + rainbow (>= 2.0, < 4.0) + rexml (~> 3.1) + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-rails (8.0.2) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.6) + rubocop (1.81.7) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.47.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.48.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rails-omakase (1.1.0) + rubocop (>= 1.72) + rubocop-performance (>= 1.24) + rubocop-rails (>= 2.30) + ruby-progressbar (1.13.0) + ruby_parser (3.21.1) + racc (~> 1.5) + sexp_processor (~> 4.16) + rubycritic (4.11.0) + flay (~> 2.13) + flog (~> 4.7) + launchy (>= 2.5.2) + parser (>= 3.3.0.5) + rainbow (~> 3.1.1) + reek (~> 6.5.0, < 7.0) + rexml + ruby_parser (~> 3.21) + simplecov (>= 0.22.0) + tty-which (~> 0.5.0) + virtus (~> 2.0) + rubyzip (3.2.2) + securerandom (0.4.1) + selenium-webdriver (4.1.0) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2) + sexp_processor (4.17.4) + shoulda-matchers (5.3.0) + activesupport (>= 5.2.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + solid_cable (3.0.12) + actioncable (>= 7.2) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) + solid_cache (1.0.10) + activejob (>= 7.2) + activerecord (>= 7.2) + railties (>= 7.2) + solid_queue (1.2.4) + activejob (>= 7.1) + activerecord (>= 7.1) + concurrent-ruby (>= 1.3.1) + fugit (~> 1.11) + railties (>= 7.1) + thor (>= 1.3.1) + sqlite3 (2.8.1-x86_64-linux-gnu) + stimulus-rails (1.3.4) + railties (>= 6.0.0) + stringio (3.1.9) + sys-uname (1.4.1) + ffi (~> 1.1) + memoist3 (~> 1.0.0) + thor (1.4.0) + thread_safe (0.3.6) + thruster (0.1.16-x86_64-linux) + timeout (0.5.0) + tsort (0.2.0) + tty-which (0.5.0) + turbo-rails (2.0.20) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + tzinfo-data (1.2025.3) + tzinfo (>= 1.0.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.1.1) + useragent (0.16.11) + virtus (2.0.0) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + webdrivers (5.3.1) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (~> 4.0, < 4.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.6.18) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + bcrypt + bootsnap + brakeman + capybara + cucumber-rails + database_cleaner + debug + factory_bot_rails + faker + flog + importmap-rails + jbuilder + metric_fu-Saikuro + propshaft + puma + rails (~> 8.0.0) + rails-controller-testing + rdoc + rspec-rails + rubocop-rails-omakase + rubycritic + selenium-webdriver + shoulda-matchers + simplecov + solid_cable + solid_cache + solid_queue + sqlite3 + stimulus-rails + thruster + turbo-rails + tzinfo-data + web-console + webdrivers + +BUNDLED WITH + 2.4.1 diff --git a/CAMAAR/Rakefile b/CAMAAR/Rakefile new file mode 100644 index 0000000000..d1baef0699 --- /dev/null +++ b/CAMAAR/Rakefile @@ -0,0 +1,3 @@ +require_relative "config/application" + +Rails.application.load_tasks diff --git a/CAMAAR/app/assets/images/.keep b/CAMAAR/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/CAMAAR/app/assets/stylesheets/application.css b/CAMAAR/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..45574ddb99 --- /dev/null +++ b/CAMAAR/app/assets/stylesheets/application.css @@ -0,0 +1,18 @@ +.template-card { + transition: transform 0.2s; +} + +.template-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.pergunta-card { + border-left: 4px solid #0d6efd; +} + +@media (max-width: 768px) { + .card-title { + font-size: 1.1rem; + } +} \ No newline at end of file diff --git a/CAMAAR/app/controllers/admin/application_controller.rb b/CAMAAR/app/controllers/admin/application_controller.rb new file mode 100644 index 0000000000..e1699692d4 --- /dev/null +++ b/CAMAAR/app/controllers/admin/application_controller.rb @@ -0,0 +1,70 @@ +# Namespace for administrative area controllers. +# +# All controllers inside this module inherit shared authentication +# and layout behavior specific to administrators. +module Admin + + # Base controller for all admin area controllers. + # + # This controller enforces administrator authentication, + # sets the admin layout and provides helper methods related + # to the currently logged administrator. + class ApplicationController < ::ApplicationController + + # Sets the layout used by all admin controllers. + layout "admin" + + # Ensures that only authenticated administrators can access admin pages. + before_action :authenticate_administrador! + + # Exposes authentication helper methods to views. + helper_method :current_administrador, :administrador_signed_in? + + private + + # Returns the currently authenticated administrator. + # + # Retrieves the administrator record based on the + # administrator ID stored in the session. + # + # @return [Administrador, nil] the current administrator if logged in, + # or nil if no administrator is authenticated + # + # Side effects: + # - Reads data from the session + # - Memoizes the administrator in an instance variable + def current_administrador + @current_administrador ||= Administrador.find_by( + id: session[:administrador_id] + ) + end + + # Checks whether an administrator is currently signed in. + # + # @return [Boolean] true if an administrator is authenticated, + # false otherwise + # + # Side effects: + # - None + def administrador_signed_in? + current_administrador.present? + end + + # Authenticates access to admin area. + # + # Verifies if an administrator is logged in. If not, + # redirects the user to the admin login page with an alert message. + # + # @return [void] + # + # Side effects: + # - Sets a flash alert message + # - Redirects the HTTP request to the admin login page + def authenticate_administrador! + unless administrador_signed_in? + flash[:alert] = "Acesso restrito a administradores" + redirect_to admin_login_path + end + end + end +end diff --git a/CAMAAR/app/controllers/admin/base_controller.rb b/CAMAAR/app/controllers/admin/base_controller.rb new file mode 100644 index 0000000000..d42e54a347 --- /dev/null +++ b/CAMAAR/app/controllers/admin/base_controller.rb @@ -0,0 +1,21 @@ +# Namespace for administrative area controllers. +module Admin + + # Base controller for admin-specific controllers. + # + # This controller serves as a common superclass for all + # admin controllers, ensuring that the admin layout is used + # and that administrator authentication is enforced. + # + # It does not introduce new public methods, but applies + # shared behavior through inheritance and filters. + class BaseController < Admin::ApplicationController + + # Sets the layout used by admin controllers. + layout "admin" + + # Ensures that only authenticated administrators can access + # actions that inherit from this controller. + before_action :authenticate_administrador! + end +end diff --git a/CAMAAR/app/controllers/admin/dashboard_controller.rb b/CAMAAR/app/controllers/admin/dashboard_controller.rb new file mode 100644 index 0000000000..5b1f25fba9 --- /dev/null +++ b/CAMAAR/app/controllers/admin/dashboard_controller.rb @@ -0,0 +1,25 @@ +# Namespace for administrative area controllers. +module Admin + + # Controller responsible for rendering the administrative dashboard. + # + # Provides summary information related to the currently authenticated + # administrator, such as total counts of associated records. + class DashboardController < Admin::BaseController + + # Displays the admin dashboard page. + # + # Retrieves summary statistics related to the current administrator, + # including the total number of forms and templates. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + # - Reads associated records from the database + def index + @total_formularios = current_administrador.formularios.count + @total_templates = current_administrador.templates.count + end + end +end diff --git a/CAMAAR/app/controllers/admin/formularios_controller.rb b/CAMAAR/app/controllers/admin/formularios_controller.rb new file mode 100644 index 0000000000..05fd18545e --- /dev/null +++ b/CAMAAR/app/controllers/admin/formularios_controller.rb @@ -0,0 +1,250 @@ +require 'csv' + +# Namespace for administrative area controllers. +module Admin + + # Controller responsible for managing forms (formularios) in the admin area. + # + # Provides actions for creating, viewing, editing, deleting, sending, + # exporting and analyzing results of forms associated with the + # currently authenticated administrator. + class FormulariosController < ApplicationController + + # Sets the layout used by admin controllers. + layout "admin" + + # Ensures that only authenticated administrators can access the actions. + before_action :authenticate_administrador! + + # Loads the form for actions that require an existing record. + before_action :set_formulario, + only: [:show, :edit, :update, :destroy, :results, :export_csv] + + # Lists all forms belonging to the current administrator. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @formularios = current_administrador.formularios + .includes(:template, :turma) + .order(created_at: :desc) + end + + # Displays the form creation page. + # + # Initializes a new form and loads templates and classes (turmas) + # available to the administrator. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Formulario object + # - Queries the database + # - Assigns instance variables used by the view + def new + @formulario = current_administrador.formularios.new + @templates = current_administrador.templates + @turmas = Turma.all.order(:semestre, :horario) + end + + # Displays a specific form and its associated questions and answers. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def show + @perguntas = @formulario.perguntas + @respostas = @formulario.respostas.includes(:pergunta) + end + + # Displays the form editing page. + # + # Loads templates and classes (turmas) available for editing. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def edit + @templates = current_administrador.templates + @turmas = Turma.all.order(:semestre, :horario) + end + + # Creates a new form associated with the current administrator. + # + # @return [void] + # + # Side effects: + # - Attempts to persist a new record in the database + # - Redirects on success + # - Renders a view with validation errors on failure + def create + @formulario = current_administrador.formularios.new(formulario_params) + @templates = current_administrador.templates + @turmas = Turma.all.order(:semestre, :horario) + + if @formulario.save + redirect_to admin_formulario_path(@formulario), + notice: 'Formulário criado com sucesso. Você pode enviá-lo aos alunos quando estiver pronto.' + else + render :new, status: :unprocessable_entity + end + end + + # Sends the form to all students enrolled in the associated class. + # + # @return [void] + # + # Side effects: + # - Updates associations between students and the form + # - Writes data to the database + # - Redirects the HTTP request with a flash message + def send_to_students + @formulario = current_administrador.formularios.find(params[:id]) + + if @formulario.turma.alunos.any? + @formulario.turma.alunos.each do |aluno| + aluno.formularios_respostas << @formulario unless aluno.formularios_respostas.include?(@formulario) + end + + redirect_to admin_formulario_path(@formulario), + notice: 'Formulário enviado para todos os alunos da turma.' + else + redirect_to admin_formulario_path(@formulario), + alert: 'Esta turma não tem alunos matriculados.' + end + end + + # Updates an existing form. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects on success + # - Renders a view with validation errors on failure + def update + if @formulario.update(formulario_params) + redirect_to admin_formulario_path(@formulario), + notice: 'Formulário atualizado com sucesso.' + else + render :edit, status: :unprocessable_entity + end + end + + # Deletes a form. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + def destroy + @formulario.destroy + redirect_to admin_formularios_url, + notice: 'Formulário excluído com sucesso.' + end + + # Displays aggregated results of a form. + # + # Calculates response statistics and groups answers by question. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Performs calculations in memory + # - Assigns instance variables used by the view + def results + @respostas = Resposta.joins(:pergunta) + .where(pergunta: { formulario_id: @formulario.id }) + .includes(:pergunta) + + @answers_by_question = @respostas.group_by(&:pergunta) + + @total_responses = @respostas.select(:aluno_id).distinct.count + @total_students = @formulario.turma&.alunos&.count || 0 + @response_rate = @total_students > 0 ? + (@total_responses.to_f / @total_students * 100).round(1) : 0 + end + + # Exports form results to a CSV file. + # + # @return [void] + # + # Side effects: + # - Generates a CSV file in memory + # - Sends the file as an HTTP response attachment + def export_csv + perguntas = @formulario.perguntas.includes(:respostas) + turma = @formulario.turma + template = @formulario.template + + csv_string = CSV.generate(headers: true) do |csv| + csv << ["Template", "Turma", "Semestre", "Pergunta", "Resposta", "Respondido em"] + + perguntas.each do |pergunta| + pergunta.respostas.each do |resposta| + csv << [ + template.nome, + turma.disciplina.nome, + turma.semestre, + pergunta.texto, + resposta.texto, + I18n.l(resposta.created_at, format: :short) + ] + end + end + end + + filename = "resultados_formulario_#{@formulario.id}.csv" + + send_data csv_string, + filename: filename, + type: "text/csv; charset=utf-8", + disposition: "attachment" + end + + private + + # Loads the form associated with the given request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_formulario + @formulario = current_administrador.formularios.find(params[:id]) + end + + # Defines permitted parameters for form creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def formulario_params + params.require(:formulario).permit(:template_id, :turma_id) + end + + # Associates a form with all students of its class. + # + # @param formulario [Formulario] the form to be assigned + # @return [void] + # + # Side effects: + # - Updates associations in the database + def assign_form_to_students(formulario) + formulario.turma.alunos.each do |aluno| + aluno.formularios_respostas << formulario + end + end + end +end diff --git a/CAMAAR/app/controllers/admin/importacoes_controller.rb b/CAMAAR/app/controllers/admin/importacoes_controller.rb new file mode 100644 index 0000000000..30f46b43e9 --- /dev/null +++ b/CAMAAR/app/controllers/admin/importacoes_controller.rb @@ -0,0 +1,31 @@ +# Controller responsible for importing data into the system. +# +# Handles administrative import operations from predefined JSON files. +class Admin::ImportacoesController < Admin::BaseController + + # Imports members and classes data from JSON files. + # + # Reads predefined JSON files from the application data directory + # and triggers the import process for each file. + # + # @return [void] + # + # Side effects: + # - Reads files from the filesystem + # - Creates or updates records in the database via the import service + # - Redirects the HTTP request with a flash message + def create + members_path = Rails.root.join("app", "data", "class_members.json") + classes_path = Rails.root.join("app", "data", "classes.json") + + unless File.exist?(members_path) && File.exist?(classes_path) + redirect_to admin_root_path, alert: "Arquivos JSON não encontrados!" + return + end + + ImportJson.call(members_path) + ImportJson.call(classes_path) + + redirect_to admin_root_path, notice: "Importação concluída!" + end +end diff --git a/CAMAAR/app/controllers/admin/pages_controller.rb b/CAMAAR/app/controllers/admin/pages_controller.rb new file mode 100644 index 0000000000..6488e072a8 --- /dev/null +++ b/CAMAAR/app/controllers/admin/pages_controller.rb @@ -0,0 +1,49 @@ +# Controller responsible for handling static and utility pages +# in the administrative area. +class Admin::PagesController < Admin::BaseController + + # Handles the import page for administrative data. + # + # When accessed via a POST request, triggers the import process + # using the uploaded file. + # + # @return [void] + # + # Side effects: + # - May trigger a data import process + # - Redirects the HTTP request with a flash message + def importacoes + if request.post? + run_importacao + end + end + + private + + # Executes the import process using an uploaded file. + # + # Reads the uploaded file and delegates the import logic to + # the import service. + # + # @return [void] + # + # Side effects: + # - Reads data from an uploaded file + # - Creates or updates records in the database + # - Redirects the HTTP request with a flash message + def run_importacao + uploaded_file = params[:arquivo] + + if uploaded_file.blank? + redirect_to admin_importacoes_path, alert: "Nenhum arquivo enviado." + return + end + + begin + ImportJson.call(uploaded_file.read) + redirect_to admin_importacoes_path, notice: "Importação concluída com sucesso!" + rescue => e + redirect_to admin_importacoes_path, alert: "Falha ao importar: #{e.message}" + end + end +end diff --git a/CAMAAR/app/controllers/admin/sessions_controller.rb b/CAMAAR/app/controllers/admin/sessions_controller.rb new file mode 100644 index 0000000000..fe582a1763 --- /dev/null +++ b/CAMAAR/app/controllers/admin/sessions_controller.rb @@ -0,0 +1,63 @@ +# Namespace for administrative area controllers. +module Admin + + # Controller responsible for administrator authentication sessions. + # + # Manages login and logout actions for administrators, handling + # session creation and destruction. + class SessionsController < Admin::BaseController + + # Sets the layout used by the authentication pages. + layout "admin" + + # Skips authentication check for login-related actions. + skip_before_action :authenticate_administrador!, only: [:new, :create] + + # Displays the administrator login page. + # + # @return [void] + # + # Side effects: + # - Renders the login view + def new + end + + # Creates a new administrator session. + # + # Authenticates the administrator using the provided credentials + # and stores the administrator ID in the session upon success. + # + # @return [void] + # + # Side effects: + # - Reads request parameters + # - Writes data to the session + # - Redirects the HTTP request on success + # - Renders the login page with an alert on failure + def create + administrador = Administrador.find_by(usuario: params[:usuario]) + + if administrador&.authenticate(params[:password]) + session[:administrador_id] = administrador.id + redirect_to admin_root_path + else + flash[:alert] = "Usuário ou senha inválidos" + render :new + end + end + + # Destroys the current administrator session. + # + # Logs out the administrator by removing the session data. + # + # @return [void] + # + # Side effects: + # - Deletes data from the session + # - Redirects the HTTP request to the login page + def destroy + session.delete(:administrador_id) + redirect_to admin_login_path + end + end +end diff --git a/CAMAAR/app/controllers/admin/templates_controller.rb b/CAMAAR/app/controllers/admin/templates_controller.rb new file mode 100644 index 0000000000..2fde01cf04 --- /dev/null +++ b/CAMAAR/app/controllers/admin/templates_controller.rb @@ -0,0 +1,142 @@ +# Namespace for administrative area controllers. +module Admin + + # Controller responsible for managing templates. + # + # Provides actions to create, view, update and delete templates + # associated with the currently authenticated administrator. + class TemplatesController < Admin::ApplicationController + + # Loads the template for actions that require an existing record. + before_action :set_template, only: [:show, :edit, :update, :destroy] + + # Lists all templates belonging to the current administrator. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @templates = current_administrador.templates + .includes(:perguntas) + .order(created_at: :desc) + end + + # Displays a specific template and its questions. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def show + @perguntas = @template.perguntas + end + + # Displays the template creation page. + # + # Initializes a new template with a default number of questions. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Template object + # - Builds associated question objects + # - Assigns instance variables used by the view + def new + @template = current_administrador.templates.new + 3.times { @template.perguntas.build } + end + + # Displays the template editing page. + # + # Builds an additional question to allow insertion via the form. + # + # @return [void] + # + # Side effects: + # - Builds an associated question object + # - Assigns instance variables used by the view + def edit + @template.perguntas.build + end + + # Creates a new template. + # + # @return [void] + # + # Side effects: + # - Attempts to persist a new record in the database + # - Redirects the HTTP request on success + # - Renders the form with validation errors on failure + def create + @template = current_administrador.templates.new(template_params) + + if @template.save + redirect_to admin_template_path(@template), + notice: 'Template criado com sucesso.' + else + flash.now[:alert] = "Não foi possível criar o template. Verifique os erros abaixo." + (@template.perguntas.count...3).each { @template.perguntas.build } if @template.perguntas.count < 3 + render :new, status: :unprocessable_entity + end + end + + # Updates an existing template. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects the HTTP request on success + # - Renders the form with validation errors on failure + def update + if @template.update(template_params) + redirect_to admin_template_path(@template), + notice: 'Template atualizado com sucesso.' + else + render :edit, status: :unprocessable_entity + end + end + + # Deletes a template. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + def destroy + @template.destroy + redirect_to admin_templates_url, + notice: 'Template excluído com sucesso.' + end + + private + + # Loads the template associated with the given request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_template + @template = current_administrador.templates.find(params[:id]) + end + + # Defines permitted parameters for template creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def template_params + params.require(:template).permit( + :nome, + perguntas_attributes: [:id, :texto, :_destroy] + ) + end + end +end diff --git a/CAMAAR/app/controllers/administradors_controller.rb b/CAMAAR/app/controllers/administradors_controller.rb new file mode 100644 index 0000000000..9878a7f8da --- /dev/null +++ b/CAMAAR/app/controllers/administradors_controller.rb @@ -0,0 +1,140 @@ +# Controller responsible for managing administrators. +# +# Provides CRUD actions for creating, viewing, updating and deleting +# administrator records. +class AdministradorsController < ApplicationController + + # Loads the administrator for actions that require an existing record. + before_action :set_administrador, only: %i[ show edit update destroy ] + + # Lists all administrators. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @administradors = Administrador.all + end + + # Displays a specific administrator. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def show + end + + # Displays the administrator creation form. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Administrador object + def new + @administrador = Administrador.new + end + + # Displays the administrator editing form. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def edit + end + + # Creates a new administrator. + # + # @return [void] + # + # Side effects: + # - Attempts to persist a new record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def create + @administrador = Administrador.new(administrador_params) + + respond_to do |format| + if @administrador.save + format.html { redirect_to @administrador, notice: "Administrador was successfully created." } + format.json { render :show, status: :created, location: @administrador } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @administrador.errors, status: :unprocessable_entity } + end + end + end + + # Updates an existing administrator. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def update + respond_to do |format| + if @administrador.update(administrador_params) + format.html { redirect_to @administrador, notice: "Administrador was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @administrador } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @administrador.errors, status: :unprocessable_entity } + end + end + end + + # Deletes an administrator. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + # - Responds to HTML and JSON formats + def destroy + @administrador.destroy! + + respond_to do |format| + format.html { redirect_to administradors_path, notice: "Administrador was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + # Loads the administrator based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_administrador + @administrador = Administrador.find(params.expect(:id)) + end + + # Defines permitted parameters for administrator creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def administrador_params + params.expect( + administrador: [ + :nome, + :departamento, + :formacao, + :usuario, + :email, + :ocupacao, + :password_digest + ] + ) + end +end diff --git a/CAMAAR/app/controllers/alunos_controller.rb b/CAMAAR/app/controllers/alunos_controller.rb new file mode 100644 index 0000000000..9159f1785a --- /dev/null +++ b/CAMAAR/app/controllers/alunos_controller.rb @@ -0,0 +1,146 @@ +# Controller responsible for managing students (Alunos). +# +# Provides CRUD actions for listing, creating, viewing, updating +# and deleting student records. Access is restricted to authenticated users. +class AlunosController < ApplicationController + + # Ensures the user is authenticated before accessing any action. + before_action :require_login + + # Loads the student for actions that require an existing record. + before_action :set_aluno, only: %i[ show edit update destroy ] + + # Lists all students. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @alunos = Aluno.all + end + + # Displays a specific student. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def show + end + + # Displays the student creation form. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Aluno object + def new + @aluno = Aluno.new + end + + # Displays the student editing form. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def edit + end + + # Creates a new student. + # + # @return [void] + # + # Side effects: + # - Attempts to persist a new record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def create + @aluno = Aluno.new(aluno_params) + + respond_to do |format| + if @aluno.save + format.html { redirect_to @aluno, notice: "Aluno was successfully created." } + format.json { render :show, status: :created, location: @aluno } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @aluno.errors, status: :unprocessable_entity } + end + end + end + + # Updates an existing student. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def update + respond_to do |format| + if @aluno.update(aluno_params) + format.html { redirect_to @aluno, notice: "Aluno was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @aluno } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @aluno.errors, status: :unprocessable_entity } + end + end + end + + # Deletes a student. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + # - Responds to HTML and JSON formats + def destroy + @aluno.destroy! + + respond_to do |format| + format.html { redirect_to alunos_path, notice: "Aluno was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + # Loads the student based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_aluno + @aluno = Aluno.find(params.expect(:id)) + end + + # Defines permitted parameters for student creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def aluno_params + params.expect( + aluno: [ + :nome, + :curso, + :matricula, + :departamento, + :formacao, + :usuario, + :email, + :ocupacao, + :registered, + :password_digest + ] + ) + end +end diff --git a/CAMAAR/app/controllers/application_controller.rb b/CAMAAR/app/controllers/application_controller.rb new file mode 100644 index 0000000000..c27ad7ca4a --- /dev/null +++ b/CAMAAR/app/controllers/application_controller.rb @@ -0,0 +1,143 @@ +# Base controller for the application. +# +# Provides authentication helpers and current user resolution +# for different user roles (Administrador, Aluno, Professor). +class ApplicationController < ActionController::Base + + # Exposes authentication helper methods to the views. + helper_method :current_user, + :logged_in?, + :current_aluno, + :current_professor, + :current_administrador + + private + + # Returns the currently logged-in user, regardless of role. + # + # The user is resolved based on session data, which must include + # both the user ID and the user type. + # + # @return [Administrador, Aluno, Professor, nil] + # The authenticated user instance or nil if no user is logged in. + # + # Side effects: + # - Reads from the session + # - Queries the database + def current_user + return nil unless session[:user_id] && session[:user_type] + + @current_user ||= begin + user_class = session[:user_type].constantize + user_class.find_by(id: session[:user_id]) + end + end + + # Checks whether a user is currently logged in. + # + # @return [Boolean] + # true if a user is authenticated, false otherwise. + # + # Side effects: + # - Calls +current_user+ + def logged_in? + current_user.present? + end + + # Enforces authentication for protected pages. + # + # If the user is not logged in, redirects to the login page. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request + # - Sets a flash alert message + def require_login + unless logged_in? + flash[:alert] = 'Você precisa fazer login para acessar esta página' + redirect_to login_path + end + end + + # Returns the current administrator, if logged in as one. + # + # @return [Administrador, nil] + # + # Side effects: + # - Calls +current_user+ + def current_administrador + return nil unless logged_in? && current_user.is_a?(Administrador) + current_user + end + + # Restricts access to administrator-only actions. + # + # Redirects to the admin login page if the user is not an administrator. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request + # - Sets a flash alert message + def authenticate_administrador! + unless current_administrador + flash[:alert] = 'Acesso restrito a administradores' + redirect_to admin_login_path + end + end + + # Returns the current student, if logged in as one. + # + # @return [Aluno, nil] + # + # Side effects: + # - Calls +current_user+ + def current_aluno + return nil unless logged_in? && current_user.is_a?(Aluno) + current_user + end + + # Restricts access to student-only actions. + # + # Redirects to the login page if the user is not a student. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request + # - Sets a flash alert message + def authenticate_aluno! + unless current_aluno + flash[:alert] = 'Acesso restrito a alunos' + redirect_to login_path + end + end + + # Returns the current professor, if logged in as one. + # + # @return [Professor, nil] + # + # Side effects: + # - Calls +current_user+ + def current_professor + return nil unless logged_in? && current_user.is_a?(Professor) + current_user + end + + # Restricts access to professor-only actions. + # + # Redirects to the login page if the user is not a professor. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request + # - Sets a flash alert message + def authenticate_professor! + unless current_professor + flash[:alert] = 'Acesso restrito a professores' + redirect_to login_path + end + end +end diff --git a/CAMAAR/app/controllers/concerns/.keep b/CAMAAR/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/CAMAAR/app/controllers/disciplinas_controller.rb b/CAMAAR/app/controllers/disciplinas_controller.rb new file mode 100644 index 0000000000..0ed537caf1 --- /dev/null +++ b/CAMAAR/app/controllers/disciplinas_controller.rb @@ -0,0 +1,130 @@ +# Controller responsible for managing disciplines (Disciplinas). +# +# Provides CRUD actions to list, create, view, update and delete +# discipline records. +class DisciplinasController < ApplicationController + + # Loads the discipline for actions that require an existing record. + before_action :set_disciplina, only: %i[ show edit update destroy ] + + # Lists all disciplines. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @disciplinas = Disciplina.all + end + + # Displays a specific discipline. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def show + end + + # Displays the discipline creation form. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Disciplina object + def new + @disciplina = Disciplina.new + end + + # Displays the discipline editing form. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def edit + end + + # Creates a new discipline. + # + # @return [void] + # + # Side effects: + # - Attempts to persist a new record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def create + @disciplina = Disciplina.new(disciplina_params) + + respond_to do |format| + if @disciplina.save + format.html { redirect_to @disciplina, notice: "Disciplina was successfully created." } + format.json { render :show, status: :created, location: @disciplina } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @disciplina.errors, status: :unprocessable_entity } + end + end + end + + # Updates an existing discipline. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def update + respond_to do |format| + if @disciplina.update(disciplina_params) + format.html { redirect_to @disciplina, notice: "Disciplina was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @disciplina } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @disciplina.errors, status: :unprocessable_entity } + end + end + end + + # Deletes a discipline. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + # - Responds to HTML and JSON formats + def destroy + @disciplina.destroy! + + respond_to do |format| + format.html { redirect_to disciplinas_path, notice: "Disciplina was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + # Loads the discipline based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_disciplina + @disciplina = Disciplina.find(params.expect(:id)) + end + + # Defines permitted parameters for discipline creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def disciplina_params + params.expect(disciplina: [ :codigo, :nome ]) + end +end diff --git a/CAMAAR/app/controllers/formularios_controller.rb b/CAMAAR/app/controllers/formularios_controller.rb new file mode 100644 index 0000000000..a79b75bf0a --- /dev/null +++ b/CAMAAR/app/controllers/formularios_controller.rb @@ -0,0 +1,156 @@ +# Controller responsible for managing forms (Formulários) in the admin area. +# +# Provides actions to create, view, update, delete and analyze results +# of forms created by administrators. +class FormulariosController < ApplicationController + + # Uses the admin layout for all views rendered by this controller. + layout "admin" + + # Restricts access to authenticated administrators only. + before_action :authenticate_administrador! + + # Loads the form for actions that require an existing record. + before_action :set_formulario, only: [:show, :edit, :update, :destroy, :results] + + # Lists all forms created by the current administrator. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @formularios = current_administrador.formularios + .includes(:template, :turma) + .order(created_at: :desc) + end + + # Displays the form creation page. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Formulario object + # - Queries templates and classes (turmas) + def new + @formulario = current_administrador.formularios.new + @templates = current_administrador.templates + @turmas = Turma.all.order(:semestre, :horario) + end + + # Displays a specific form. + # + # @return [void] + # + # Side effects: + # - Loads associated questions + def show + @perguntas = @formulario.perguntas + end + + # Displays the form editing page. + # + # @return [void] + # + # Side effects: + # - Queries templates and classes (turmas) + def edit + @templates = current_administrador.templates + @turmas = Turma.all.order(:semestre, :horario) + end + + # Creates a new form. + # + # @return [void] + # + # Side effects: + # - Persists a new record in the database + # - Redirects or renders views depending on success or failure + def create + @formulario = current_administrador.formularios.new(formulario_params) + @templates = current_administrador.templates + @turmas = Turma.all.order(:semestre, :horario) + + if @formulario.save + redirect_to admin_formulario_path(@formulario), + notice: 'Formulário criado com sucesso.' + else + render :new, status: :unprocessable_entity + end + end + + # Updates an existing form. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects or renders views depending on success or failure + def update + if @formulario.update(formulario_params) + redirect_to admin_formulario_path(@formulario), + notice: 'Formulário atualizado com sucesso.' + else + render :edit, status: :unprocessable_entity + end + end + + # Deletes a form. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + def destroy + @formulario.destroy + redirect_to admin_formularios_url, + notice: 'Formulário excluído com sucesso.' + end + + # Displays aggregated results for a form. + # + # Calculates response statistics and groups answers by question. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Performs calculations + # - Assigns instance variables used by the view + def results + @respostas = Resposta.joins(:pergunta) + .where(pergunta: { formulario_id: @formulario.id }) + + @answers_by_question = @respostas.group_by(&:pergunta) + + @total_responses = @respostas.count + @total_students = @formulario.turma&.alunos&.count || 0 + @response_rate = @total_students > 0 ? + (@total_responses.to_f / @total_students * 100).round(1) : 0 + end + + private + + # Loads the form based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_formulario + @formulario = current_administrador.formularios.find(params[:id]) + end + + # Defines permitted parameters for form creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def formulario_params + params.require(:formulario).permit(:template_id, :turma_id) + end +end diff --git a/CAMAAR/app/controllers/pages_controller.rb b/CAMAAR/app/controllers/pages_controller.rb new file mode 100644 index 0000000000..be3da33f54 --- /dev/null +++ b/CAMAAR/app/controllers/pages_controller.rb @@ -0,0 +1,53 @@ +# Controller responsible for handling generic application pages. +# +# Manages the home page, login page and role-based dashboard redirection. +class PagesController < ApplicationController + + # Requires authentication for all actions except the home page. + before_action :require_login, except: [:home] + + # Handles the application home page. + # + # Redirects authenticated users to the dashboard and unauthenticated + # users to the login page. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request + def home + if logged_in? + redirect_to dashboard_path + else + redirect_to login_path + end + end + + # Displays the login page. + # + # @return [void] + # + # Side effects: + # - Renders the login view + def login + end + + # Redirects the user to the appropriate dashboard based on their role. + # + # @return [void] + # + # Side effects: + # - Inspects the current user role + # - Redirects the HTTP request + def dashboard + if current_user.is_a?(Aluno) + redirect_to student_dashboard_path + elsif current_user.is_a?(Professor) + redirect_to professor_dashboard_path + elsif current_user.is_a?(Administrador) + redirect_to admin_path + else + redirect_to login_path, alert: 'Por favor, faça login para continuar.' + end + end +end diff --git a/CAMAAR/app/controllers/password_resets_controller.rb b/CAMAAR/app/controllers/password_resets_controller.rb new file mode 100644 index 0000000000..ddfb6c831f --- /dev/null +++ b/CAMAAR/app/controllers/password_resets_controller.rb @@ -0,0 +1,109 @@ +# Controller responsible for handling password reset flow. +# +# Allows students and professors to request a password reset and +# update their credentials using a secure token. +class PasswordResetsController < ApplicationController + + # Loads the user based on the signed reset token for edit and update actions. + before_action :find_user_by_token, only: [:edit, :update] + + # Displays the password reset request form. + # + # @return [void] + # + # Side effects: + # - Renders the password reset request view + def new + end + + # Creates a password reset request. + # + # Finds the user by email and sends password reset instructions + # via email if the user exists. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Enqueues an email delivery job + # - Redirects or renders views + def create + email = params[:email] + + @user = Aluno.find_by(email: email) || Professor.find_by(email: email) + + if @user + PasswordResetMailer.reset_password(@user).deliver_later + + redirect_to login_path, + notice: 'Instruções de reset de senha foram enviadas para seu email.' + else + flash.now[:alert] = 'Email não encontrado' + render :new, status: :unprocessable_entity + end + end + + # Displays the password reset form. + # + # @return [void] + # + # Side effects: + # - Renders the password reset form view + def edit + end + + # Updates the user's password. + # + # Validates and saves the new password. If the user was not previously + # registered, marks them as registered. + # + # @return [void] + # + # Side effects: + # - Updates user credentials in the database + # - Updates the registration status + # - Redirects or renders views + def update + if @user.update(user_params) + @user.update_column(:registered, true) unless @user.registered? + + redirect_to login_path, + notice: 'Senha atualizada com sucesso! Faça login com sua nova senha.' + else + render :edit, status: :unprocessable_entity + end + end + + private + + # Finds a user based on a signed password reset token. + # + # The token is validated for expiration and purpose. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Redirects if the token is invalid or expired + def find_user_by_token + token = params[:id] + + @user = Aluno.find_signed(token, purpose: :password_reset) || + Professor.find_signed(token, purpose: :password_reset) + + if @user.nil? + redirect_to login_path, + alert: 'O link de reset de senha é inválido ou expirou. Solicite um novo.' + end + end + + # Defines permitted parameters for updating the user password. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def user_params + params.require(:user).permit(:password, :password_confirmation) + end +end diff --git a/CAMAAR/app/controllers/password_setups_controller.rb b/CAMAAR/app/controllers/password_setups_controller.rb new file mode 100644 index 0000000000..224d9cea97 --- /dev/null +++ b/CAMAAR/app/controllers/password_setups_controller.rb @@ -0,0 +1,98 @@ +# Controller responsible for handling initial password setup. +# +# Allows students and professors to define their password for the first time +# using a secure, signed token. +class PasswordSetupsController < ApplicationController + + # Skips CSRF verification for the update action to allow token-based access. + skip_before_action :verify_authenticity_token, only: [:update], raise: false + + # Loads the user based on the signed setup token. + before_action :find_user_by_token, only: [:edit, :update] + + # Prevents access if the user account is already active. + before_action :check_user_already_active, only: [:edit] + + # Displays the password setup form. + # + # @return [void] + # + # Side effects: + # - Renders the password setup view + def edit + end + + # Updates the user's password and activates the account. + # + # Marks the user as registered and automatically logs them in + # after a successful password setup. + # + # @return [void] + # + # Side effects: + # - Updates user credentials in the database + # - Updates the registration status + # - Writes to the session + # - Redirects or renders views + def update + if @user.update(user_params) + @user.update_column(:registered, true) + + session[:user_id] = @user.id + session[:user_type] = @user.class.name + + redirect_to dashboard_path, + notice: 'Senha definida com sucesso! Bem-vindo ao CAMAAR.' + else + render :edit, status: :unprocessable_entity + end + end + + private + + # Finds a user based on a signed password setup token. + # + # Validates the token for expiration and purpose. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Redirects if the token is invalid or expired + def find_user_by_token + token = params[:token] + + @user = Aluno.find_signed(token, purpose: :password_setup) || + Professor.find_signed(token, purpose: :password_setup) + + if @user.nil? + redirect_to login_path, + alert: 'O link de configuração de senha é inválido ou expirou. Solicite um novo.' + end + end + + # Checks whether the user account is already active. + # + # Prevents already registered users from accessing the setup flow. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request + def check_user_already_active + if @user&.registered? + redirect_to login_path, + alert: 'Sua conta já está ativa. Por favor, faça login.' + end + end + + # Defines permitted parameters for password setup. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def user_params + params.require(:user).permit(:password, :password_confirmation) + end +end diff --git a/CAMAAR/app/controllers/pergunta_controller.rb b/CAMAAR/app/controllers/pergunta_controller.rb new file mode 100644 index 0000000000..76d086447c --- /dev/null +++ b/CAMAAR/app/controllers/pergunta_controller.rb @@ -0,0 +1,130 @@ +# Controller responsible for managing questions (Perguntas). +# +# Provides CRUD actions to list, create, view, update and delete +# question records. +class PerguntaController < ApplicationController + + # Loads the question for actions that require an existing record. + before_action :set_perguntum, only: %i[ show edit update destroy ] + + # Lists all questions. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @pergunta = Perguntum.all + end + + # Displays a specific question. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def show + end + + # Displays the question creation form. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Perguntum object + def new + @perguntum = Perguntum.new + end + + # Displays the question editing form. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def edit + end + + # Creates a new question. + # + # @return [void] + # + # Side effects: + # - Persists a new record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def create + @perguntum = Perguntum.new(perguntum_params) + + respond_to do |format| + if @perguntum.save + format.html { redirect_to @perguntum, notice: "Perguntum was successfully created." } + format.json { render :show, status: :created, location: @perguntum } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @perguntum.errors, status: :unprocessable_entity } + end + end + end + + # Updates an existing question. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def update + respond_to do |format| + if @perguntum.update(perguntum_params) + format.html { redirect_to @perguntum, notice: "Perguntum was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @perguntum } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @perguntum.errors, status: :unprocessable_entity } + end + end + end + + # Deletes a question. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + # - Responds to HTML and JSON formats + def destroy + @perguntum.destroy! + + respond_to do |format| + format.html { redirect_to pergunta_path, notice: "Perguntum was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + # Loads the question based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_perguntum + @perguntum = Perguntum.find(params.expect(:id)) + end + + # Defines permitted parameters for question creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def perguntum_params + params.expect(perguntum: [ :template_id, :formulario_id, :texto, :resposta ]) + end +end diff --git a/CAMAAR/app/controllers/professor/dashboard_controller.rb b/CAMAAR/app/controllers/professor/dashboard_controller.rb new file mode 100644 index 0000000000..18b7970f6b --- /dev/null +++ b/CAMAAR/app/controllers/professor/dashboard_controller.rb @@ -0,0 +1,22 @@ +# Namespace for professor-related controllers. +module Professor + + # Controller responsible for rendering the professor dashboard. + # + # Provides a placeholder dashboard page for professors. + class DashboardController < ApplicationController + + # Displays the professor dashboard page. + # + # Currently renders a plain text message indicating that + # the dashboard is under construction. + # + # @return [void] + # + # Side effects: + # - Sends a plain text HTTP response + def index + render plain: "Professor Dashboard - Under Construction" + end + end +end diff --git a/CAMAAR/app/controllers/professors_controller.rb b/CAMAAR/app/controllers/professors_controller.rb new file mode 100644 index 0000000000..780b44b50d --- /dev/null +++ b/CAMAAR/app/controllers/professors_controller.rb @@ -0,0 +1,140 @@ +# Controller responsible for managing professors. +# +# Provides CRUD actions to list, create, view, update and delete +# professor records. +class ProfessorsController < ApplicationController + + # Loads the professor for actions that require an existing record. + before_action :set_professor, only: %i[ show edit update destroy ] + + # Lists all professors. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @professors = Professor.all + end + + # Displays a specific professor. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def show + end + + # Displays the professor creation form. + # + # @return [void] + # + # Side effects: + # - Instantiates a new Professor object + def new + @professor = Professor.new + end + + # Displays the professor editing form. + # + # @return [void] + # + # Side effects: + # - Assigns instance variables used by the view + def edit + end + + # Creates a new professor. + # + # @return [void] + # + # Side effects: + # - Persists a new record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def create + @professor = Professor.new(professor_params) + + respond_to do |format| + if @professor.save + format.html { redirect_to @professor, notice: "Professor was successfully created." } + format.json { render :show, status: :created, location: @professor } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @professor.errors, status: :unprocessable_entity } + end + end + end + + # Updates an existing professor. + # + # @return [void] + # + # Side effects: + # - Updates a record in the database + # - Redirects or renders views depending on success or failure + # - Responds to HTML and JSON formats + def update + respond_to do |format| + if @professor.update(professor_params) + format.html { redirect_to @professor, notice: "Professor was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @professor } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @professor.errors, status: :unprocessable_entity } + end + end + end + + # Deletes a professor. + # + # @return [void] + # + # Side effects: + # - Removes a record from the database + # - Redirects the HTTP request + # - Responds to HTML and JSON formats + def destroy + @professor.destroy! + + respond_to do |format| + format.html { redirect_to professors_path, notice: "Professor was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + # Loads the professor based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + def set_professor + @professor = Professor.find(params.expect(:id)) + end + + # Defines permitted parameters for professor creation and update. + # + # @return [ActionController::Parameters] permitted parameters + # + # Side effects: + # - Filters request parameters + def professor_params + params.expect( + professor: [ + :nome, + :departamento, + :formacao, + :usuario, + :email, + :ocupacao, + :password_digest + ] + ) + end +end diff --git a/CAMAAR/app/controllers/sessions_controller.rb b/CAMAAR/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..416bbdea0f --- /dev/null +++ b/CAMAAR/app/controllers/sessions_controller.rb @@ -0,0 +1,89 @@ +# Controller responsible for managing user sessions (authentication). +# +# Handles login and logout logic for Aluno and Professor users. +class SessionsController < ApplicationController + + # Prevents access to login pages if the user is already authenticated. + before_action :redirect_if_logged_in, only: [:new, :create] + + # Displays the login form. + # + # @return [void] + def new + end + + # Authenticates a user and creates a session. + # + # The method attempts to authenticate first as an Aluno, and if not found, + # as a Professor. Only registered users with valid credentials are allowed. + # + # @return [void] + # + # Side effects: + # - Sets session variables (:user_id, :user_type) + # - Redirects on success + # - Renders login form with error on failure + def create + email = params[:email] + password = params[:password] + + user = Aluno.find_by(email: email) + user_type = 'Aluno' + + if user.nil? + user = Professor.find_by(email: email) + user_type = 'Professor' + end + + if user && user.registered? && user.authenticate(password) + session[:user_id] = user.id + session[:user_type] = user_type + + redirect_to dashboard_path, notice: 'Login realizado com sucesso!' + else + @error_message = 'Email ou senha inválidos' + render :new, status: :unprocessable_entity + end + end + + # Destroys the current session (logout). + # + # @return [void] + # + # Side effects: + # - Clears session variables + # - Redirects to login page + def destroy + session.delete(:user_id) + session.delete(:user_type) + + redirect_to login_path, notice: 'Logout realizado com sucesso' + end + + private + + # Redirects the user if already logged in. + # + # Validates the stored session data and ensures the user still exists. + # If the user is valid, redirects to the dashboard. + # If not, clears the session. + # + # @return [void] + # + # Side effects: + # - Redirects authenticated users + # - Clears invalid session data + def redirect_if_logged_in + if session[:user_id] && session[:user_type] + user_class = session[:user_type].constantize + user = user_class.find_by(id: session[:user_id]) + + if user + redirect_to dashboard_path, notice: 'Você já está logado!' + else + session.delete(:user_id) + session.delete(:user_type) + end + end + end +end diff --git a/CAMAAR/app/controllers/student/dashboard_controller.rb b/CAMAAR/app/controllers/student/dashboard_controller.rb new file mode 100644 index 0000000000..38696dab09 --- /dev/null +++ b/CAMAAR/app/controllers/student/dashboard_controller.rb @@ -0,0 +1,34 @@ +# Namespace for student-related controllers. +module Student + + # Controller responsible for rendering the student dashboard. + # + # Provides summary information related to the currently authenticated + # student, including pending and answered forms. + class DashboardController < ApplicationController + + # Ensures that only authenticated students can access the dashboard. + before_action :authenticate_aluno! + + # Displays the student dashboard page. + # + # Calculates statistics related to the student's forms, such as + # pending forms, answered forms and total available forms. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @formularios_pendentes_count = current_aluno.formularios_pendentes.count + + formularios_respondidos = Formulario.joins(perguntas: :respostas) + .where(respostas: { aluno_id: current_aluno.id }) + .distinct + @formularios_respondidos_count = formularios_respondidos.count + + @total_formularios = current_aluno.formularios.count + end + end +end diff --git a/CAMAAR/app/controllers/student/formularios_controller.rb b/CAMAAR/app/controllers/student/formularios_controller.rb new file mode 100644 index 0000000000..a7ea4b6a8d --- /dev/null +++ b/CAMAAR/app/controllers/student/formularios_controller.rb @@ -0,0 +1,161 @@ +# Namespace for student-related controllers. +module Student + + # Controller responsible for managing student access to forms. + # + # Allows students to view pending forms and submit their answers, + # enforcing authentication and access validation rules. + class FormulariosController < ApplicationController + + # Ensures that only authenticated students can access the actions. + before_action :authenticate_aluno! + + # Loads the form for actions that require an existing record. + before_action :set_formulario, only: [:show, :submit] + + # Validates whether the student has access to the form. + before_action :validate_formulario_access, only: [:show, :submit] + + # Prevents access to forms that have already been answered. + before_action :validate_not_already_answered, only: [:show, :submit] + + # Lists all pending forms for the current student. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def index + @formularios_pendentes = current_aluno.formularios_pendentes + .includes(:template, :turma, :perguntas) + .order(created_at: :desc) + end + + # Displays a specific form and its questions. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns instance variables used by the view + def show + @perguntas = @formulario.perguntas.order(:id) + end + + # Submits the student's answers for a form. + # + # Validates the session state, checks for missing answers and + # persists responses within a database transaction. + # + # @return [void] + # + # Side effects: + # - Reads request parameters + # - Creates response records in the database + # - Writes flash messages + # - Redirects or renders views based on outcome + def submit + unless logged_in? + flash[:alert] = "Sua sessão expirou. Por favor, faça login novamente." + redirect_to login_path + return + end + + respostas_params = params[:respostas] || {} + perguntas = @formulario.perguntas + + missing_answers = [] + perguntas.each do |pergunta| + resposta_texto = respostas_params[pergunta.id.to_s] + if resposta_texto.blank? + missing_answers << pergunta.id + end + end + + if missing_answers.any? + flash.now[:alert] = "Por favor, responda todas as perguntas obrigatórias." + @perguntas = perguntas.order(:id) + render :show, status: :unprocessable_entity + return + end + + begin + ActiveRecord::Base.transaction do + perguntas.each do |pergunta| + Resposta.create!( + aluno: current_aluno, + pergunta: pergunta, + texto: respostas_params[pergunta.id.to_s].strip + ) + end + end + + flash[:notice] = "Respostas enviadas com sucesso! Obrigado por participar da avaliação." + redirect_to student_formularios_path + + rescue ActiveRecord::RecordInvalid => e + flash.now[:alert] = "Erro ao salvar respostas: #{e.message}" + @perguntas = perguntas.order(:id) + render :show, status: :unprocessable_entity + end + end + + private + + # Loads the form based on the request parameters. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Assigns an instance variable + # - Redirects the request if the form is not found + def set_formulario + @formulario = Formulario.find_by(id: params[:id]) + + unless @formulario + flash[:alert] = "Formulário não encontrado." + redirect_to student_formularios_path + end + end + + # Validates whether the current student is allowed to access the form. + # + # @return [void] + # + # Side effects: + # - Redirects the HTTP request when access is denied + # - Writes a flash alert message + def validate_formulario_access + return unless @formulario + + unless current_aluno.turmas.include?(@formulario.turma) + flash[:alert] = "Você não tem permissão para acessar este formulário." + redirect_to student_formularios_path + end + end + + # Prevents students from accessing forms that have already been answered. + # + # @return [void] + # + # Side effects: + # - Queries the database + # - Redirects the HTTP request when the form has already been answered + # - Writes a flash alert message + def validate_not_already_answered + return unless @formulario + + has_responses = Resposta.joins(:pergunta) + .where(pergunta: { formulario_id: @formulario.id }, + aluno_id: current_aluno.id) + .exists? + + if has_responses + flash[:alert] = "Você já respondeu este formulário." + redirect_to student_formularios_path + end + end + end +end diff --git a/CAMAAR/app/controllers/turmas_controller.rb b/CAMAAR/app/controllers/turmas_controller.rb new file mode 100644 index 0000000000..65de3a4ea1 --- /dev/null +++ b/CAMAAR/app/controllers/turmas_controller.rb @@ -0,0 +1,146 @@ +# Controller responsible for managing Turma resources. +# +# Provides full CRUD operations and supports both HTML and JSON responses. +class TurmasController < ApplicationController + + # Sets the turma instance for actions that operate on a specific record. + before_action :set_turma, only: %i[ show edit update destroy ] + + # Lists all turmas. + # + # @return [void] + def index + @turmas = Turma.all + end + + # Displays a single turma. + # + # @return [void] + def show + end + + # Initializes a new turma instance. + # + # @return [void] + def new + @turma = Turma.new + end + + # Displays the edit form for an existing turma. + # + # @return [void] + def edit + end + + # Creates a new turma. + # + # Responds with HTML or JSON depending on the request format. + # + # @return [void] + # + # Side effects: + # - Persists a new Turma record on success + # - Redirects or renders errors on failure + def create + @turma = Turma.new(turma_params) + + respond_to do |format| + if @turma.save + format.html { redirect_to @turma, notice: "Turma was successfully created." } + format.json { render :show, status: :created, location: @turma } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @turma.errors, status: :unprocessable_entity } + end + end + end + + # Updates an existing turma. + # + # Responds with HTML or JSON depending on the request format. + # + # @return [void] + # + # Side effects: + # - Updates the Turma record on success + # - Redirects or renders errors on failure + def update + respond_to do |format| + if @turma.update(turma_params) + format.html { redirect_to @turma, notice: "Turma was successfully updated.", status: :see_other } + format.json { render :show, status: :ok, location: @turma } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @turma.errors, status: :unprocessable_entity } + end + end + end + + # Deletes an existing turma. + # + # @return [void] + # + # Side effects: + # - Removes the Turma record from the database + # - Redirects to the index page + def destroy + @turma.destroy! + + respond_to do |format| + format.html { redirect_to turmas_path, notice: "Turma was successfully destroyed.", status: :see_other } + format.json { head :no_content } + end + end + + private + + # Finds and sets the turma based on the request parameters. + # + # @return [void] + def set_turma + @turma = Turma.find(params.expect(:id)) + end + + # Strong parameters for turma creation and update. + # + # @return [ActionController::Parameters] + def turma_params + params.expect( + turma: [ + :professor_id, + :disciplina_id, + :formulario_id, + :semestre, + :horario + ] + ) + end + + # Returns detailed information about a turma in JSON format. + # + # This action is intended for API or AJAX usage. + # + # @return [void] + # + # JSON response includes: + # - Disciplina name and code + # - Professor name + # - Semester and schedule + # - Number of enrolled students + def details + @turma = Turma.find(params[:id]) + + respond_to do |format| + format.json do + render json: { + disciplina_nome: @turma.disciplina.nome, + disciplina_codigo: @turma.disciplina.codigo, + professor_nome: @turma.professor.nome, + semestre: @turma.semestre, + horario: @turma.horario, + alunos_count: @turma.alunos.count + } + end + end + end +end diff --git a/CAMAAR/app/data/class_members.json b/CAMAAR/app/data/class_members.json new file mode 100755 index 0000000000..cd8ee43d2f --- /dev/null +++ b/CAMAAR/app/data/class_members.json @@ -0,0 +1,413 @@ +[ + { + "code": "CIC0097", + "classCode": "TA", + "semester": "2021.2", + "dicente": [ + { + "nome": "Ana Clara Jordao Perna", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190084006", + "usuario": "190084006", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "acjpjvjp@gmail.com" + }, + { + "nome": "Andre Carvalho de Roure", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "200033522", + "usuario": "200033522", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "andreCarvalhoroure@gmail.com" + }, + { + "nome": "André Carvalho Marques", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "150005491", + "usuario": "150005491", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "andre.acm97@outlook.com" + }, + { + "nome": "Antonio Vinicius de Moura Rodrigues", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190084502", + "usuario": "190084502", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "antoniovmoura.r@gmail.com" + }, + { + "nome": "Arthur Barreiros de Oliveira Mota", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190102829", + "usuario": "190102829", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "arthurbarreirosmota@gmail.com" + }, + { + "nome": "ARTHUR RODRIGUES NEVES", + "curso": "ENGENHARIA DE COMPUTAÇÃO/CIC", + "matricula": "202014403", + "usuario": "202014403", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "arthurcontroleambiental@gmail.com" + }, + { + "nome": "Bianca Glycia Boueri", + "curso": "ENGENHARIA MECATRÔNICA - CONTROLE E AUTOMAÇÃO/FTD", + "matricula": "170161561", + "usuario": "170161561", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "biancaglyciaboueri@gmail.com" + }, + { + "nome": "Caio Otávio Peluti Alencar", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190085312", + "usuario": "190085312", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "peluticaio@gmail.com" + }, + { + "nome": "Camila Frealdo Fraga", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "170007561", + "usuario": "170007561", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "camilizx2021@gmail.com" + }, + { + "nome": "Claudio Roberto Oliveira Peres de Barros", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190097591", + "usuario": "190097591", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "dinhobarros15@gmail.com" + }, + { + "nome": "Daltro Oliveira Vinuto", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "160025966", + "usuario": "160025966", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "daltroov777@gmail.com" + }, + { + "nome": "Davi de Moura Amaral", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "200016750", + "usuario": "200016750", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "davimouraamaral@gmail.com" + }, + { + "nome": "Eduardo Xavier Dantas", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190086530", + "usuario": "190086530", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "eduardoxdantas@gmail.com" + }, + { + "nome": "Enzo Nunes Leal Sampaio", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190062789", + "usuario": "190062789", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "enzonleal2016@hotmail.com" + }, + { + "nome": "Enzo Yoshio Niho", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190027304", + "usuario": "190027304", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "enzoyn@hotmail.com" + }, + { + "nome": "Gabriel Faustino Lima da Rocha", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190013249", + "usuario": "190013249", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "gabrielfaustino99@gmail.com" + }, + { + "nome": "Gabriel Ligoski", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190087498", + "usuario": "190087498", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "gabriel.ligoski@gmail.com" + }, + { + "nome": "GABRIEL MENDES CIRIATICO GUIMARÃES", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "202033202", + "usuario": "202033202", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "gabrielciriatico@gmail.com" + }, + { + "nome": "Gustavo Rodrigues dos Santos", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190014121", + "usuario": "190014121", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "190014121@aluno.unb.br" + }, + { + "nome": "Gustavo Rodrigues Gualberto", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190108266", + "usuario": "190108266", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "gustavorgualberto@gmail.com" + }, + { + "nome": "Igor David Morais", + "curso": "ENGENHARIA MECATRÔNICA - CONTROLE E AUTOMAÇÃO/FTD", + "matricula": "180102141", + "usuario": "180102141", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "igordavid13@gmail.com" + }, + { + "nome": "Jefte Augusto Gomes Batista", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "180057570", + "usuario": "180057570", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "ndaffte@gmail.com" + }, + { + "nome": "Karolina de Souza Silva", + "curso": "ENGENHARIA DE COMPUTAÇÃO/CIC", + "matricula": "190046791", + "usuario": "190046791", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "karolinasouza@outlook.com" + }, + { + "nome": "Kléber Rodrigues da Costa Júnior", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "200053680", + "usuario": "200053680", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "kleberrjr7@gmail.com" + }, + { + "nome": "Luca Delpino Barbabella", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "180125559", + "usuario": "180125559", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "barbadluca@gmail.com" + }, + { + "nome": "Lucas de Almeida Abreu Faria", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "170016668", + "usuario": "170016668", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "lucasaafaria@gmail.com" + }, + { + "nome": "Lucas Gonçalves Ramalho", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190098091", + "usuario": "190098091", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "lucasramalho29@gmail.com" + }, + { + "nome": "Lucas Monteiro Miranda", + "curso": "ENGENHARIA MECATRÔNICA - CONTROLE E AUTOMAÇÃO/FTD", + "matricula": "170149684", + "usuario": "170149684", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "luquinha_miranda@hotmail.com" + }, + { + "nome": "Lucas Resende Silveira Reis", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "180144421", + "usuario": "180144421", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "180144421@aluno.unb.br" + }, + { + "nome": "Luis Fernando Freitas Lamellas", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190016841", + "usuario": "190016841", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "lflamellas@icloud.com" + }, + { + "nome": "Luiza de Araujo Nunes Gomes", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190112794", + "usuario": "190112794", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "luizangomes@outlook.com" + }, + { + "nome": "Marcelo Aiache Postiglione", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "180126652", + "usuario": "180126652", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "180126652@aluno.unb.br" + }, + { + "nome": "Marcelo Junqueira Ferreira", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "200023624", + "usuario": "200023624", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "marcelojunqueiraf@gmail.com" + }, + { + "nome": "MARIA EDUARDA CARVALHO SANTOS", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190092556", + "usuario": "190092556", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "auntduda@gmail.com" + }, + { + "nome": "Maria Eduarda Lacerda Dantas", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "200067184", + "usuario": "200067184", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "lacwerda@gmail.com" + }, + { + "nome": "Maylla Krislainy de Sousa Silva", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190043873", + "usuario": "190043873", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "mayllak@hotmail.com" + }, + { + "nome": "Pedro Cesar Ribeiro Passos", + "curso": "ENGENHARIA MECATRÔNICA - CONTROLE E AUTOMAÇÃO/FTD", + "matricula": "180139312", + "usuario": "180139312", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "pedrocesarribeiro2013@gmail.com" + }, + { + "nome": "Rafael Mascarenhas Dal Moro", + "curso": "ENGENHARIA DE COMPUTAÇÃO/CIC", + "matricula": "170021041", + "usuario": "170021041", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "170021041@aluno.unb.br" + }, + { + "nome": "Rodrigo Mamedio Arrelaro", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190095164", + "usuario": "190095164", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "arrelaro1@hotmail.com" + }, + { + "nome": "Thiago de Oliveira Albuquerque", + "curso": "ENGENHARIA DE COMPUTAÇÃO/CIC", + "matricula": "140177442", + "usuario": "140177442", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "thiago.work.ti@outlook.com" + }, + { + "nome": "Thiago Elias dos Reis", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190126892", + "usuario": "190126892", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "thiagoeliasdosreis01@gmail.com" + }, + { + "nome": "Victor Hugo Rodrigues Fernandes", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "180132041", + "usuario": "180132041", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "aluno0sem.luz@gmail.com" + }, + { + "nome": "Vinicius Lima Passos", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "200028545", + "usuario": "200028545", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "viniciuslimapassos@gmail.com" + }, + { + "nome": "William Xavier dos Santos", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190075384", + "usuario": "190075384", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "wilxavier@me.com" + } + ], + "docente": { + "nome": "MARISTELA TERTO DE HOLANDA", + "departamento": "DEPTO CIÊNCIAS DA COMPUTAÇÃO", + "formacao": "DOUTORADO", + "usuario": "83807519491", + "email": "mholanda@unb.br", + "ocupacao": "docente" + } + } +] \ No newline at end of file diff --git a/CAMAAR/app/data/classes.json b/CAMAAR/app/data/classes.json new file mode 100755 index 0000000000..7f67530314 --- /dev/null +++ b/CAMAAR/app/data/classes.json @@ -0,0 +1,29 @@ +[ + { + "code": "CIC0097", + "name": "BANCOS DE DADOS", + "class": { + "classCode": "TA", + "semester": "2021.2", + "time": "35T45" + } + }, + { + "code": "CIC0105", + "name": "ENGENHARIA DE SOFTWARE", + "class": { + "classCode": "TA", + "semester": "2021.2", + "time": "35M12" + } + }, + { + "code": "CIC0202", + "name": "PROGRAMAÇÃO CONCORRENTE", + "class": { + "classCode": "TA", + "semester": "2021.2", + "time": "35M34" + } + } +] \ No newline at end of file diff --git a/CAMAAR/app/helpers/admin/dashboard_helper.rb b/CAMAAR/app/helpers/admin/dashboard_helper.rb new file mode 100644 index 0000000000..4052b7c4b7 --- /dev/null +++ b/CAMAAR/app/helpers/admin/dashboard_helper.rb @@ -0,0 +1,2 @@ +module Admin::DashboardHelper +end diff --git a/CAMAAR/app/helpers/administradors_helper.rb b/CAMAAR/app/helpers/administradors_helper.rb new file mode 100644 index 0000000000..73d0ee09b6 --- /dev/null +++ b/CAMAAR/app/helpers/administradors_helper.rb @@ -0,0 +1,2 @@ +module AdministradorsHelper +end diff --git a/CAMAAR/app/helpers/aluno/dashboard_helper.rb b/CAMAAR/app/helpers/aluno/dashboard_helper.rb new file mode 100644 index 0000000000..bb44ad8c9d --- /dev/null +++ b/CAMAAR/app/helpers/aluno/dashboard_helper.rb @@ -0,0 +1,2 @@ +module Aluno::DashboardHelper +end diff --git a/CAMAAR/app/helpers/alunos_helper.rb b/CAMAAR/app/helpers/alunos_helper.rb new file mode 100644 index 0000000000..cdbe19f2dd --- /dev/null +++ b/CAMAAR/app/helpers/alunos_helper.rb @@ -0,0 +1,2 @@ +module AlunosHelper +end diff --git a/CAMAAR/app/helpers/application_helper.rb b/CAMAAR/app/helpers/application_helper.rb new file mode 100644 index 0000000000..de6be7945c --- /dev/null +++ b/CAMAAR/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/CAMAAR/app/helpers/disciplinas_helper.rb b/CAMAAR/app/helpers/disciplinas_helper.rb new file mode 100644 index 0000000000..30b225dd00 --- /dev/null +++ b/CAMAAR/app/helpers/disciplinas_helper.rb @@ -0,0 +1,2 @@ +module DisciplinasHelper +end diff --git a/CAMAAR/app/helpers/formularios_helper.rb b/CAMAAR/app/helpers/formularios_helper.rb new file mode 100644 index 0000000000..3b96bb2209 --- /dev/null +++ b/CAMAAR/app/helpers/formularios_helper.rb @@ -0,0 +1,2 @@ +module FormulariosHelper +end diff --git a/CAMAAR/app/helpers/pages_helper.rb b/CAMAAR/app/helpers/pages_helper.rb new file mode 100644 index 0000000000..2c057fd054 --- /dev/null +++ b/CAMAAR/app/helpers/pages_helper.rb @@ -0,0 +1,2 @@ +module PagesHelper +end diff --git a/CAMAAR/app/helpers/pergunta_helper.rb b/CAMAAR/app/helpers/pergunta_helper.rb new file mode 100644 index 0000000000..9eda7229ee --- /dev/null +++ b/CAMAAR/app/helpers/pergunta_helper.rb @@ -0,0 +1,2 @@ +module PerguntaHelper +end diff --git a/CAMAAR/app/helpers/professor/dashboard_helper.rb b/CAMAAR/app/helpers/professor/dashboard_helper.rb new file mode 100644 index 0000000000..d5ab003ddc --- /dev/null +++ b/CAMAAR/app/helpers/professor/dashboard_helper.rb @@ -0,0 +1,2 @@ +module Professor::DashboardHelper +end diff --git a/CAMAAR/app/helpers/professors_helper.rb b/CAMAAR/app/helpers/professors_helper.rb new file mode 100644 index 0000000000..15367d63c7 --- /dev/null +++ b/CAMAAR/app/helpers/professors_helper.rb @@ -0,0 +1,2 @@ +module ProfessorsHelper +end diff --git a/CAMAAR/app/helpers/sessions_helper.rb b/CAMAAR/app/helpers/sessions_helper.rb new file mode 100644 index 0000000000..309f8b2eb3 --- /dev/null +++ b/CAMAAR/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/CAMAAR/app/helpers/templates_helper.rb b/CAMAAR/app/helpers/templates_helper.rb new file mode 100644 index 0000000000..888093b832 --- /dev/null +++ b/CAMAAR/app/helpers/templates_helper.rb @@ -0,0 +1,2 @@ +module TemplatesHelper +end diff --git a/CAMAAR/app/helpers/turmas_helper.rb b/CAMAAR/app/helpers/turmas_helper.rb new file mode 100644 index 0000000000..617e1373e9 --- /dev/null +++ b/CAMAAR/app/helpers/turmas_helper.rb @@ -0,0 +1,2 @@ +module TurmasHelper +end diff --git a/CAMAAR/app/javascript/application.js b/CAMAAR/app/javascript/application.js new file mode 100644 index 0000000000..76c8ec2ef8 --- /dev/null +++ b/CAMAAR/app/javascript/application.js @@ -0,0 +1,2 @@ +import "@hotwired/turbo-rails" +import "controllers" diff --git a/CAMAAR/app/javascript/controllers/application.js b/CAMAAR/app/javascript/controllers/application.js new file mode 100644 index 0000000000..1c617ed1de --- /dev/null +++ b/CAMAAR/app/javascript/controllers/application.js @@ -0,0 +1,11 @@ +import { Application } from "@hotwired/stimulus" +import QuestionsController from "./controllers/questions_controller" +import "@hotwired/turbo-rails" + +const application = Application.start() +application.register("questions", QuestionsController) + +application.debug = false +window.Stimulus = application + +export { application } \ No newline at end of file diff --git a/CAMAAR/app/javascript/controllers/hello_controller.js b/CAMAAR/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000000..5975c0789d --- /dev/null +++ b/CAMAAR/app/javascript/controllers/hello_controller.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/CAMAAR/app/javascript/controllers/index.js b/CAMAAR/app/javascript/controllers/index.js new file mode 100644 index 0000000000..6ffb4e9ee7 --- /dev/null +++ b/CAMAAR/app/javascript/controllers/index.js @@ -0,0 +1,3 @@ +import { application } from "controllers/application" +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) diff --git a/CAMAAR/app/javascript/controllers/question_controllers.js b/CAMAAR/app/javascript/controllers/question_controllers.js new file mode 100644 index 0000000000..1403f4a9b0 --- /dev/null +++ b/CAMAAR/app/javascript/controllers/question_controllers.js @@ -0,0 +1,79 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["container", "index"] + + connect() { + console.log("Questions controller connected"); + this.updateQuestionNumbers(); + } + + add(event) { + event.preventDefault(); + + const timestamp = new Date().getTime(); + const currentIndex = parseInt(this.indexTarget.value) || 0; + + const html = ` +
+
+ + +
+ + +
+ `; + + this.containerTarget.insertAdjacentHTML('beforeend', html); + this.indexTarget.value = currentIndex + 1; + this.updateQuestionNumbers(); + } + + remove(event) { + event.preventDefault(); + const field = event.target.closest('.pergunta-field'); + field.remove(); + this.reindexQuestions(); + this.updateQuestionNumbers(); + } + + reindexQuestions() { + const newFields = this.containerTarget.querySelectorAll('.pergunta-field[data-persisted="false"]'); + let currentIndex = 0; + + newFields.forEach((field, position) => { + const textarea = field.querySelector('textarea'); + textarea.name = `template[perguntas_attributes][${currentIndex}][texto]`; + + const hiddenId = field.querySelector('input[type="hidden"][name$="[id]"]'); + if (hiddenId) { + hiddenId.name = `template[perguntas_attributes][${currentIndex}][id]`; + } + + currentIndex++; + }); + + this.indexTarget.value = currentIndex; + } + + updateQuestionNumbers() { + const allFields = this.containerTarget.querySelectorAll('.pergunta-field'); + let questionNumber = 1; + + allFields.forEach((field) => { + const label = field.querySelector('label'); + if (label && field.dataset.persisted === 'true') { + label.textContent = `Pergunta #${questionNumber}`; + questionNumber++; + } + }); + } +} \ No newline at end of file diff --git a/CAMAAR/app/javascript/controllers/templates_controller.js b/CAMAAR/app/javascript/controllers/templates_controller.js new file mode 100644 index 0000000000..a6204a51cd --- /dev/null +++ b/CAMAAR/app/javascript/controllers/templates_controller.js @@ -0,0 +1,34 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["container"] + + connect() { + console.log("Templates form controller connected"); + } + + addQuestion(event) { + event.preventDefault(); + + const questionCount = this.containerTarget.querySelectorAll('.pergunta-field').length; + + const html = ` +
+
+ + +
+ + +
+ `; + + this.containerTarget.insertAdjacentHTML('beforeend', html); + } +} \ No newline at end of file diff --git a/CAMAAR/app/jobs/application_job.rb b/CAMAAR/app/jobs/application_job.rb new file mode 100644 index 0000000000..a009ace51c --- /dev/null +++ b/CAMAAR/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/CAMAAR/app/jobs/user_invite_job.rb b/CAMAAR/app/jobs/user_invite_job.rb new file mode 100644 index 0000000000..b2cbf25664 --- /dev/null +++ b/CAMAAR/app/jobs/user_invite_job.rb @@ -0,0 +1,33 @@ +class UserInviteJob < ApplicationJob + queue_as :default + + retry_on Net::SMTPServerBusy, wait: 5.minutes, attempts: 3 + retry_on Net::SMTPAuthenticationError, wait: 10.minutes, attempts: 2 + + discard_on Net::SMTPFatalError do |job, error| + Rails.logger.error "SMTP Fatal Error in UserInviteJob: #{error.message}" + Rails.logger.error "Job arguments: #{job.arguments.inspect}" + end + + def perform(user_ids) + Rails.logger.info "UserInviteJob started with #{user_ids.inspect}" + + sent_count = 0 + + if user_ids[:alunos].present? + Aluno.where(id: user_ids[:alunos], registered: false).find_each do |aluno| + UserInviteMailer.invite_student(aluno).deliver_later + sent_count += 1 + end + end + + if user_ids[:professores].present? + Professor.where(id: user_ids[:professores], registered: false).find_each do |professor| + UserInviteMailer.invite_professor(professor).deliver_later + sent_count += 1 + end + end + + Rails.logger.info "UserInviteJob completed: #{sent_count} emails enqueued" + end +end diff --git a/CAMAAR/app/mailers/application_mailer.rb b/CAMAAR/app/mailers/application_mailer.rb new file mode 100644 index 0000000000..3c34c8148f --- /dev/null +++ b/CAMAAR/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/CAMAAR/app/mailers/password_reset_mailer.rb b/CAMAAR/app/mailers/password_reset_mailer.rb new file mode 100644 index 0000000000..d9f62e96d6 --- /dev/null +++ b/CAMAAR/app/mailers/password_reset_mailer.rb @@ -0,0 +1,29 @@ +class PasswordResetMailer < ApplicationMailer + default from: 'noreply@camaar.unb.br' + + def reset_password(user) + @user = user + @reset_link = generate_reset_link(user) + @user_type = user.class.name == 'Aluno' ? 'Aluno' : 'Professor' + + mail( + to: @user.email, + subject: 'CAMAAR - Redefinir sua senha' + ) do |format| + format.html + format.text + end + end + + private + + def generate_reset_link(user) + token = user.signed_id(purpose: :password_reset, expires_in: 2.hours) + + Rails.application.routes.url_helpers.edit_password_reset_url( + id: token, + host: Rails.application.config.action_mailer.default_url_options[:host], + port: Rails.application.config.action_mailer.default_url_options[:port] + ) + end +end diff --git a/CAMAAR/app/mailers/user_invite_mailer.rb b/CAMAAR/app/mailers/user_invite_mailer.rb new file mode 100644 index 0000000000..6f265e4f9d --- /dev/null +++ b/CAMAAR/app/mailers/user_invite_mailer.rb @@ -0,0 +1,43 @@ +class UserInviteMailer < ApplicationMailer + default from: 'noreply@camaar.unb.br' + + def invite_student(aluno) + @user = aluno + @setup_link = generate_setup_link(aluno) + @user_type = 'Aluno' + + mail( + to: @user.email, + subject: 'Bem-vindo ao CAMAAR - Configure sua senha de acesso' + ) do |format| + format.html + format.text + end + end + + def invite_professor(professor) + @user = professor + @setup_link = generate_setup_link(professor) + @user_type = 'Professor' + + mail( + to: @user.email, + subject: 'Acesso ao CAMAAR - Configure sua senha de acesso' + ) do |format| + format.html + format.text + end + end + + private + + def generate_setup_link(user) + token = user.signed_id(purpose: :password_setup, expires_in: 48.hours) + + Rails.application.routes.url_helpers.edit_password_setup_url( + token: token, + host: Rails.application.config.action_mailer.default_url_options[:host], + port: Rails.application.config.action_mailer.default_url_options[:port] + ) + end +end diff --git a/CAMAAR/app/models/administrador.rb b/CAMAAR/app/models/administrador.rb new file mode 100644 index 0000000000..8eed6acf5d --- /dev/null +++ b/CAMAAR/app/models/administrador.rb @@ -0,0 +1,71 @@ +## +# Representa um usuário do tipo Administrador no sistema. +# +# O Administrador é responsável por gerenciar templates, formulários +# e acessar funcionalidades administrativas da aplicação. +# +# Utiliza autenticação segura via +has_secure_password+. +# +class Administrador < ApplicationRecord + + ## + # Habilita autenticação baseada em senha utilizando BCrypt. + # + # Requisitos: + # - A tabela deve possuir a coluna +password_digest+. + # + # Efeitos colaterais: + # - Adiciona métodos de autenticação como +authenticate+. + # + has_secure_password + + ## + # Valida a presença do nome do administrador. + # + # @return [void] + # + validates :nome, presence: true + + ## + # Valida a presença e unicidade do email do administrador. + # + # @return [void] + # + validates :email, presence: true, uniqueness: true + + ## + # Valida a presença e unicidade do usuário (login) do administrador. + # + # @return [void] + # + validates :usuario, presence: true, uniqueness: true + + ## + # Valida a presença do departamento ao qual o administrador pertence. + # + # @return [void] + # + validates :departamento, presence: true + + ## + # Associação com templates criados pelo administrador. + # + # @return [ActiveRecord::Associations::CollectionProxy