From d0738bea3102a76bff56609b1d8f12f80a0d4bf6 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:44:41 -0300 Subject: [PATCH 01/63] Update README with team member information Added team member names and IDs to README. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9d7fe1bf53..07a95d0c24 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # CAMAAR Sistema para avaliação de atividades acadêmicas remotas do CIC + +Integrantes Grupo 10: +- Bernardo Gomes Rodrigues (231034190) +- Isaac Silva (231025216) +- Maria Carolina Burgum Abreu Jorge (231013547) From cd8bf69963bbbf9a82d0bec62c1d2fa4af7d65f4 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Thu, 13 Nov 2025 09:10:27 -0300 Subject: [PATCH 02/63] chore: inicializa o projeto ruby on rails --- .dockerignore | 51 ++ .gitattributes | 9 + .github/dependabot.yml | 12 + .github/workflows/ci.yml | 90 ++++ .gitignore | 34 ++ .kamal/hooks/docker-setup.sample | 3 + .kamal/hooks/post-app-boot.sample | 3 + .kamal/hooks/post-deploy.sample | 14 + .kamal/hooks/post-proxy-reboot.sample | 3 + .kamal/hooks/pre-app-boot.sample | 3 + .kamal/hooks/pre-build.sample | 51 ++ .kamal/hooks/pre-connect.sample | 47 ++ .kamal/hooks/pre-deploy.sample | 122 +++++ .kamal/hooks/pre-proxy-reboot.sample | 3 + .kamal/secrets | 17 + .rubocop.yml | 8 + .ruby-version | 1 + Dockerfile | 72 +++ Gemfile | 66 +++ Gemfile.lock | 437 ++++++++++++++++++ README.md | 2 +- Rakefile | 6 + app/assets/images/.keep | 0 app/assets/stylesheets/application.css | 10 + app/controllers/application_controller.rb | 4 + app/controllers/concerns/.keep | 0 app/helpers/application_helper.rb | 2 + app/javascript/application.js | 3 + app/javascript/controllers/application.js | 9 + .../controllers/hello_controller.js | 7 + app/javascript/controllers/index.js | 4 + app/jobs/application_job.rb | 7 + app/mailers/application_mailer.rb | 4 + app/models/application_record.rb | 3 + app/models/concerns/.keep | 0 app/views/layouts/application.html.erb | 28 ++ app/views/layouts/mailer.html.erb | 13 + app/views/layouts/mailer.text.erb | 1 + app/views/pwa/manifest.json.erb | 22 + app/views/pwa/service-worker.js | 26 ++ bin/brakeman | 7 + bin/bundle | 109 +++++ bin/cucumber | 11 + bin/dev | 2 + bin/docker-entrypoint | 14 + bin/importmap | 4 + bin/jobs | 6 + bin/kamal | 27 ++ bin/rails | 4 + bin/rake | 4 + bin/rubocop | 8 + bin/setup | 34 ++ bin/thrust | 5 + config.ru | 6 + config/application.rb | 27 ++ config/boot.rb | 4 + config/cable.yml | 17 + config/cache.yml | 16 + config/credentials.yml.enc | 1 + config/cucumber.yml | 8 + config/database.yml | 41 ++ config/deploy.yml | 116 +++++ config/environment.rb | 5 + config/environments/development.rb | 76 +++ config/environments/production.rb | 90 ++++ config/environments/test.rb | 57 +++ config/importmap.rb | 7 + config/initializers/assets.rb | 7 + .../initializers/content_security_policy.rb | 25 + .../initializers/filter_parameter_logging.rb | 8 + config/initializers/inflections.rb | 16 + config/locales/en.yml | 31 ++ config/puma.rb | 41 ++ config/queue.yml | 18 + config/recurring.yml | 15 + config/routes.rb | 14 + config/storage.yml | 34 ++ db/cable_schema.rb | 11 + db/cache_schema.rb | 12 + db/queue_schema.rb | 129 ++++++ db/seeds.rb | 9 + features/importar_dados_sigaa.feature | 24 + features/step_definitions/.keep | 0 features/support/env.rb | 53 +++ lib/tasks/.keep | 0 lib/tasks/cucumber.rake | 69 +++ log/.keep | 0 public/400.html | 114 +++++ public/404.html | 114 +++++ public/406-unsupported-browser.html | 114 +++++ public/422.html | 114 +++++ public/500.html | 114 +++++ public/icon.png | Bin 0 -> 4166 bytes public/icon.svg | 3 + public/robots.txt | 1 + script/.keep | 0 storage/.keep | 0 test/application_system_test_case.rb | 5 + test/controllers/.keep | 0 test/fixtures/files/.keep | 0 test/helpers/.keep | 0 test/integration/.keep | 0 test/mailers/.keep | 0 test/models/.keep | 0 test/system/.keep | 0 test/test_helper.rb | 15 + tmp/.keep | 0 tmp/pids/.keep | 0 tmp/storage/.keep | 0 vendor/.keep | 0 vendor/javascript/.keep | 0 111 files changed, 3012 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .kamal/hooks/docker-setup.sample create mode 100644 .kamal/hooks/post-app-boot.sample create mode 100644 .kamal/hooks/post-deploy.sample create mode 100644 .kamal/hooks/post-proxy-reboot.sample create mode 100644 .kamal/hooks/pre-app-boot.sample create mode 100644 .kamal/hooks/pre-build.sample create mode 100644 .kamal/hooks/pre-connect.sample create mode 100644 .kamal/hooks/pre-deploy.sample create mode 100644 .kamal/hooks/pre-proxy-reboot.sample create mode 100644 .kamal/secrets create mode 100644 .rubocop.yml create mode 100644 .ruby-version create mode 100644 Dockerfile create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Rakefile create mode 100644 app/assets/images/.keep create mode 100644 app/assets/stylesheets/application.css create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/concerns/.keep create mode 100644 app/helpers/application_helper.rb create mode 100644 app/javascript/application.js create mode 100644 app/javascript/controllers/application.js create mode 100644 app/javascript/controllers/hello_controller.js create mode 100644 app/javascript/controllers/index.js create mode 100644 app/jobs/application_job.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/models/application_record.rb create mode 100644 app/models/concerns/.keep create mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100644 app/views/pwa/manifest.json.erb create mode 100644 app/views/pwa/service-worker.js create mode 100644 bin/brakeman create mode 100644 bin/bundle create mode 100644 bin/cucumber create mode 100644 bin/dev create mode 100644 bin/docker-entrypoint create mode 100644 bin/importmap create mode 100644 bin/jobs create mode 100644 bin/kamal create mode 100644 bin/rails create mode 100644 bin/rake create mode 100644 bin/rubocop create mode 100644 bin/setup create mode 100644 bin/thrust create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/cable.yml create mode 100644 config/cache.yml create mode 100644 config/credentials.yml.enc create mode 100644 config/cucumber.yml create mode 100644 config/database.yml create mode 100644 config/deploy.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/importmap.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/locales/en.yml create mode 100644 config/puma.rb create mode 100644 config/queue.yml create mode 100644 config/recurring.yml create mode 100644 config/routes.rb create mode 100644 config/storage.yml create mode 100644 db/cable_schema.rb create mode 100644 db/cache_schema.rb create mode 100644 db/queue_schema.rb create mode 100644 db/seeds.rb create mode 100644 features/importar_dados_sigaa.feature create mode 100644 features/step_definitions/.keep create mode 100644 features/support/env.rb create mode 100644 lib/tasks/.keep create mode 100644 lib/tasks/cucumber.rake create mode 100644 log/.keep create mode 100644 public/400.html create mode 100644 public/404.html create mode 100644 public/406-unsupported-browser.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/icon.png create mode 100644 public/icon.svg create mode 100644 public/robots.txt create mode 100644 script/.keep create mode 100644 storage/.keep create mode 100644 test/application_system_test_case.rb create mode 100644 test/controllers/.keep create mode 100644 test/fixtures/files/.keep create mode 100644 test/helpers/.keep create mode 100644 test/integration/.keep create mode 100644 test/mailers/.keep create mode 100644 test/models/.keep create mode 100644 test/system/.keep create mode 100644 test/test_helper.rb create mode 100644 tmp/.keep create mode 100644 tmp/pids/.keep create mode 100644 tmp/storage/.keep create mode 100644 vendor/.keep create mode 100644 vendor/javascript/.keep diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..325bfc036d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ +/.gitignore + +# Ignore bundler config. +/.bundle + +# Ignore all environment files. +/.env* + +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/.keep + +# Ignore assets. +/node_modules/ +/app/assets/builds/* +!/app/assets/builds/.keep +/public/assets + +# Ignore CI service files. +/.github + +# Ignore Kamal files. +/config/deploy*.yml +/.kamal + +# Ignore development files +/.devcontainer + +# Ignore Docker-related files +/.dockerignore +/Dockerfile* diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..8dc4323435 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored +config/credentials/*.yml.enc diff=rails_credentials +config/credentials.yml.enc diff=rails_credentials diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f0527e6be1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..7b7c0c59b3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,90 @@ +name: CI + +on: + pull_request: + push: + branches: [ main ] + +jobs: + scan_ruby: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for common Rails security vulnerabilities using static analysis + run: bin/brakeman --no-pager + + scan_js: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for security vulnerabilities in JavaScript dependencies + run: bin/importmap audit + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop -f github + + test: + runs-on: ubuntu-latest + + # services: + # redis: + # image: redis + # ports: + # - 6379:6379 + # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Install packages + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config google-chrome-stable + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run tests + env: + RAILS_ENV: test + # REDIS_URL: redis://localhost:6379/0 + run: bin/rails db:test:prepare test test:system + + - name: Keep screenshots from failed system tests + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots + path: ${{ github.workspace }}/tmp/screenshots + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..f92525ca5e --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# Temporary files generated by your text editor or operating system +# belong in git's global ignore instead: +# `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` + +# Ignore bundler config. +/.bundle + +# Ignore all environment files. +/.env* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key diff --git a/.kamal/hooks/docker-setup.sample b/.kamal/hooks/docker-setup.sample new file mode 100644 index 0000000000..2fb07d7d7a --- /dev/null +++ b/.kamal/hooks/docker-setup.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Docker set up on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-app-boot.sample b/.kamal/hooks/post-app-boot.sample new file mode 100644 index 0000000000..70f9c4bc95 --- /dev/null +++ b/.kamal/hooks/post-app-boot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-deploy.sample b/.kamal/hooks/post-deploy.sample new file mode 100644 index 0000000000..fd364c2a77 --- /dev/null +++ b/.kamal/hooks/post-deploy.sample @@ -0,0 +1,14 @@ +#!/bin/sh + +# A sample post-deploy hook +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" diff --git a/.kamal/hooks/post-proxy-reboot.sample b/.kamal/hooks/post-proxy-reboot.sample new file mode 100644 index 0000000000..1435a677f2 --- /dev/null +++ b/.kamal/hooks/post-proxy-reboot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Rebooted kamal-proxy on $KAMAL_HOSTS" diff --git a/.kamal/hooks/pre-app-boot.sample b/.kamal/hooks/pre-app-boot.sample new file mode 100644 index 0000000000..45f7355045 --- /dev/null +++ b/.kamal/hooks/pre-app-boot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/pre-build.sample b/.kamal/hooks/pre-build.sample new file mode 100644 index 0000000000..c5a55678b2 --- /dev/null +++ b/.kamal/hooks/pre-build.sample @@ -0,0 +1,51 @@ +#!/bin/sh + +# A sample pre-build hook +# +# Checks: +# 1. We have a clean checkout +# 2. A remote is configured +# 3. The branch has been pushed to the remote +# 4. The version we are deploying matches the remote +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) + +if [ -n "$(git status --porcelain)" ]; then + echo "Git checkout is not clean, aborting..." >&2 + git status --porcelain >&2 + exit 1 +fi + +first_remote=$(git remote) + +if [ -z "$first_remote" ]; then + echo "No git remote set, aborting..." >&2 + exit 1 +fi + +current_branch=$(git branch --show-current) + +if [ -z "$current_branch" ]; then + echo "Not on a git branch, aborting..." >&2 + exit 1 +fi + +remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) + +if [ -z "$remote_head" ]; then + echo "Branch not pushed to remote, aborting..." >&2 + exit 1 +fi + +if [ "$KAMAL_VERSION" != "$remote_head" ]; then + echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 + exit 1 +fi + +exit 0 diff --git a/.kamal/hooks/pre-connect.sample b/.kamal/hooks/pre-connect.sample new file mode 100644 index 0000000000..77744bdca8 --- /dev/null +++ b/.kamal/hooks/pre-connect.sample @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby + +# A sample pre-connect check +# +# Warms DNS before connecting to hosts in parallel +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +hosts = ENV["KAMAL_HOSTS"].split(",") +results = nil +max = 3 + +elapsed = Benchmark.realtime do + results = hosts.map do |host| + Thread.new do + tries = 1 + + begin + Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) + rescue SocketError + if tries < max + puts "Retrying DNS warmup: #{host}" + tries += 1 + sleep rand + retry + else + puts "DNS warmup failed: #{host}" + host + end + end + + tries + end + end.map(&:value) +end + +retries = results.sum - hosts.size +nopes = results.count { |r| r == max } + +puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] diff --git a/.kamal/hooks/pre-deploy.sample b/.kamal/hooks/pre-deploy.sample new file mode 100644 index 0000000000..05b3055b72 --- /dev/null +++ b/.kamal/hooks/pre-deploy.sample @@ -0,0 +1,122 @@ +#!/usr/bin/env ruby + +# A sample pre-deploy hook +# +# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. +# +# Fails unless the combined status is "success" +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_COMMAND +# KAMAL_SUBCOMMAND +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) + +# Only check the build status for production deployments +if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" + exit 0 +end + +require "bundler/inline" + +# true = install gems so this is fast on repeat invocations +gemfile(true, quiet: true) do + source "https://rubygems.org" + + gem "octokit" + gem "faraday-retry" +end + +MAX_ATTEMPTS = 72 +ATTEMPTS_GAP = 10 + +def exit_with_error(message) + $stderr.puts message + exit 1 +end + +class GithubStatusChecks + attr_reader :remote_url, :git_sha, :github_client, :combined_status + + def initialize + @remote_url = github_repo_from_remote_url + @git_sha = `git rev-parse HEAD`.strip + @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) + refresh! + end + + def refresh! + @combined_status = github_client.combined_status(remote_url, git_sha) + end + + def state + combined_status[:state] + end + + def first_status_url + first_status = combined_status[:statuses].find { |status| status[:state] == state } + first_status && first_status[:target_url] + end + + def complete_count + combined_status[:statuses].count { |status| status[:state] != "pending"} + end + + def total_count + combined_status[:statuses].count + end + + def current_status + if total_count > 0 + "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." + else + "Build not started..." + end + end + + private + def github_repo_from_remote_url + url = `git config --get remote.origin.url`.strip.delete_suffix(".git") + if url.start_with?("https://github.com/") + url.delete_prefix("https://github.com/") + elsif url.start_with?("git@github.com:") + url.delete_prefix("git@github.com:") + else + url + end + end +end + + +$stdout.sync = true + +begin + puts "Checking build status..." + + attempts = 0 + checks = GithubStatusChecks.new + + loop do + case checks.state + when "success" + puts "Checks passed, see #{checks.first_status_url}" + exit 0 + when "failure" + exit_with_error "Checks failed, see #{checks.first_status_url}" + when "pending" + attempts += 1 + end + + exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS + + puts checks.current_status + sleep(ATTEMPTS_GAP) + checks.refresh! + end +rescue Octokit::NotFound + exit_with_error "Build status could not be found" +end diff --git a/.kamal/hooks/pre-proxy-reboot.sample b/.kamal/hooks/pre-proxy-reboot.sample new file mode 100644 index 0000000000..061f8059e6 --- /dev/null +++ b/.kamal/hooks/pre-proxy-reboot.sample @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." diff --git a/.kamal/secrets b/.kamal/secrets new file mode 100644 index 0000000000..9a771a3985 --- /dev/null +++ b/.kamal/secrets @@ -0,0 +1,17 @@ +# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, +# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either +# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. + +# Example of extracting secrets from 1password (or another compatible pw manager) +# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) +# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) +# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) + +# Use a GITHUB_TOKEN if private repositories are needed for the image +# GITHUB_TOKEN=$(gh config get -h github.com oauth_token) + +# Grab the registry password from ENV +KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD + +# Improve security by using a password manager. Never check config/master.key into git! +RAILS_MASTER_KEY=$(cat config/master.key) diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..f9d86d4a54 --- /dev/null +++ b/.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/.ruby-version b/.ruby-version new file mode 100644 index 0000000000..fdeaef88f6 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-3.4.7 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..44381516b7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +# syntax=docker/dockerfile:1 +# check=error=true + +# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: +# docker build -t projeto_camaar . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name projeto_camaar projeto_camaar + +# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version +ARG RUBY_VERSION=3.4.7 +FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base + +# Rails app lives here +WORKDIR /rails + +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Set production environment +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" + +# Throw-away build stage to reduce size of final image +FROM base AS build + +# Install packages needed to build gems +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Install application gems +COPY Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile + +# Copy application code +COPY . . + +# Precompile bootsnap code for faster boot times +RUN bundle exec bootsnap precompile app/ lib/ + +# Precompiling assets for production without requiring secret RAILS_MASTER_KEY +RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile + + + + +# Final stage for app image +FROM base + +# Copy built artifacts: gems, application +COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --from=build /rails /rails + +# Run and own only the runtime files as a non-root user for security +RUN groupadd --system --gid 1000 rails && \ + useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ + chown -R rails:rails db log storage tmp +USER 1000:1000 + +# Entrypoint prepares the database. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Start server via Thruster by default, this can be overwritten at runtime +EXPOSE 80 +CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..41a51bd086 --- /dev/null +++ b/Gemfile @@ -0,0 +1,66 @@ +source "https://rubygems.org" + +# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" +gem "rails", "~> 8.0.4" +# The modern asset pipeline for Rails [https://github.com/rails/propshaft] +gem "propshaft" +# Use sqlite3 as the database for Active Record +gem "sqlite3", ">= 2.1" +# Use the Puma web server [https://github.com/puma/puma] +gem "puma", ">= 5.0" +# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] +gem "importmap-rails" +# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] +gem "turbo-rails" +# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] +gem "stimulus-rails" +# Build JSON APIs with ease [https://github.com/rails/jbuilder] +gem "jbuilder" + +# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] +# gem "bcrypt", "~> 3.1.7" + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem "tzinfo-data", platforms: %i[ windows jruby ] + +# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable +gem "solid_cache" +gem "solid_queue" +gem "solid_cable" + +# Reduces boot times through caching; required in config/boot.rb +gem "bootsnap", require: false + +# Deploy this application anywhere as a Docker container [https://kamal-deploy.org] +gem "kamal", require: false + +# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/] +gem "thruster", require: false + +# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] +# gem "image_processing", "~> 1.2" + +group :development, :test do + # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem + gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" + + # Static analysis for security vulnerabilities [https://brakemanscanner.org/] + gem "brakeman", require: false + + # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] + gem "rubocop-rails-omakase", require: false +end + +group :development do + # Use console on exceptions pages [https://github.com/rails/web-console] + gem "web-console" +end + +group :test do + # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] + gem "capybara" + gem "selenium-webdriver" + gem 'cucumber-rails', require: false + # database_cleaner is not mandatory, but highly recommended + gem 'database_cleaner' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..88d9dd45db --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,437 @@ +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.7) + public_suffix (>= 2.0.2, < 7.0) + ast (2.4.3) + base64 (0.3.0) + bcrypt_pbkdf (1.1.1) + benchmark (0.5.0) + bigdecimal (3.3.1) + bindex (0.8.1) + bootsnap (1.18.6) + 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) + concurrent-ruby (1.3.5) + connection_pool (2.5.4) + 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 (34.0.0) + cucumber-messages (> 25, < 29) + cucumber-html-formatter (21.15.1) + cucumber-messages (> 19, < 28) + cucumber-messages (27.2.0) + cucumber-rails (4.0.0) + capybara (>= 3.25, < 4) + cucumber (>= 7, < 11) + railties (>= 6.1, < 9) + cucumber-tag-expressions (8.0.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.0) + debug (1.11.0) + irb (~> 1.10) + reline (>= 0.3.8) + diff-lcs (1.6.2) + dotenv (3.1.8) + drb (2.2.3) + ed25519 (1.4.0) + erb (6.0.0) + erubi (1.13.1) + et-orbi (1.4.0) + tzinfo + ffi (1.17.2-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + 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) + 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.16.0) + kamal (2.8.2) + activesupport (>= 7.0) + base64 (~> 0.2) + bcrypt_pbkdf (~> 1.0) + concurrent-ruby (~> 1.2) + dotenv (~> 3.1) + ed25519 (~> 1.4) + net-ssh (~> 7.3) + sshkit (>= 1.23.0, < 2.0) + thor (~> 1.3) + zeitwerk (>= 2.6.18, < 3.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.24.1) + 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) + mini_mime (1.1.5) + minitest (5.26.1) + msgpack (1.8.0) + multi_test (1.1.0) + net-imap (0.5.12) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-scp (4.1.0) + net-ssh (>= 2.6.5, < 8.0.0) + net-sftp (4.0.0) + net-ssh (>= 5.0.0, < 8.0.0) + net-smtp (0.5.1) + net-protocol + net-ssh (7.3.0) + nio4r (2.7.5) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + 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.2.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-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.15.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + rexml (3.4.4) + 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.33.4) + 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) + rubyzip (3.2.2) + securerandom (0.4.1) + selenium-webdriver (4.38.0) + base64 (~> 0.2) + logger (~> 1.4) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 4.0) + websocket (~> 1.0) + 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.0-aarch64-linux-gnu) + sqlite3 (2.8.0-aarch64-linux-musl) + sqlite3 (2.8.0-arm-linux-gnu) + sqlite3 (2.8.0-arm-linux-musl) + sqlite3 (2.8.0-x86_64-linux-gnu) + sqlite3 (2.8.0-x86_64-linux-musl) + sshkit (1.24.0) + base64 + logger + net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) + net-ssh (>= 2.8.0) + ostruct + stimulus-rails (1.3.4) + railties (>= 6.0.0) + stringio (3.1.8) + sys-uname (1.4.1) + ffi (~> 1.1) + memoist3 (~> 1.0.0) + thor (1.4.0) + thruster (0.1.16) + thruster (0.1.16-aarch64-linux) + thruster (0.1.16-x86_64-linux) + timeout (0.4.4) + tsort (0.2.0) + turbo-rails (2.0.20) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.1.1) + useragent (0.16.11) + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + websocket (1.2.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.7.3) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap + brakeman + capybara + cucumber-rails + database_cleaner + debug + importmap-rails + jbuilder + kamal + propshaft + puma (>= 5.0) + rails (~> 8.0.4) + rubocop-rails-omakase + selenium-webdriver + solid_cable + solid_cache + solid_queue + sqlite3 (>= 2.1) + stimulus-rails + thruster + turbo-rails + tzinfo-data + web-console + +BUNDLED WITH + 2.6.9 diff --git a/README.md b/README.md index 07a95d0c24..12fe37f3d4 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ Sistema para avaliação de atividades acadêmicas remotas do CIC Integrantes Grupo 10: - Bernardo Gomes Rodrigues (231034190) - Isaac Silva (231025216) -- Maria Carolina Burgum Abreu Jorge (231013547) +- Maria Carolina Burgum Abreu Jorge (231013547) \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..9a5ea7383a --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 0000000000..fe93333c0f --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,10 @@ +/* + * This is a manifest file that'll be compiled into application.css. + * + * With Propshaft, assets are served efficiently without preprocessing steps. You can still include + * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard + * cascading order, meaning styles declared later in the document or manifest will override earlier ones, + * depending on specificity. + * + * Consider organizing styles into separate files for maintainability. + */ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000000..0d95db22b4 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,4 @@ +class ApplicationController < ActionController::Base + # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. + allow_browser versions: :modern +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000000..de6be7945c --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js new file mode 100644 index 0000000000..0d7b49404c --- /dev/null +++ b/app/javascript/application.js @@ -0,0 +1,3 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js new file mode 100644 index 0000000000..1213e85c7a --- /dev/null +++ b/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js new file mode 100644 index 0000000000..5975c0789d --- /dev/null +++ b/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/app/javascript/controllers/index.js b/app/javascript/controllers/index.js new file mode 100644 index 0000000000..1156bf8362 --- /dev/null +++ b/app/javascript/controllers/index.js @@ -0,0 +1,4 @@ +// Import and register all your controllers from the importmap via controllers/**/*_controller +import { application } from "controllers/application" +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000000..d394c3d106 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 0000000000..3c34c8148f --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 0000000000..b63caeb8a5 --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000000..b4e08b61db --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,28 @@ + + + + <%= content_for(:title) || "Projeto Camaar" %> + + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= yield :head %> + + <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> + <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> + + + + + + <%# Includes all stylesheet files in app/assets/stylesheets %> + <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000000..3aac9002ed --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000000..37f0bddbd7 --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb new file mode 100644 index 0000000000..76a2c73c81 --- /dev/null +++ b/app/views/pwa/manifest.json.erb @@ -0,0 +1,22 @@ +{ + "name": "ProjetoCamaar", + "icons": [ + { + "src": "/icon.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "/icon.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "maskable" + } + ], + "start_url": "/", + "display": "standalone", + "scope": "/", + "description": "ProjetoCamaar.", + "theme_color": "red", + "background_color": "red" +} diff --git a/app/views/pwa/service-worker.js b/app/views/pwa/service-worker.js new file mode 100644 index 0000000000..b3a13fb7bb --- /dev/null +++ b/app/views/pwa/service-worker.js @@ -0,0 +1,26 @@ +// Add a service worker for processing Web Push notifications: +// +// self.addEventListener("push", async (event) => { +// const { title, options } = await event.data.json() +// event.waitUntil(self.registration.showNotification(title, options)) +// }) +// +// self.addEventListener("notificationclick", function(event) { +// event.notification.close() +// event.waitUntil( +// clients.matchAll({ type: "window" }).then((clientList) => { +// for (let i = 0; i < clientList.length; i++) { +// let client = clientList[i] +// let clientPath = (new URL(client.url)).pathname +// +// if (clientPath == event.notification.data.path && "focus" in client) { +// return client.focus() +// } +// } +// +// if (clients.openWindow) { +// return clients.openWindow(event.notification.data.path) +// } +// }) +// ) +// }) diff --git a/bin/brakeman b/bin/brakeman new file mode 100644 index 0000000000..ace1c9ba08 --- /dev/null +++ b/bin/brakeman @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +ARGV.unshift("--ensure-latest") + +load Gem.bin_path("brakeman", "brakeman") diff --git a/bin/bundle b/bin/bundle new file mode 100644 index 0000000000..50da5fdf9e --- /dev/null +++ b/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/cucumber b/bin/cucumber new file mode 100644 index 0000000000..eb5e962e86 --- /dev/null +++ b/bin/cucumber @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +if vendored_cucumber_bin + load File.expand_path(vendored_cucumber_bin) +else + require 'rubygems' unless ENV['NO_RUBYGEMS'] + require 'cucumber' + load Cucumber::BINARY +end diff --git a/bin/dev b/bin/dev new file mode 100644 index 0000000000..5f91c20545 --- /dev/null +++ b/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100644 index 0000000000..57567d69b4 --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,14 @@ +#!/bin/bash -e + +# Enable jemalloc for reduced memory usage and latency. +if [ -z "${LD_PRELOAD+x}" ]; then + LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) + export LD_PRELOAD +fi + +# If running the rails server then create or migrate existing database +if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/bin/importmap b/bin/importmap new file mode 100644 index 0000000000..36502ab16c --- /dev/null +++ b/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/bin/jobs b/bin/jobs new file mode 100644 index 0000000000..dcf59f309a --- /dev/null +++ b/bin/jobs @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby + +require_relative "../config/environment" +require "solid_queue/cli" + +SolidQueue::Cli.start(ARGV) diff --git a/bin/kamal b/bin/kamal new file mode 100644 index 0000000000..cbe59b95ed --- /dev/null +++ b/bin/kamal @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kamal' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kamal", "kamal") diff --git a/bin/rails b/bin/rails new file mode 100644 index 0000000000..efc0377492 --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake new file mode 100644 index 0000000000..4fbf10b960 --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/bin/rubocop b/bin/rubocop new file mode 100644 index 0000000000..40330c0ff1 --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/setup b/bin/setup new file mode 100644 index 0000000000..be3db3c0d6 --- /dev/null +++ b/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/bin/thrust b/bin/thrust new file mode 100644 index 0000000000..36bde2d832 --- /dev/null +++ b/bin/thrust @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("thruster", "thrust") diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..4a3c09a688 --- /dev/null +++ b/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000..47d380417a --- /dev/null +++ b/config/application.rb @@ -0,0 +1,27 @@ +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module ProjetoCamaar + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..988a5ddc46 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 0000000000..b9adc5aa3a --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,17 @@ +# Async adapter only works within the same process, so for manually triggering cable updates from a console, +# and seeing results in the browser, you must do so from the web console (running inside the dev process), +# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view +# to make the web console appear. +development: + adapter: async + +test: + adapter: test + +production: + adapter: solid_cable + connects_to: + database: + writing: cable + polling_interval: 0.1.seconds + message_retention: 1.day diff --git a/config/cache.yml b/config/cache.yml new file mode 100644 index 0000000000..19d490843b --- /dev/null +++ b/config/cache.yml @@ -0,0 +1,16 @@ +default: &default + store_options: + # Cap age of oldest cache entry to fulfill retention policies + # max_age: <%= 60.days.to_i %> + max_size: <%= 256.megabytes %> + namespace: <%= Rails.env %> + +development: + <<: *default + +test: + <<: *default + +production: + database: cache + <<: *default diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc new file mode 100644 index 0000000000..a5d0df1768 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +ctHs0gkoY+BU5N44M3TdJPGY8WdddNyhshywXQnUtSTir7HHVB16FPzt5cCS35Pt43gSnz89k/jUHaDyK+ovHMHav3vRLYbiEIvLjzAXmXRETGRK1xX+yHD4ddg4jJjrBL2KGNRea1dzgp6RM+nfY2dXSvOCljJ1Xm4RnTXdUYCUXQN86mwocT8+O5hw7lnb4CSagQDHpdWFgO1p/2lcnax4bdRNup6N4wop6ALHRozj6Iu82Jwc+Zk8hm/8bqflkw6bmPHHDsgQop9BwefWKJaBBdhvwxD0x1eMLdmzbYHhtiuU4TZkfUq7s8PacQGyQBJZ6XniNsYtT25tpVgCr2rnDIkxqemziHeOXDnU5nVbMd0RiljELgUZQ4eXYFmlUc1ULTmWDElHvBzrUK5dV7EZ+3bpG4F0CXwb2OIKL/VlHIPwDnqPtF56XofPZIZprvL8GkgMSPGvrZm2EwHX8m9YgN7UGLjCgaZUygybXihsQKRDNnk1ilFj--HmGOjo0474w4Pvp8--Ho7PmAnPSHMEvSmHl1sQkQ== \ No newline at end of file diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000000..47a4663ae2 --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,8 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun = rerun.strip.gsub /\s/, ' ' +rerun_opts = rerun.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags 'not @wip'" +%> +default: <%= std_opts %> features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags 'not @wip' diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000000..2640cb5f30 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,41 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: storage/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: storage/test.sqlite3 + + +# Store production database in the storage/ directory, which by default +# is mounted as a persistent Docker volume in config/deploy.yml. +production: + primary: + <<: *default + database: storage/production.sqlite3 + cache: + <<: *default + database: storage/production_cache.sqlite3 + migrations_paths: db/cache_migrate + queue: + <<: *default + database: storage/production_queue.sqlite3 + migrations_paths: db/queue_migrate + cable: + <<: *default + database: storage/production_cable.sqlite3 + migrations_paths: db/cable_migrate diff --git a/config/deploy.yml b/config/deploy.yml new file mode 100644 index 0000000000..4a5018889d --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,116 @@ +# Name of your application. Used to uniquely configure containers. +service: projeto_camaar + +# Name of the container image. +image: your-user/projeto_camaar + +# Deploy to these servers. +servers: + web: + - 192.168.0.1 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs + +# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. +# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer. +# +# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. +proxy: + ssl: true + host: app.example.com + +# Credentials for your image host. +registry: + # Specify the registry server, if you're not using Docker Hub + # server: registry.digitalocean.com / ghcr.io / ... + username: your-user + + # Always use an access token rather than real password when possible. + password: + - KAMAL_REGISTRY_PASSWORD + +# Inject ENV variables into containers (secrets come from .kamal/secrets). +env: + secret: + - RAILS_MASTER_KEY + clear: + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + SOLID_QUEUE_IN_PUMA: true + + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # Use projeto_camaar-db for a db accessory server on same machine via local kamal docker network. + # DB_HOST: 192.168.0.2 + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. +aliases: + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole" + + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +volumes: + - "projeto_camaar_storage:/rails/storage" + + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +asset_path: /rails/public/assets + +# Configure the image builder. +builder: + arch: amd64 + + # # Build image via remote server (useful for faster amd64 builds on arm64 computers) + # remote: ssh://docker@docker-builder-server + # + # # Pass arguments and secrets to the Docker build process + # args: + # RUBY_VERSION: ruby-3.4.7 + # secrets: + # - GITHUB_TOKEN + # - RAILS_MASTER_KEY + +# Use a different ssh user than root +# ssh: +# user: app + +# Use accessory services (secrets come from .kamal/secrets). +# accessories: +# db: +# image: mysql:8.0 +# host: 192.168.0.2 +# # Change to 3306 to expose port to the world instead of just local network. +# port: "127.0.0.1:3306:3306" +# env: +# clear: +# MYSQL_ROOT_HOST: '%' +# secret: +# - MYSQL_ROOT_PASSWORD +# files: +# - config/mysql/production.cnf:/etc/mysql/my.cnf +# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql +# directories: +# - data:/var/lib/mysql +# redis: +# image: redis:7.0 +# host: 192.168.0.2 +# port: 6379 +# directories: +# - data:/data diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..cac5315775 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000000..1a0bf09e40 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,76 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Configure 'rails notes' to inspect Cucumber files + config.annotations.register_directories('features') + config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "localhost", port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000000..bdcd01d1bf --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,90 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + config.cache_store = :solid_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + config.active_job.queue_adapter = :solid_queue + config.solid_queue.connects_to = { database: { writing: :queue } } + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000000..e6b5c1b020 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,57 @@ +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Configure 'rails notes' to inspect Cucumber files + config.annotations.register_directories('features') + config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/config/importmap.rb b/config/importmap.rb new file mode 100644 index 0000000000..909dfc542d --- /dev/null +++ b/config/importmap.rb @@ -0,0 +1,7 @@ +# Pin npm packages by running ./bin/importmap + +pin "application" +pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@hotwired/stimulus", to: "stimulus.min.js" +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 0000000000..487324424f --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000000..b3076b38fe --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000000..c0b717f7ec --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000000..3860f659ea --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000000..6c349ae5e3 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,31 @@ +# Files in the config/locales directory are used for internationalization and +# are automatically loaded by Rails. If you want to use locales other than +# English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more about the API, please read the Rails Internationalization guide +# at https://guides.rubyonrails.org/i18n.html. +# +# Be aware that YAML interprets the following case-insensitive strings as +# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings +# must be quoted to be interpreted as strings. For example: +# +# en: +# "yes": yup +# enabled: "ON" + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb new file mode 100644 index 0000000000..a248513b24 --- /dev/null +++ b/config/puma.rb @@ -0,0 +1,41 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/config/queue.yml b/config/queue.yml new file mode 100644 index 0000000000..9eace59c41 --- /dev/null +++ b/config/queue.yml @@ -0,0 +1,18 @@ +default: &default + dispatchers: + - polling_interval: 1 + batch_size: 500 + workers: + - queues: "*" + threads: 3 + processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> + polling_interval: 0.1 + +development: + <<: *default + +test: + <<: *default + +production: + <<: *default diff --git a/config/recurring.yml b/config/recurring.yml new file mode 100644 index 0000000000..b4207f9b07 --- /dev/null +++ b/config/recurring.yml @@ -0,0 +1,15 @@ +# examples: +# periodic_cleanup: +# class: CleanSoftDeletedRecordsJob +# queue: background +# args: [ 1000, { batch_size: 500 } ] +# schedule: every hour +# periodic_cleanup_with_command: +# command: "SoftDeletedRecord.due.delete_all" +# priority: 2 +# schedule: at 5am every day + +production: + clear_solid_queue_finished_jobs: + command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)" + schedule: every hour at minute 12 diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..48254e88ed --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,14 @@ +Rails.application.routes.draw do + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html + + # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. + # Can be used by load balancers and uptime monitors to verify that the app is live. + get "up" => "rails/health#show", as: :rails_health_check + + # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb) + # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest + # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker + + # Defines the root path route ("/") + # root "posts#index" +end diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000000..4942ab6694 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/db/cable_schema.rb b/db/cable_schema.rb new file mode 100644 index 0000000000..23666604a5 --- /dev/null +++ b/db/cable_schema.rb @@ -0,0 +1,11 @@ +ActiveRecord::Schema[7.1].define(version: 1) do + create_table "solid_cable_messages", force: :cascade do |t| + t.binary "channel", limit: 1024, null: false + t.binary "payload", limit: 536870912, null: false + t.datetime "created_at", null: false + t.integer "channel_hash", limit: 8, null: false + t.index ["channel"], name: "index_solid_cable_messages_on_channel" + t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash" + t.index ["created_at"], name: "index_solid_cable_messages_on_created_at" + end +end diff --git a/db/cache_schema.rb b/db/cache_schema.rb new file mode 100644 index 0000000000..81a410d188 --- /dev/null +++ b/db/cache_schema.rb @@ -0,0 +1,12 @@ +ActiveRecord::Schema[7.2].define(version: 1) do + create_table "solid_cache_entries", force: :cascade do |t| + t.binary "key", limit: 1024, null: false + t.binary "value", limit: 536870912, null: false + t.datetime "created_at", null: false + t.integer "key_hash", limit: 8, null: false + t.integer "byte_size", limit: 4, null: false + t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size" + t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size" + t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true + end +end diff --git a/db/queue_schema.rb b/db/queue_schema.rb new file mode 100644 index 0000000000..85194b6a88 --- /dev/null +++ b/db/queue_schema.rb @@ -0,0 +1,129 @@ +ActiveRecord::Schema[7.1].define(version: 1) do + create_table "solid_queue_blocked_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.string "concurrency_key", null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.index [ "concurrency_key", "priority", "job_id" ], name: "index_solid_queue_blocked_executions_for_release" + t.index [ "expires_at", "concurrency_key" ], name: "index_solid_queue_blocked_executions_for_maintenance" + t.index [ "job_id" ], name: "index_solid_queue_blocked_executions_on_job_id", unique: true + end + + create_table "solid_queue_claimed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.bigint "process_id" + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_claimed_executions_on_job_id", unique: true + t.index [ "process_id", "job_id" ], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id" + end + + create_table "solid_queue_failed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.text "error" + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_failed_executions_on_job_id", unique: true + end + + create_table "solid_queue_jobs", force: :cascade do |t| + t.string "queue_name", null: false + t.string "class_name", null: false + t.text "arguments" + t.integer "priority", default: 0, null: false + t.string "active_job_id" + t.datetime "scheduled_at" + t.datetime "finished_at" + t.string "concurrency_key" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "active_job_id" ], name: "index_solid_queue_jobs_on_active_job_id" + t.index [ "class_name" ], name: "index_solid_queue_jobs_on_class_name" + t.index [ "finished_at" ], name: "index_solid_queue_jobs_on_finished_at" + t.index [ "queue_name", "finished_at" ], name: "index_solid_queue_jobs_for_filtering" + t.index [ "scheduled_at", "finished_at" ], name: "index_solid_queue_jobs_for_alerting" + end + + create_table "solid_queue_pauses", force: :cascade do |t| + t.string "queue_name", null: false + t.datetime "created_at", null: false + t.index [ "queue_name" ], name: "index_solid_queue_pauses_on_queue_name", unique: true + end + + create_table "solid_queue_processes", force: :cascade do |t| + t.string "kind", null: false + t.datetime "last_heartbeat_at", null: false + t.bigint "supervisor_id" + t.integer "pid", null: false + t.string "hostname" + t.text "metadata" + t.datetime "created_at", null: false + t.string "name", null: false + t.index [ "last_heartbeat_at" ], name: "index_solid_queue_processes_on_last_heartbeat_at" + t.index [ "name", "supervisor_id" ], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true + t.index [ "supervisor_id" ], name: "index_solid_queue_processes_on_supervisor_id" + end + + create_table "solid_queue_ready_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_ready_executions_on_job_id", unique: true + t.index [ "priority", "job_id" ], name: "index_solid_queue_poll_all" + t.index [ "queue_name", "priority", "job_id" ], name: "index_solid_queue_poll_by_queue" + end + + create_table "solid_queue_recurring_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "task_key", null: false + t.datetime "run_at", null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_recurring_executions_on_job_id", unique: true + t.index [ "task_key", "run_at" ], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true + end + + create_table "solid_queue_recurring_tasks", force: :cascade do |t| + t.string "key", null: false + t.string "schedule", null: false + t.string "command", limit: 2048 + t.string "class_name" + t.text "arguments" + t.string "queue_name" + t.integer "priority", default: 0 + t.boolean "static", default: true, null: false + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "key" ], name: "index_solid_queue_recurring_tasks_on_key", unique: true + t.index [ "static" ], name: "index_solid_queue_recurring_tasks_on_static" + end + + create_table "solid_queue_scheduled_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "scheduled_at", null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true + t.index [ "scheduled_at", "priority", "job_id" ], name: "index_solid_queue_dispatch_all" + end + + create_table "solid_queue_semaphores", force: :cascade do |t| + t.string "key", null: false + t.integer "value", default: 1, null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "expires_at" ], name: "index_solid_queue_semaphores_on_expires_at" + t.index [ "key", "value" ], name: "index_solid_queue_semaphores_on_key_and_value" + t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true + end + + add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000000..4fbd6ed970 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,9 @@ +# This file should ensure the existence of records required to run the application in every environment (production, +# development, test). The code here should be idempotent so that it can be executed at any point in every environment. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Example: +# +# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| +# MovieGenre.find_or_create_by!(name: genre_name) +# end diff --git a/features/importar_dados_sigaa.feature b/features/importar_dados_sigaa.feature new file mode 100644 index 0000000000..04a13e0cdb --- /dev/null +++ b/features/importar_dados_sigaa.feature @@ -0,0 +1,24 @@ +Feature: Importar dados do SIGAA + Como Administrador + Quero importar dados de turmas, matérias e participantes do SIGAA + A fim de alimentar a base de dados do sistema + + Background: + Given que estou autenticado como administrador + + Scenario: Importação bem-sucedida (feliz) + Given que existem turmas, matérias e participantes no SIGAA + When eu realizo a importação de dados + Then os dados devem ser armazenados na base do sistema + And eu devo ver a mensagem "Importação concluída com sucesso" + + Scenario: Dados já existentes na base (triste) + Given que alguns dados do SIGAA já existem na base local + When eu tento importar novamente + Then o sistema deve ignorar os registros duplicados + And eu devo ver a mensagem "Nenhum dado novo foi importado" + + Scenario: Erro de conexão com o SIGAA (triste) + Given que o SIGAA está indisponível + When eu tento realizar a importação + Then o sistema deve exibir a mensagem "Erro ao conectar com o SIGAA" diff --git a/features/step_definitions/.keep b/features/step_definitions/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000000..3b97d14087 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,53 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +require 'cucumber/rails' + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false + +# Remove/comment out the lines below if your app doesn't have a database. +# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. +begin + DatabaseCleaner.strategy = :transaction +rescue NameError + raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +end + +# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. +# See the DatabaseCleaner documentation for details. Example: +# +# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do +# # { except: [:widgets] } may not do what you expect here +# # as Cucumber::Rails::Database.javascript_strategy overrides +# # this setting. +# DatabaseCleaner.strategy = :truncation +# end +# +# Before('not @no-txn', 'not @selenium', 'not @culerity', 'not @celerity', 'not @javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# + +# Possible values are :truncation and :transaction +# The :transaction strategy is faster, but might give you threading problems. +# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature +Cucumber::Rails::Database.javascript_strategy = :truncation diff --git a/lib/tasks/.keep b/lib/tasks/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000000..0caa4d2553 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,69 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({ok: 'test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({wip: 'test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({rerun: 'test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task all: [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + + end + + desc 'Alias for cucumber:ok' + task cucumber: 'cucumber:ok' + + task default: :cucumber + + task features: :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon. + task 'test:prepare' do + end + + task stats: 'cucumber:statsetup' + + +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/log/.keep b/log/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/400.html b/public/400.html new file mode 100644 index 0000000000..282dbc8cc9 --- /dev/null +++ b/public/400.html @@ -0,0 +1,114 @@ + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000000..c0670bc877 --- /dev/null +++ b/public/404.html @@ -0,0 +1,114 @@ + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/406-unsupported-browser.html b/public/406-unsupported-browser.html new file mode 100644 index 0000000000..9532a9ccd0 --- /dev/null +++ b/public/406-unsupported-browser.html @@ -0,0 +1,114 @@ + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000000..8bcf06014f --- /dev/null +++ b/public/422.html @@ -0,0 +1,114 @@ + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000000..d77718c3a4 --- /dev/null +++ b/public/500.html @@ -0,0 +1,114 @@ + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/icon.png b/public/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c9dbfbbd2f7c1421ffd5727188146213abbcef GIT binary patch literal 4166 zcmd6qU;WFw?|v@m)Sk^&NvB8tcujdV-r1b=i(NJxn&7{KTb zX$3(M+3TP2o^#KAo{#tIjl&t~(8D-k004kqPglzn0HFG(Q~(I*AKsD#M*g7!XK0T7 zN6P7j>HcT8rZgKl$v!xr806dyN19Bd4C0x_R*I-a?#zsTvb_89cyhuC&T**i|Rc zq5b8M;+{8KvoJ~uj9`u~d_f6`V&3+&ZX9x5pc8s)d175;@pjm(?dapmBcm0&vl9+W zx1ZD2o^nuyUHWj|^A8r>lUorO`wFF;>9XL-Jy!P}UXC{(z!FO%SH~8k`#|9;Q|eue zqWL0^Bp(fg_+Pkm!fDKRSY;+^@BF?AJE zCUWpXPst~hi_~u)SzYBDZroR+Z4xeHIlm_3Yc_9nZ(o_gg!jDgVa=E}Y8uDgem9`b zf=mfJ_@(BXSkW53B)F2s!&?_R4ptb1fYXlF++@vPhd=marQgEGRZS@B4g1Mu?euknL= z67P~tZ?*>-Hmi7GwlisNHHJDku-dSm7g@!=a}9cSL6Pa^w^2?&?$Oi8ibrr>w)xqx zOH_EMU@m05)9kuNR>>4@H%|){U$^yvVQ(YgOlh;5oU_-vivG-p4=LrN-k7D?*?u1u zsWly%tfAzKd6Fb=`eU2un_uaTXmcT#tlOL+aRS=kZZf}A7qT8lvcTx~7j` z*b>=z)mwg7%B2_!D0!1IZ?Nq{^Y$uI4Qx*6T!E2Col&2{k?ImCO=dD~A&9f9diXy^$x{6CwkBimn|1E09 zAMSezYtiL?O6hS37KpvDM?22&d{l)7h-!F)C-d3j8Z`c@($?mfd{R82)H>Qe`h{~G z!I}(2j(|49{LR?w4Jspl_i!(4T{31|dqCOpI52r5NhxYV+cDAu(xp*4iqZ2e-$YP= zoFOPmm|u*7C?S{Fp43y+V;>~@FFR76bCl@pTtyB93vNWy5yf;HKr8^0d7&GVIslYm zo3Tgt@M!`8B6IW&lK{Xk>%zp41G%`(DR&^u z5^pwD4>E6-w<8Kl2DzJ%a@~QDE$(e87lNhy?-Qgep!$b?5f7+&EM7$e>|WrX+=zCb z=!f5P>MxFyy;mIRxjc(H*}mceXw5a*IpC0PEYJ8Y3{JdoIW)@t97{wcUB@u+$FCCO z;s2Qe(d~oJC^`m$7DE-dsha`glrtu&v&93IZadvl_yjp!c89>zo;Krk+d&DEG4?x$ zufC1n+c1XD7dolX1q|7}uelR$`pT0Z)1jun<39$Sn2V5g&|(j~Z!wOddfYiZo7)A< z!dK`aBHOOk+-E_xbWCA3VR-+o$i5eO9`rMI#p_0xQ}rjEpGW;U!&&PKnivOcG(|m9 z!C8?WC6nCXw25WVa*eew)zQ=h45k8jSIPbq&?VE{oG%?4>9rwEeB4&qe#?-y_es4c|7ufw%+H5EY#oCgv!Lzv291#-oNlX~X+Jl5(riC~r z=0M|wMOP)Tt8@hNg&%V@Z9@J|Q#K*hE>sr6@oguas9&6^-=~$*2Gs%h#GF@h)i=Im z^iKk~ipWJg1VrvKS;_2lgs3n1zvNvxb27nGM=NXE!D4C!U`f*K2B@^^&ij9y}DTLB*FI zEnBL6y{jc?JqXWbkIZd7I16hA>(f9T!iwbIxJj~bKPfrO;>%*5nk&Lf?G@c2wvGrY&41$W{7HM9+b@&XY@>NZM5s|EK_Dp zQX60CBuantx>|d#DsaZ*8MW(we|#KTYZ=vNa#d*DJQe6hr~J6{_rI#?wi@s|&O}FR zG$kfPxheXh1?IZ{bDT-CWB4FTvO-k5scW^mi8?iY5Q`f8JcnnCxiy@m@D-%lO;y0pTLhh6i6l@x52j=#^$5_U^os}OFg zzdHbo(QI`%9#o*r8GCW~T3UdV`szO#~)^&X_(VW>o~umY9-ns9-V4lf~j z`QBD~pJ4a#b`*6bJ^3RS5y?RAgF7K5$ll97Y8#WZduZ`j?IEY~H(s^doZg>7-tk*t z4_QE1%%bb^p~4F5SB$t2i1>DBG1cIo;2(xTaj*Y~hlM{tSDHojL-QPg%Mo%6^7FrpB*{ z4G0@T{-77Por4DCMF zB_5Y~Phv%EQ64W8^GS6h?x6xh;w2{z3$rhC;m+;uD&pR74j+i22P5DS-tE8ABvH(U~indEbBUTAAAXfHZg5QpB@TgV9eI<)JrAkOI z8!TSOgfAJiWAXeM&vR4Glh;VxH}WG&V$bVb`a`g}GSpwggti*&)taV1@Ak|{WrV|5 zmNYx)Ans=S{c52qv@+jmGQ&vd6>6yX6IKq9O$3r&0xUTdZ!m1!irzn`SY+F23Rl6# zFRxws&gV-kM1NX(3(gnKpGi0Q)Dxi~#?nyzOR9!en;Ij>YJZVFAL*=R%7y%Mz9hU% zs>+ZB?qRmZ)nISx7wxY)y#cd$iaC~{k0avD>BjyF1q^mNQ1QcwsxiTySe<6C&cC6P zE`vwO9^k-d`9hZ!+r@Jnr+MF*2;2l8WjZ}DrwDUHzSF{WoG zucbSWguA!3KgB3MU%HH`R;XqVv0CcaGq?+;v_A5A2kpmk5V%qZE3yzQ7R5XWhq=eR zyUezH=@V)y>L9T-M-?tW(PQYTRBKZSVb_!$^H-Pn%ea;!vS_?M<~Tm>_rWIW43sPW z=!lY&fWc1g7+r?R)0p8(%zp&vl+FK4HRkns%BW+Up&wK8!lQ2~bja|9bD12WrKn#M zK)Yl9*8$SI7MAwSK$%)dMd>o+1UD<2&aQMhyjS5R{-vV+M;Q4bzl~Z~=4HFj_#2V9 zB)Gfzx3ncy@uzx?yzi}6>d%-?WE}h7v*w)Jr_gBl!2P&F3DX>j_1#--yjpL%<;JMR z*b70Gr)MMIBWDo~#<5F^Q0$VKI;SBIRneuR7)yVsN~A9I@gZTXe)E?iVII+X5h0~H zx^c(fP&4>!*q>fb6dAOC?MI>Cz3kld#J*;uik+Ps49cwm1B4 zZc1|ZxYyTv;{Z!?qS=D)sgRKx^1AYf%;y_V&VgZglfU>d+Ufk5&LV$sKv}Hoj+s; xK3FZRYdhbXT_@RW*ff3@`D1#ps#~H)p+y&j#(J|vk^lW{fF9OJt5(B-_&*Xgn9~3N literal 0 HcmV?d00001 diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000000..04b34bf83f --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000..c19f78ab68 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/script/.keep b/script/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/storage/.keep b/storage/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb new file mode 100644 index 0000000000..cee29fd214 --- /dev/null +++ b/test/application_system_test_case.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] +end diff --git a/test/controllers/.keep b/test/controllers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/files/.keep b/test/fixtures/files/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/helpers/.keep b/test/helpers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/integration/.keep b/test/integration/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/mailers/.keep b/test/mailers/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/models/.keep b/test/models/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/system/.keep b/test/system/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..0c22470ec1 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,15 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" + +module ActiveSupport + class TestCase + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) + + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + # Add more helper methods to be used by all tests here... + end +end diff --git a/tmp/.keep b/tmp/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tmp/pids/.keep b/tmp/pids/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tmp/storage/.keep b/tmp/storage/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/.keep b/vendor/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/javascript/.keep b/vendor/javascript/.keep new file mode 100644 index 0000000000..e69de29bb2 From 66d2d9e998c4575de7c1fd5bac4be9f5e479739b Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 17 Nov 2025 14:27:49 -0300 Subject: [PATCH 03/63] feat: adicao do BDD da issue Edicao e Delecao de Templates --- features/edicao_e_delecao_templates.feature | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 features/edicao_e_delecao_templates.feature diff --git a/features/edicao_e_delecao_templates.feature b/features/edicao_e_delecao_templates.feature new file mode 100644 index 0000000000..2aee533b68 --- /dev/null +++ b/features/edicao_e_delecao_templates.feature @@ -0,0 +1,38 @@ +Feature: Edição e deleção de templates + Como Administrador + Quero editar e/ou deletar um template que eu criei sem afetar os formulários já criados + A fim de organizar os templates existentes + + Background: + Given que estou autenticado como administrador + And existe um template criado previamente + + Scenario: Editar o nome de um template com sucesso (feliz) + When eu edito o template alterando seu nome para "Template Atualizado" + Then o template deve ser salvo com o novo nome + And os formulários já criados anteriormente não devem ser modificados + And eu devo ver a mensagem "Template atualizado com sucesso" + + Scenario: Editar as perguntas de um template com sucesso (feliz) + When eu edito o template alterando suas perguntas + Then o template deve ser salvo com as novas perguntas + And os formulários já criados anteriormente não devem ser modificados + And eu devo ver a mensagem "Template atualizado com sucesso" + + Scenario: Deletar um template com sucesso (feliz) + When eu deleto o template + Then o template deve ser removido do sistema + And os formulários já criados anteriormente devem permanecer intactos + And eu devo ver a mensagem "Template excluído com sucesso" + + Scenario: Tentar editar um template que está em uso por um formulário ativo (triste) + Given que o template está vinculado a um formulário ativo + When eu tento editar o template + Then a alteração deve ser bloqueada + And eu devo ver a mensagem "Não é possível editar um template em uso" + + Scenario: Tentar deletar um template que está em uso por um formulário ativo (triste) + Given que o template está vinculado a um formulário ativo + When eu tento deletar o template + Then a remoção deve ser bloqueada + And eu devo ver a mensagem "Não é possível remover um template em uso" From e865c124d734a6a9efc4d9c8d4b8345972c0465d Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 17 Nov 2025 14:47:13 -0300 Subject: [PATCH 04/63] feat: adicao do BDD da issue Atualizacao de Dados no SIGAA --- features/atualizar_dados_sigaa.feature | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 features/atualizar_dados_sigaa.feature diff --git a/features/atualizar_dados_sigaa.feature b/features/atualizar_dados_sigaa.feature new file mode 100644 index 0000000000..1eb2d4d4bc --- /dev/null +++ b/features/atualizar_dados_sigaa.feature @@ -0,0 +1,43 @@ +Feature: Atualização de Dados do SIGAA + Como Administrador + Quero atualizar a base de dados já existente com os dados atuais do SIGAA + A fim de corrigir a base de dados do sistema + + Background: + Given que estou autenticado como administrador + + Scenario: Atualização bem-sucedida (dados novos e modificados) (feliz) + Given que a base local possui a "Turma A" com o aluno "João Silva" + And o SIGAA possui a "Turma A" com o nome do aluno atualizado para "João Silva Santos" + And o SIGAA também possui o novo aluno "Maria Costa" na "Turma A" + When eu realizo a atualização de dados + Then o nome do aluno na base local deve ser atualizado para "João Silva Santos" + And a aluna "Maria Costa" deve ser adicionada à "Turma A" na base local + And eu devo ver a mensagem "Atualização concluída com sucesso" + + Scenario: Correção de dados (remoção de dados antigos) (feliz) + Given que a base local possui o aluno "Carlos Souza" na "Turma B" + And o aluno "Carlos Souza" não está mais listado na "Turma B" no SIGAA + When eu realizo a atualização de dados + Then o aluno "Carlos Souza" deve ser removido da "Turma B" na base local + And eu devo ver a mensagem "Atualização concluída com sucesso" + + Scenario: Dados já estão atualizados (triste) + Given que os dados da base local já estão idênticos aos dados do SIGAA + When eu realizo a atualização de dados + Then o sistema não deve realizar nenhuma alteração + And eu devo ver a mensagem "A base de dados já está atualizada" + + Scenario: Erro de conexão com o SIGAA (triste) + Given que o SIGAA está indisponível + When eu tento realizar a atualização + Then o sistema deve exibir a mensagem "Erro ao conectar com o SIGAA" + And nenhuma alteração deve ser feita na base local + + Scenario: Fonte de dados não encontrada no SIGAA (triste) + Given que a base local está configurada para atualizar a "Turma de Engenharia 2020" + And a "Turma de Engenharia 2020" foi permanentemente excluída do SIGAA + When eu realizo a atualização de dados + Then o sistema deve falhar ao buscar os dados + And eu devo ver a mensagem "Erro: A 'Turma de Engenharia 2020' não foi encontrada no SIGAA. A atualização foi cancelada." + And nenhuma alteração deve ser feita na base local \ No newline at end of file From dc175af7c2f57c4a7bf96559f2fed70ea20965eb Mon Sep 17 00:00:00 2001 From: Bernardo Date: Mon, 17 Nov 2025 19:33:46 -0300 Subject: [PATCH 05/63] =?UTF-8?q?Issues=20implementadas:=20Definir=20senha?= =?UTF-8?q?=20ao=20cadastrar,=20exportar=20relat=C3=B3rios=20do=20administ?= =?UTF-8?q?rador,=20gerenciamento=20de=20departamento=20e=20redefinir=20se?= =?UTF-8?q?nha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/definir_senha_cadastro.feature | 40 +++++++++++++++++++ features/exportar_relatorios_csv.feature | 34 ++++++++++++++++ .../gerenciar_turmas_departamento.feature | 32 +++++++++++++++ features/redefinir_senha.feature | 38 ++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 features/definir_senha_cadastro.feature create mode 100644 features/exportar_relatorios_csv.feature create mode 100644 features/gerenciar_turmas_departamento.feature create mode 100644 features/redefinir_senha.feature diff --git a/features/definir_senha_cadastro.feature b/features/definir_senha_cadastro.feature new file mode 100644 index 0000000000..153affd8fd --- /dev/null +++ b/features/definir_senha_cadastro.feature @@ -0,0 +1,40 @@ +Feature: Definir senha após cadastro + Como Usuário + Quero definir uma senha para o meu usuário a partir do email do sistema de solicitação de cadastro + A fim de acessar o sistema + + Background: + Given que recebi um email de convite para cadastro + And o email contém um link de ativação + + Scenario: Definir senha com sucesso (feliz) + Given que clico no link de ativação do email + When eu sou redirecionado para a página de definição de senha + And eu preencho uma senha segura + And eu confirmo a senha + And eu clico em "Definir senha" + Then a minha senha deve ser criada + And eu devo receber a mensagem "Senha definida com sucesso" + And eu devo ser redirecionado para login + + Scenario: Senhas não conferem (triste) + Given que estou na página de definição de senha + When eu preencho a senha "Senha123!" + And eu confirmo com uma senha diferente "Senha456!" + And eu clico em "Definir senha" + Then o sistema deve exibir a mensagem "As senhas não conferem" + And a senha não deve ser criada + + Scenario: Senha muito fraca (triste) + Given que estou na página de definição de senha + When eu preencho uma senha fraca "123" + And eu confirmo a mesma senha + And eu clico em "Definir senha" + Then o sistema deve exibir a mensagem "Senha fraca. Use letras, números e caracteres especiais" + And a senha não deve ser criada + + Scenario: Link de ativação expirado (triste) + Given que o link de ativação expirou + When eu clico no link + Then o sistema deve exibir a mensagem "Link de ativação expirado" + And eu devo ser orientado a solicitar um novo link \ No newline at end of file diff --git a/features/exportar_relatorios_csv.feature b/features/exportar_relatorios_csv.feature new file mode 100644 index 0000000000..ef7f22c047 --- /dev/null +++ b/features/exportar_relatorios_csv.feature @@ -0,0 +1,34 @@ +Feature: Baixar resultados de formulário em CSV + Como Administrador + Quero baixar um arquivo CSV contendo os resultados de um formulário + A fim de avaliar o desempenho das turmas + + Background: + Given que estou autenticado como administrador + And que existe um formulário com respostas + + Scenario: Exportar resultados com sucesso (feliz) + Given que um formulário tem respostas de participantes + When eu clico em "Exportar para CSV" + Then um arquivo CSV deve ser gerado + And o arquivo deve conter todas as respostas + And o arquivo deve estar pronto para download + + Scenario: Arquivo CSV com estrutura correta (feliz) + Given que vou exportar resultados de um formulário + When eu clico em "Exportar para CSV" + Then o arquivo deve ter colunas com: ID da resposta, Participante, Data de resposta, Respostas + And cada linha deve representar uma resposta individual + And caracteres especiais devem estar codificados corretamente + + Scenario: Exportar formulário sem respostas (triste) + Given que um formulário não possui respostas + When eu tento exportar para CSV + Then o sistema deve exibir a mensagem "Não há dados para exportar" + And nenhum arquivo deve ser criado + + Scenario: Exportar apenas respostas de uma turma específica (feliz) + Given que um formulário foi respondido por múltiplas turmas + When eu seleciono uma turma específica + And eu clico em "Exportar para CSV" + Then o arquivo deve conter apenas as respostas da turma selecionada \ No newline at end of file diff --git a/features/gerenciar_turmas_departamento.feature b/features/gerenciar_turmas_departamento.feature new file mode 100644 index 0000000000..25e9ee59a6 --- /dev/null +++ b/features/gerenciar_turmas_departamento.feature @@ -0,0 +1,32 @@ +Feature: Gerenciar turmas apenas do departamento + Como Administrador + Quero gerenciar somente as turmas do departamento o qual eu pertenço + A fim de avaliar o desempenho das turmas no semestre atual + + Background: + Given que estou autenticado como administrador + And que pertenço ao departamento "Engenharia de Computação" + + Scenario: Visualizar apenas turmas do departamento (feliz) + Given que existem turmas de diferentes departamentos + When eu acesso a listagem de turmas + Then eu devo ver apenas as turmas do "Departamento de Engenharia de Computação" + And eu não devo ver turmas de outros departamentos + + Scenario: Criar formulário apenas para turmas do departamento (feliz) + Given que estou criando um formulário + When eu seleciono um template + Then a listagem de turmas deve mostrar apenas turmas do meu departamento + And eu não devo ter a opção de selecionar turmas de outros departamentos + + Scenario: Administrador de outro departamento vê apenas suas turmas (feliz) + Given que um outro administrador pertence ao "Departamento de Engenharia Mecânica" + When ele acessa o sistema + Then ele deve ver apenas turmas do "Departamento de Engenharia Mecânica" + And não deve ver minhas turmas + + Scenario: Impossível atualizar turma de outro departamento (triste) + Given que estou autenticado como administrador + When eu tento acessar dados de uma turma de outro departamento + Then o sistema deve retornar erro 403 (Acesso Negado) + And a operação não deve ser permitida \ No newline at end of file diff --git a/features/redefinir_senha.feature b/features/redefinir_senha.feature new file mode 100644 index 0000000000..36e0803578 --- /dev/null +++ b/features/redefinir_senha.feature @@ -0,0 +1,38 @@ +Feature: Redefinir senha + Como Usuário + Quero redefinir uma senha para o meu usuário a partir do email recebido após a solicitação da troca de senha + A fim de recuperar o meu acesso ao sistema + + Background: + Given que estou na página de login + + Scenario: Solicitar redefinição de senha com sucesso (feliz) + Given que clico em "Esqueci minha senha" + When eu preencho meu email "usuario@example.com" + And eu clico em "Enviar link de recuperação" + Then um email deve ser enviado para "usuario@example.com" + And o email deve conter um link de redefinição + And eu devo ver a mensagem "Email de recuperação enviado" + + Scenario: Redefinir senha com novo link (feliz) + Given que recebi um email de recuperação de senha + And eu clico no link de recuperação + When eu sou redirecionado para a página de redefinição + And eu preencho a nova senha + And eu confirmo a nova senha + And eu clico em "Atualizar senha" + Then a minha senha deve ser atualizada + And eu devo receber a mensagem "Senha atualizada com sucesso" + + Scenario: Link de recuperação expirado (triste) + Given que o link de recuperação expirou + When eu clico no link + Then o sistema deve exibir a mensagem "Link expirado" + And eu devo ser orientado a solicitar um novo link + + Scenario: Usuário não encontrado (triste) + Given que estou na página de recuperação + When eu preencho um email inexistente "naoexisto@example.com" + And eu clico em "Enviar link de recuperação" + Then o sistema deve exibir a mensagem "Email não encontrado" + And nenhum email deve ser enviado \ No newline at end of file From 226e776c578cf12c095bd02012dc0cdc221ae242 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:02:20 -0300 Subject: [PATCH 06/63] Estrutura inicial do Markdown --- README.md | 664 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 658 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 12fe37f3d4..7df6655edb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,659 @@ -# CAMAAR -Sistema para avaliação de atividades acadêmicas remotas do CIC +# Wiki CAMAAR - Plataforma de Avaliação de Cursos -Integrantes Grupo 10: -- Bernardo Gomes Rodrigues (231034190) -- Isaac Silva (231025216) -- Maria Carolina Burgum Abreu Jorge (231013547) \ No newline at end of file +--- + +## Informações do Projeto + +| Item | Descrição | +|------|-----------| +| **Nome do Projeto** | CAMAAR - Plataforma de Avaliação de Cursos | +| **Disciplina** | Engenharia de Software | +| **Período** | 2025.2 | +| **Integrantes do Grupo** | Bernardo Gomes Rodrigues - 231034190
[Nome 2 - Matrícula 2]
[Nome 3 - Matrícula 3]
[Nome 4 - Matrícula 4] | + +--- + +## Papéis Scrum + +| Papel | Responsável | Matrícula | +|-------|-------------|-----------| +| **Scrum Master** | Bernardo Gomes Rodrigues | 231034190 | +| **Product Owner** | [Nome do Product Owner] | [Matrícula] | + +--- + +## Escopo do Projeto + +### Descrição Geral + +O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** integrada com o SIGAA (Sistema Integrado de Gestão de Atividades Acadêmicas) por meio de arquivos JSON. O sistema permite que administradores (coordenadores de cursos) criem formulários de avaliação que são respondidos por alunos e professores, gerando relatórios sobre o desempenho das disciplinas e infraestrutura. + +### Objetivos Principais + +- Automatizar o processo de coleta de feedback sobre disciplinas +- Integrar dados do SIGAA para alimentar a plataforma +- Permitir análise de dados através de exportação em CSV +- Fornecer controle de acesso baseado em departamento +- Facilitar a gestão de templates reutilizáveis de formulários + +### Stakeholders + +- **Coordenadores de Curso** (Administradores): Criam e gerenciam formulários +- **Professores**: Respondem questionários sobre infraestrutura e ementa +- **Alunos**: Avaliam disciplinas e professores +- **Sistema SIGAA**: Fornecedor de dados de turmas, disciplinas e participantes + +--- + +## Funcionalidades e Regras de Negócio + +### 1. Autenticação de Usuários (5 pontos) + +**Funcionalidades:** +- Login com email ou matrícula + senha +- Definir senha após convite de cadastro +- Recuperar acesso através de email (redefinir senha) + +**Regras de Negócio:** +- Senhas devem ter no mínimo 8 caracteres +- Senhas devem conter pelo menos: 1 letra maiúscula, 1 letra minúscula, 1 número e 1 caractere especial +- Link de ativação de conta válido por 48 horas +- Link de redefinição de senha válido por 24 horas +- Máximo de 5 tentativas de login falhadas antes de bloquear por 15 minutos +- Email de boas-vindas enviado automaticamente após criação de usuário +- Usuário deve alterar senha temporária no primeiro acesso + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 2. Importação de Dados do SIGAA (8 pontos) + +**Funcionalidades:** +- Importar turmas, disciplinas e participantes do SIGAA +- Atualizar dados existentes com informações atualizadas +- Manter histórico de importações +- Marcar turmas como inativas quando descontinuadas + +**Regras de Negócio:** +- Importação deve ser realizada apenas por administradores +- Registros duplicados são ignorados (validação por ID único do SIGAA) +- Ao importar novos usuários, criar contas com senha temporária +- Dados importados não devem sobrescrever informações customizadas no sistema +- Cada importação deve ser registrada com: data, hora, usuário responsável, quantidade de registros processados +- Participantes removidos do SIGAA devem ter acesso desativado +- Atualização de mudanças de turmas de alunos/professores +- Máximo 1 importação simultânea (evitar condições de corrida) + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 3. Gerenciamento de Templates de Formulários (5 pontos) + +**Funcionalidades:** +- Criar templates com múltiplos tipos de questões +- Visualizar templates criados +- Editar templates sem afetar formulários já criados +- Deletar templates sem afetar formulários existentes + +**Regras de Negócio:** +- Template deve ter no mínimo 1 questão +- Nome do template deve ser único por administrador +- Apenas o criador do template pode editá-lo (ou outro admin do mesmo departamento) +- Tipos de questões suportados: Múltipla escolha, Escala Likert, Texto aberto, Verdadeiro/Falso +- Edições em template afetam apenas novos formulários criados, não retroativamente +- Template deletado não afeta formulários já criados baseados nele +- Histórico de versões do template é mantido +- Administrador só pode gerenciar templates do seu departamento + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 4. Criação de Formulários (8 pontos) + +**Funcionalidades:** +- Criar formulário baseado em template +- Selecionar turmas para aplicar o formulário +- Escolher tipo de avaliador (docentes ou discentes) +- Definir data de abertura e fechamento +- Visualizar formulários criados + +**Regras de Negócio:** +- Formulário herda todas as questões do template no momento da criação +- Deve selecionar no mínimo 1 turma +- Deve escolher tipo de avaliador (obrigatório) +- Data de fechamento deve ser posterior à data de abertura +- Formulário não pode ser editado após criação (apenas deletado e recriado) +- Apenas turmas do departamento do administrador podem ser selecionadas +- Formulário ativo quando data atual está entre abertura e fechamento +- Mesmo formulário não pode ser respondido 2 vezes pelo mesmo participante +- Notificação automática enviada aos participantes quando formulário é criado + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 5. Resposta de Questionários (5 pontos) + +**Funcionalidades:** +- Visualizar formulários não respondidos +- Responder questionário +- Salvar progresso como rascunho +- Submeter respostas + +**Regras de Negócio:** +- Participante visualiza apenas formulários de turmas onde está matriculado +- Formulário deve estar dentro do período de abertura/fechamento para responder +- Todos os campos obrigatórios devem ser preenchidos antes de enviar +- Rascunho é salvo automaticamente a cada 30 segundos +- Participante pode editar respostas enquanto rascunho não foi enviado +- Uma vez enviado, não pode ser alterado +- Resposta não pode ser submetida duas vezes pelo mesmo participante +- Sistema valida tipo de resposta conforme tipo de questão + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 6. Exportação de Resultados (5 pontos) + +**Funcionalidades:** +- Exportar respostas em formato CSV +- Filtrar por turma específica +- Download do arquivo + +**Regras de Negócio:** +- Apenas administrador pode exportar resultados +- Arquivo CSV deve conter: ID da resposta, Participante, Data de resposta, Respostas individuais +- Formato CSV deve ser compatível com Excel e planilhas +- Caracteres especiais devem estar codificados corretamente (UTF-8) +- Arquivo só pode ser gerado se houver respostas +- Apenas respostas de formulários do departamento do admin são exportadas +- Nomes de participantes anonimizados por padrão (apenas matrícula mostrada) +- Arquivo gerado com timestamp: `formulario_[id]_[data_hora].csv` + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 7. Controle de Acesso por Departamento (5 pontos) + +**Funcionalidades:** +- Visualizar apenas turmas do departamento +- Criar formulários apenas para turmas do departamento +- Gerenciar templates apenas do departamento +- Controle de permissão em operações + +**Regras de Negócio:** +- Administrador só vê/gerencia turmas do seu departamento +- Administrador não pode criar formulário para turma de outro departamento +- Erro 403 (Acesso Negado) deve ser retornado para tentativas não autorizadas +- Super Admin pode gerenciar múltiplos departamentos (se aplicável) +- Departamento é definido no perfil do usuário durante importação do SIGAA +- Logs de tentativas de acesso não autorizado devem ser registrados + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 8. Diferenciação de Avaliadores (3 pontos) + +**Funcionalidades:** +- Escolher tipo de avaliador (docentes ou discentes) +- Exibir formulário apenas para o tipo correto + +**Regras de Negócio:** +- Ao criar formulário, obrigatório selecionar tipo: "Docentes" ou "Discentes" +- Aluno só vê formulários criados para "Discentes" +- Professor só vê formulários criados para "Docentes" +- Sistema valida tipo de usuário ao tentar acessar formulário +- Formulário para docente é enviado apenas para professores das turmas +- Formulário para discente é enviado apenas para alunos das turmas +- Mesmo formulário com dois tipos diferentes pode coexistir para mesma turma + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 9. Gestão de Usuários e Participantes (3 pontos) + +**Funcionalidades:** +- Cadastro automático de participantes na importação +- Desativação de participantes inativos +- Visualização de histórico de usuários + +**Regras de Negócio:** +- Usuários são criados automaticamente durante importação do SIGAA +- Usuário inativo não pode fazer login +- Quando participante é removido do SIGAA, status muda para inativo +- Email de boas-vindas é enviado automaticamente +- Senha temporária é gerada e enviada por email +- Usuário pode ter múltiplos papéis (aluno em uma turma, professor em outra, admin de disciplina) +- Histórico de mudanças de status é mantido + +**Responsável:** [Nome do desenvolvedor] + +--- + +## Atribuição de Histórias de Usuário + +| # | História de Usuário | Funcionalidade | Pontos | Responsável | +|---|---------------------|-----------------|--------|-------------| +| 1 | Importar dados do SIGAA | Importação de Dados | 8 | [Nome] | +| 2 | Responder questionário sobre turma | Resposta de Questionários | 5 | [Nome] | +| 3 | Cadastrar participantes ao importar | Gestão de Usuários | 3 | [Nome] | +| 4 | Baixar resultados em CSV | Exportação de Resultados | 5 | [Nome] | +| 5 | Criar template de formulário | Gerenciamento de Templates | 5 | [Nome] | +| 6 | Criar formulário baseado em template | Criação de Formulários | 8 | [Nome] | +| 7 | Acessar sistema com credenciais | Autenticação | 3 | [Nome] | +| 8 | Definir senha após cadastro | Autenticação | 2 | [Nome] | +| 9 | Gerenciar turmas do departamento | Controle de Acesso | 5 | [Nome] | +| 10 | Redefinir senha | Autenticação | 3 | [Nome] | +| 11 | Atualizar base de dados existente | Importação de Dados | 5 | [Nome] | +| 12 | Visualizar formulários não respondidos | Resposta de Questionários | 3 | [Nome] | +| 13 | Visualizar formulários criados | Criação de Formulários | 3 | [Nome] | +| 14 | Visualizar templates criados | Gerenciamento de Templates | 2 | [Nome] | +| 15 | Editar e deletar template | Gerenciamento de Templates | 3 | [Nome] | +| 16 | Escolher tipo de avaliador | Diferenciação de Avaliadores | 3 | [Nome] | + +**Total de Pontos: 62 pontos** + +--- + +## Histórias de Usuário Detalhadas com Pontuação + +### HU-01: Importar dados do SIGAA (8 pontos) + +``` +Como Administrador +Quero importar dados de turmas, matérias e participantes do SIGAA +A fim de alimentar a base de dados do sistema + +Critérios de Aceitação: +- Sistema importa com sucesso turmas, disciplinas e participantes do SIGAA +- Dados duplicados são ignorados e registrados em relatório +- Novos usuários recebem email de boas-vindas +- Histórico de importação é salvo com timestamp +- Erro de conexão é tratado graciosamente +- Máximo 1 importação simultânea +``` + +**Pontos:** 8 | **Responsável:** [Nome] + +--- + +### HU-02: Responder questionário sobre turma (5 pontos) + +``` +Como Participante de uma turma +Quero responder o questionário sobre a turma em que estou matriculado +A fim de submeter minha avaliação da turma + +Critérios de Aceitação: +- Visualiza questionário da turma +- Preenche questões obrigatórias +- Salva como rascunho (auto-save a cada 30s) +- Submete respostas finais +- Não pode responder 2 vezes o mesmo formulário +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-03: Cadastrar participantes ao importar (3 pontos) + +``` +Como Administrador +Quero cadastrar participantes de turmas do SIGAA ao importar dados de usuários novos +A fim de que eles acessem o sistema CAMAAR + +Critérios de Aceitação: +- Novos usuários são criados automaticamente +- Cada usuário recebe email de boas-vindas +- Senha temporária é gerada +- Usuário deve alterar senha no primeiro acesso +- Usuários já existentes são associados às novas turmas +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-04: Baixar resultados em CSV (5 pontos) + +``` +Como Administrador +Quero baixar um arquivo CSV contendo os resultados de um formulário +A fim de avaliar o desempenho das turmas + +Critérios de Aceitação: +- Arquivo CSV é gerado com estrutura correta +- Colunas: ID, Participante, Data, Respostas +- Caracteres especiais codificados (UTF-8) +- Filtro por turma específica +- Anonimização de nomes (apenas matrícula) +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-05: Criar template de formulário (5 pontos) + +``` +Como Administrador +Quero criar um template de formulário contendo as questões +A fim de gerar formulários de avaliações reutilizáveis + +Critérios de Aceitação: +- Template com nome único +- Mínimo 1 questão obrigatória +- Suporta múltiplos tipos: múltipla escolha, Likert, texto, V/F +- Apenas criador pode editar +- Edições não afetam formulários já criados +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-06: Criar formulário baseado em template (8 pontos) + +``` +Como Administrador +Quero criar um formulário baseado em um template para turmas escolhidas +A fim de avaliar o desempenho das turmas no semestre atual + +Critérios de Aceitação: +- Seleciona template +- Escolhe múltiplas turmas (mínimo 1) +- Define datas de abertura/fechamento +- Escolhe tipo de avaliador (docentes/discentes) +- Formulário herda questões do template +- Notificação enviada aos participantes +``` + +**Pontos:** 8 | **Responsável:** [Nome] + +--- + +### HU-07: Acessar sistema com credenciais (3 pontos) + +``` +Como Usuário do sistema +Quero acessar o sistema utilizando email ou matrícula e senha +A fim de responder formulários ou gerenciar o sistema + +Critérios de Aceitação: +- Login com email funciona +- Login com matrícula funciona +- Validação de credenciais +- Redirecionamento para painel +- Bloqueio após 5 tentativas falhadas por 15 minutos +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-08: Definir senha após cadastro (2 pontos) + +``` +Como Usuário +Quero definir uma senha para o meu usuário a partir do email de cadastro +A fim de acessar o sistema + +Critérios de Aceitação: +- Link de ativação válido por 48 horas +- Senha deve cumprir critérios de segurança +- Senhas devem conferir +- Redirecionamento para login após sucesso +``` + +**Pontos:** 2 | **Responsável:** [Nome] + +--- + +### HU-09: Gerenciar turmas do departamento (5 pontos) + +``` +Como Administrador +Quero gerenciar somente as turmas do departamento o qual eu pertenço +A fim de avaliar o desempenho das turmas no semestre atual + +Critérios de Aceitação: +- Visualiza apenas turmas do departamento +- Criar formulário apenas com turmas do departamento +- Erro 403 para tentativas de acesso não autorizado +- Logs de tentativas não autorizadas +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-10: Redefinir senha (3 pontos) + +``` +Como Usuário +Quero redefinir uma senha a partir do email recebido +A fim de recuperar meu acesso ao sistema + +Critérios de Aceitação: +- Link de recuperação válido por 24 horas +- Validação de senha +- Email de confirmação enviado +- Redirecionamento para login +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-11: Atualizar base de dados existente (5 pontos) + +``` +Como Administrador +Quero atualizar a base de dados com dados atuais do SIGAA +A fim de corrigir a base de dados do sistema + +Critérios de Aceitação: +- Sincroniza dados existentes +- Adiciona novos dados +- Marca turmas como inativas +- Atualiza mudanças de turmas +- Mantém histórico de atualizações +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-12: Visualizar formulários não respondidos (3 pontos) + +``` +Como Participante de uma turma +Quero visualizar os formulários não respondidos das turmas em que estou matriculado +A fim de poder escolher qual irei responder + +Critérios de Aceitação: +- Lista apenas formulários não respondidos +- Mostra turma, disciplina, datas +- Filtra por turma +- Formulários respondidos não aparecem +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-13: Visualizar formulários criados (3 pontos) + +``` +Como Administrador +Quero visualizar os formulários criados +A fim de poder gerar um relatório a partir das respostas + +Critérios de Aceitação: +- Lista todos os formulários +- Mostra nome, template, turmas, status, data +- Quantidade de respostas exibida +- Acesso apenas a formulários do departamento +- Opção de visualizar detalhes +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-14: Visualizar templates criados (2 pontos) + +``` +Como Administrador +Quero visualizar os templates criados +A fim de poder editar e/ou deletar um template que eu criei + +Critérios de Aceitação: +- Lista todos os templates +- Mostra nome, quantidade de questões, data +- Acesso aos detalhes +- Opções de editar/deletar +``` + +**Pontos:** 2 | **Responsável:** [Nome] + +--- + +### HU-15: Editar e deletar template (3 pontos) + +``` +Como Administrador +Quero editar e/ou deletar um template que eu criei sem afetar formulários já criados +A fim de organizar os templates existentes + +Critérios de Aceitação: +- Editar template não afeta formulários existentes +- Deletar template não deleta formulários +- Confirmação de exclusão +- Apenas criador pode editar +- Histórico de versões mantido +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-16: Escolher tipo de avaliador (3 pontos) + +``` +Como Administrador +Quero escolher criar um formulário para os docentes ou os discentes de uma turma +A fim de avaliar o desempenho de uma matéria + +Critérios de Aceitação: +- Opção obrigatória: Docentes ou Discentes +- Professores veem apenas formulários para Docentes +- Alunos veem apenas formulários para Discentes +- Múltiplos formulários podem coexistir para mesma turma +- Notificação apenas para tipo correto +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +## Política de Branching + +### Estratégia Git Flow + +``` +- main: Produção (releases e hotfixes) +- develop: Desenvolvimento principal +- feature/HU-XX-descricao: Novas funcionalidades +- bugfix/HU-XX-descricao: Correções de bugs +- hotfix/issue-descricao: Correções urgentes em produção +``` + +### Fluxo de Trabalho + +1. **Iniciar Nova Funcionalidade:** + - Criação de branch para a funcionalidade + - Garantir que a nova branch está atualizada com as alterações da main + +2. **Trabalhar na Funcionalidade:** + - Commits descritivos em português + - Formato: `HU-01: Descrição da alteração` + - Exemplo: `HU-01: Implementar validação de duplicatas` + +3. **Pull Request para Develop:** + - Descrever as alterações + - Referenciar a HU + - Mínimo 1 aprovação antes de merge + - Rodar testes antes de merge + +### Convenção de Commits + +``` +(): + + +``` + +**Tipos:** +- `feat`: Nova funcionalidade (HU) +- `fix`: Correção de bug +- `docs`: Documentação +- `style`: Formatação +- `refactor`: Refatoração +- `test`: Testes +- `chore`: Tarefas de build/dependências + +**Exemplo:** +``` +feat(autenticacao): Implementar login com email e matrícula +``` + +--- + +## Métricas e Velocidade + +### Cálculo de Velocity + +**Sprint Atual:** +- Total de Pontos: **62 pontos** +- Histórias por Sprint: [A definir conforme organização] + +**Fórmula:** +``` +Velocity = Pontos Concluídos / Número de Sprints + +Exemplo (após 3 sprints): +Sprint 1: 21 pontos +Sprint 2: 18 pontos +Sprint 3: 20 pontos +Velocity Média = (21+18+20)/3 = 19.67 pontos/sprint +``` + +### Capacidade de Planning + +``` +Baseado na velocity média, para próxima sprint: +- Pontos a planejar ≈ Velocity média +- Buffer (20% do total) para imprevistos +- Ajustar conforme necessário +``` + +--- + +## Próximos Passos + +- [X] Preencher nomes e matrículas dos integrantes +- [ ] Definir duração de cada sprint +- [ ] Criar backlog com priorização +- [X] Configurar repositório Git +- [X] Configurar ambiente de desenvolvimento +- [X] Criar estrutura inicial do Rails +- [X] Iniciar Sprint 1 From 4c0216221f9cd306fb3e8d137ecdfd3635734295 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 17 Nov 2025 20:28:48 -0300 Subject: [PATCH 07/63] fix: correcao do BDD de Edicao e Delecao de Templates --- features/edicao_e_delecao_templates.feature | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/features/edicao_e_delecao_templates.feature b/features/edicao_e_delecao_templates.feature index 2aee533b68..8e9f41bb25 100644 --- a/features/edicao_e_delecao_templates.feature +++ b/features/edicao_e_delecao_templates.feature @@ -5,26 +5,35 @@ Feature: Edição e deleção de templates Background: Given que estou autenticado como administrador - And existe um template criado previamente + And existe um template com o nome "Template Turma Antiga" Scenario: Editar o nome de um template com sucesso (feliz) - When eu edito o template alterando seu nome para "Template Atualizado" + When eu clico para editar o template + And eu altero seu nome para "Template Turma Atualizada" Then o template deve ser salvo com o novo nome And os formulários já criados anteriormente não devem ser modificados And eu devo ver a mensagem "Template atualizado com sucesso" Scenario: Editar as perguntas de um template com sucesso (feliz) - When eu edito o template alterando suas perguntas + When eu clico para editar o template + And eu altero suas perguntas Then o template deve ser salvo com as novas perguntas And os formulários já criados anteriormente não devem ser modificados And eu devo ver a mensagem "Template atualizado com sucesso" Scenario: Deletar um template com sucesso (feliz) - When eu deleto o template + When eu clico para deletar o template Then o template deve ser removido do sistema And os formulários já criados anteriormente devem permanecer intactos And eu devo ver a mensagem "Template excluído com sucesso" + Scenario: Tentativa de salvar com nome em branco (triste) + Given que eu clico para editar o template + When eu apago o nome do template, deixando-o em branco + And eu salvo as alterações + Then eu devo ver a mensagem "Nome do template não pode ficar em branco" + And o template deve continuar com o nome "Template Turma Antiga" + Scenario: Tentar editar um template que está em uso por um formulário ativo (triste) Given que o template está vinculado a um formulário ativo When eu tento editar o template From 80a10912e06b3cce81c39add391f14b9b72e1630 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 17 Nov 2025 20:33:16 -0300 Subject: [PATCH 08/63] fix: correcao do BDD de Importar Dados do SIGAA --- features/importar_dados_sigaa.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/importar_dados_sigaa.feature b/features/importar_dados_sigaa.feature index 04a13e0cdb..c757d1f0f6 100644 --- a/features/importar_dados_sigaa.feature +++ b/features/importar_dados_sigaa.feature @@ -13,8 +13,8 @@ Feature: Importar dados do SIGAA And eu devo ver a mensagem "Importação concluída com sucesso" Scenario: Dados já existentes na base (triste) - Given que alguns dados do SIGAA já existem na base local - When eu tento importar novamente + Given que "Turma Engenharia de Software" do SIGAA já existem na base local + When eu tento importar novamente "Turma Engenharia de Software" Then o sistema deve ignorar os registros duplicados And eu devo ver a mensagem "Nenhum dado novo foi importado" From 7f57bc80b071c2c8bc0909c1d246d3bf7a93087f Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 17 Nov 2025 20:44:37 -0300 Subject: [PATCH 09/63] =?UTF-8?q?feat:=20adicao=20do=20BDD=20da=20issue=20?= =?UTF-8?q?Criar=20Template=20de=20Formul=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/criar_template_formulario.feature | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 features/criar_template_formulario.feature diff --git a/features/criar_template_formulario.feature b/features/criar_template_formulario.feature new file mode 100644 index 0000000000..d8e88ffa83 --- /dev/null +++ b/features/criar_template_formulario.feature @@ -0,0 +1,40 @@ +Feature: Criação de Template de Formulário + Como Administrador + Quero criar um template de formulário contendo as questões do formulário + A fim de gerar formulários de avaliações para avaliar o desempenho das turmas + + Background: + Given que estou autenticado como administrador + + Scenario: Criação de template bem-sucedida (feliz) + Given que eu estou na página de criação de templates + When eu preencho o nome do template com "Avaliação Padrão Semestral" + And eu adiciono a pergunta "O professor foi claro nas explicações?" + And eu adiciono a pergunta "Os materiais de aula foram úteis?" + And eu salvo o template + Then eu devo ver a mensagem "Template criado com sucesso" + And o "Avaliação Padrão Semestral" deve aparecer na lista de templates + + Scenario: Tentativa de criar template sem nome (triste) + Given que eu estou na página de criação de templates + When eu deixo o nome do template em branco + And eu adiciono a pergunta "O professor foi claro nas explicações?" + And eu tento salvar o template + Then eu devo ver a mensagem "Erro: O nome do template é obrigatório" + And o template não deve ser criado + + Scenario: Tentativa de criar template sem perguntas (triste) + Given que eu estou na página de criação de templates + When eu preencho o nome do template com "Template Vazio" + And eu NÃO adiciono nenhuma pergunta + And eu tento salvar o template + Then eu devo ver a mensagem "Erro: O template deve conter pelo menos uma pergunta" + And o template não deve ser criado + + Scenario: Tentativa de criar template com nome duplicado (triste) + Given que já existe um template com o nome "Avaliação Padrão Semestral" + When eu preencho o nome do template com "Avaliação Padrão Semestral" + And eu adiciono uma pergunta + And eu tento salvar o template + Then eu devo ver a mensagem "Erro: Já existe um template com este nome" + And o template não deve ser criado \ No newline at end of file From f222059aa97d24ee020030347f6f3ec87458737c Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 17 Nov 2025 20:53:54 -0300 Subject: [PATCH 10/63] feat: adicao do BDD da issue Visualizacao dos Templates Criados --- .../visualizacao_templates_criados.feature | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 features/visualizacao_templates_criados.feature diff --git a/features/visualizacao_templates_criados.feature b/features/visualizacao_templates_criados.feature new file mode 100644 index 0000000000..7c8728b7c4 --- /dev/null +++ b/features/visualizacao_templates_criados.feature @@ -0,0 +1,21 @@ +Feature: Visualização de Templates + Como Administrador + Quero visualizar os templates criados + A fim de poder editar e/ou deletar um template que eu criei + + Background: + Given que estou autenticado como administrador + + Scenario: Visualizar lista de templates existentes (feliz) + Given que existem os templates "Template Avaliação Semestral" e "Template Rascunho" cadastrados + When eu acesso a página de "Meus Templates" + Then eu devo ver "Template Avaliação Semestral" na lista + And eu devo ver um botão de "Editar" e "Deletar" ao lado de "Template Avaliação Semestral" + And eu devo ver "Template Rascunho" na lista + And eu devo ver um botão de "Editar" e "Deletar" ao lado de "Template Rascunho" + + Scenario: Visualizar quando não há templates cadastrados (triste/alternativo) + Given que nenhum template foi criado ainda + When eu acesso a página de "Meus Templates" + Then eu devo ver a mensagem "Nenhum template encontrado" + And eu devo ver um botão para "Criar Novo Template" \ No newline at end of file From b2bb055d019dd3c2cf47b5b1973bd9310c0e4632 Mon Sep 17 00:00:00 2001 From: Bernardo Date: Mon, 17 Nov 2025 21:04:53 -0300 Subject: [PATCH 11/63] issues implementadas: visualizar formularios criados, visualizar formularios para responder, formularios para docentes e discentes --- .../formularios_discentes_docentes.feature | 40 +++++++++++++++++++ .../visualizar_formularios_criados.feature | 31 ++++++++++++++ ...alizar_formularios_nao_respondidos.feature | 31 ++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 features/formularios_discentes_docentes.feature create mode 100644 features/visualizar_formularios_criados.feature create mode 100644 features/visualizar_formularios_nao_respondidos.feature diff --git a/features/formularios_discentes_docentes.feature b/features/formularios_discentes_docentes.feature new file mode 100644 index 0000000000..2accdc4f7d --- /dev/null +++ b/features/formularios_discentes_docentes.feature @@ -0,0 +1,40 @@ +Feature: Escolher criar formulário para docentes ou discentes + Como Administrador + Quero escolher criar um formulário para os docentes ou os dicentes de uma turma + A fim de avaliar o desempenho de uma matéria + + Background: + Given que estou autenticado como administrador + And que estou criando um novo formulário + + Scenario: Criar formulário para alunos (discentes) (feliz) + Given que estou na página de criar formulário + When eu seleciono um template + And eu escolho as turmas + And eu seleciono "Discentes" como tipo de avaliador + And eu clico em "Criar formulário" + Then o formulário deve ser criado apenas para alunos + And apenas alunos da turma devem receber notificação + + Scenario: Criar formulário para professores (docentes) (feliz) + Given que estou na página de criar formulário + When eu seleciono um template + And eu escolho as turmas + And eu seleciono "Docentes" como tipo de avaliador + And eu clico em "Criar formulário" + Then o formulário deve ser criado apenas para professores + And apenas professores da turma devem receber notificação + + Scenario: Avaliadores corretos veem apenas seus formulários (feliz) + Given que um formulário foi criado para "Discentes" + When um professor da turma faz login + Then ele não deve ver este formulário nos pendentes + And quando um aluno faz login, ele vê o formulário + + Scenario: Criar dois formulários diferentes para mesma turma (feliz) + Given que estou criando dois formulários para a mesma turma + When eu crio um formulário para "Discentes" + And eu crio outro formulário para "Docentes" + Then os dois formulários devem coexistir + And cada grupo receberá apenas o formulário destinado a ele +``` \ No newline at end of file diff --git a/features/visualizar_formularios_criados.feature b/features/visualizar_formularios_criados.feature new file mode 100644 index 0000000000..b8bbf4823a --- /dev/null +++ b/features/visualizar_formularios_criados.feature @@ -0,0 +1,31 @@ +Feature: Visualizar formulários criados + Como Administrador + Quero visualizar os formulários criados + A fim de poder gerar um relatório a partir das respostas + + Background: + Given que estou autenticado como administrador + + Scenario: Listar todos os formulários criados (feliz) + Given que criei vários formulários + When eu acesso a seção "Meus Formulários" + Then eu devo ver uma lista de todos os formulários + And cada formulário deve mostrar: nome, template usado, turmas, status, data de criação + + Scenario: Formulário mostra quantidade de respostas (feliz) + Given que um formulário foi respondido por alguns participantes + When eu visualizo a lista de formulários + Then cada formulário deve exibir a quantidade de respostas recebidas + And eu devo conseguir comparar o progresso entre formulários + + Scenario: Acessar detalhes do formulário (feliz) + Given que existe um formulário criado + When eu clico em um formulário + Then eu devo ver os detalhes: questões, data de abertura/fechamento + And eu devo ter a opção de visualizar respostas + + Scenario: Formulários apenas do meu departamento (feliz) + Given que administro apenas um departamento + When eu acesso a seção "Meus Formulários" + Then eu devo ver apenas os formulários criados para turmas do meu departamento + And eu não devo ver formulários de outros departamentos \ No newline at end of file diff --git a/features/visualizar_formularios_nao_respondidos.feature b/features/visualizar_formularios_nao_respondidos.feature new file mode 100644 index 0000000000..af689f404d --- /dev/null +++ b/features/visualizar_formularios_nao_respondidos.feature @@ -0,0 +1,31 @@ +Feature: Visualizar formulários não respondidos + Como Participante de uma turma + Quero visualizar os formulários não respondidos das turmas em que estou matriculado + A fim de poder escolher qual irei responder + + Background: + Given que estou autenticado como participante + And que estou matriculado em 3 turmas diferentes + + Scenario: Visualizar lista de formulários não respondidos (feliz) + Given que existem formulários disponíveis nas minhas turmas + When eu acesso a seção "Formulários Pendentes" + Then eu devo ver apenas os formulários que ainda não respondi + And cada formulário deve mostrar: turma, disciplina, data de abertura e fechamento + + Scenario: Formulários respondidos não aparecem na lista (feliz) + Given que já respondi um formulário + When eu acesso a seção "Formulários Pendentes" + Then o formulário que respondi não deve aparecer na lista + And apenas formulários novos ou em aberto devem ser mostrados + + Scenario: Nenhum formulário pendente (triste) + Given que não há formulários pendentes para responder + When eu acesso a seção "Formulários Pendentes" + Then eu devo ver a mensagem "Não há formulários pendentes" + + Scenario: Filtrar formulários por turma (feliz) + Given que existem formulários não respondidos de diferentes turmas + When eu seleciono uma turma específica + Then eu devo ver apenas os formulários daquela turma + And os formulários de outras turmas devem ser ocultados \ No newline at end of file From 8ef3fc1e87057e49b0f2d44e77bb6a3a05d82e91 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:26:42 -0300 Subject: [PATCH 12/63] Atualizando wiki do projeto --- README.md | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7df6655edb..7bac3c310e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ | **Nome do Projeto** | CAMAAR - Plataforma de Avaliação de Cursos | | **Disciplina** | Engenharia de Software | | **Período** | 2025.2 | -| **Integrantes do Grupo** | Bernardo Gomes Rodrigues - 231034190
[Nome 2 - Matrícula 2]
[Nome 3 - Matrícula 3]
[Nome 4 - Matrícula 4] | +| **Integrantes do Grupo** | Bernardo Gomes Rodrigues - 231034190
Isaac Silva - 231025216
Filipe Abadia Marcelino - 190087161
Maria Carolina Burgum Abreu Jorge - 231013547 | --- @@ -18,7 +18,7 @@ | Papel | Responsável | Matrícula | |-------|-------------|-----------| | **Scrum Master** | Bernardo Gomes Rodrigues | 231034190 | -| **Product Owner** | [Nome do Product Owner] | [Matrícula] | +| **Product Owner** | Maria Carolina Burgum Abreu Jorge | 231013547 | --- @@ -52,14 +52,12 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Funcionalidades:** - Login com email ou matrícula + senha - Definir senha após convite de cadastro +- Email de boas vindas informando a senha (se aplicável) - Recuperar acesso através de email (redefinir senha) **Regras de Negócio:** -- Senhas devem ter no mínimo 8 caracteres +- Senhas devem ter no mínimo 4 caracteres - Senhas devem conter pelo menos: 1 letra maiúscula, 1 letra minúscula, 1 número e 1 caractere especial -- Link de ativação de conta válido por 48 horas -- Link de redefinição de senha válido por 24 horas -- Máximo de 5 tentativas de login falhadas antes de bloquear por 15 minutos - Email de boas-vindas enviado automaticamente após criação de usuário - Usuário deve alterar senha temporária no primeiro acesso @@ -94,17 +92,15 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Funcionalidades:** - Criar templates com múltiplos tipos de questões - Visualizar templates criados -- Editar templates sem afetar formulários já criados -- Deletar templates sem afetar formulários existentes +- Editar templates +- Deletar templates **Regras de Negócio:** - Template deve ter no mínimo 1 questão - Nome do template deve ser único por administrador - Apenas o criador do template pode editá-lo (ou outro admin do mesmo departamento) -- Tipos de questões suportados: Múltipla escolha, Escala Likert, Texto aberto, Verdadeiro/Falso +- Tipos de questões suportados: Múltipla escolha, Texto aberto, Verdadeiro/Falso - Edições em template afetam apenas novos formulários criados, não retroativamente -- Template deletado não afeta formulários já criados baseados nele -- Histórico de versões do template é mantido - Administrador só pode gerenciar templates do seu departamento **Responsável:** [Nome do desenvolvedor] @@ -117,19 +113,14 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Criar formulário baseado em template - Selecionar turmas para aplicar o formulário - Escolher tipo de avaliador (docentes ou discentes) -- Definir data de abertura e fechamento - Visualizar formulários criados **Regras de Negócio:** - Formulário herda todas as questões do template no momento da criação - Deve selecionar no mínimo 1 turma - Deve escolher tipo de avaliador (obrigatório) -- Data de fechamento deve ser posterior à data de abertura -- Formulário não pode ser editado após criação (apenas deletado e recriado) - Apenas turmas do departamento do administrador podem ser selecionadas -- Formulário ativo quando data atual está entre abertura e fechamento - Mesmo formulário não pode ser respondido 2 vezes pelo mesmo participante -- Notificação automática enviada aos participantes quando formulário é criado **Responsável:** [Nome do desenvolvedor] @@ -140,14 +131,11 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Funcionalidades:** - Visualizar formulários não respondidos - Responder questionário -- Salvar progresso como rascunho - Submeter respostas **Regras de Negócio:** - Participante visualiza apenas formulários de turmas onde está matriculado -- Formulário deve estar dentro do período de abertura/fechamento para responder - Todos os campos obrigatórios devem ser preenchidos antes de enviar -- Rascunho é salvo automaticamente a cada 30 segundos - Participante pode editar respostas enquanto rascunho não foi enviado - Uma vez enviado, não pode ser alterado - Resposta não pode ser submetida duas vezes pelo mesmo participante @@ -161,13 +149,13 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Funcionalidades:** - Exportar respostas em formato CSV -- Filtrar por turma específica +- Filtrar por formulário - Download do arquivo **Regras de Negócio:** - Apenas administrador pode exportar resultados - Arquivo CSV deve conter: ID da resposta, Participante, Data de resposta, Respostas individuais -- Formato CSV deve ser compatível com Excel e planilhas +- Formato CSV deve ser compatível com planilhas - Caracteres especiais devem estar codificados corretamente (UTF-8) - Arquivo só pode ser gerado se houver respostas - Apenas respostas de formulários do departamento do admin são exportadas @@ -192,7 +180,6 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Erro 403 (Acesso Negado) deve ser retornado para tentativas não autorizadas - Super Admin pode gerenciar múltiplos departamentos (se aplicável) - Departamento é definido no perfil do usuário durante importação do SIGAA -- Logs de tentativas de acesso não autorizado devem ser registrados **Responsável:** [Nome do desenvolvedor] @@ -228,9 +215,8 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Usuários são criados automaticamente durante importação do SIGAA - Usuário inativo não pode fazer login - Quando participante é removido do SIGAA, status muda para inativo -- Email de boas-vindas é enviado automaticamente -- Senha temporária é gerada e enviada por email -- Usuário pode ter múltiplos papéis (aluno em uma turma, professor em outra, admin de disciplina) +- Email de boas-vindas é enviado automaticamente (se aplicável) +- Senha temporária é gerada e enviada no email de boas vindas - Histórico de mudanças de status é mantido **Responsável:** [Nome do desenvolvedor] From 95a8c09709d29a4eabce79804f7e970889500570 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Mon, 17 Nov 2025 21:39:32 -0300 Subject: [PATCH 13/63] =?UTF-8?q?Mais=20altera=C3=A7=C3=B5es=20no=20md=20d?= =?UTF-8?q?a=20wiki?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7bac3c310e..4bbe7c5b93 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Visualizar formulários não respondidos - Responder questionário - Submeter respostas +- Filtrar respostas de turmas **Regras de Negócio:** - Participante visualiza apenas formulários de turmas onde está matriculado @@ -280,7 +281,6 @@ A fim de submeter minha avaliação da turma Critérios de Aceitação: - Visualiza questionário da turma - Preenche questões obrigatórias -- Salva como rascunho (auto-save a cada 30s) - Submete respostas finais - Não pode responder 2 vezes o mesmo formulário ``` @@ -337,7 +337,7 @@ A fim de gerar formulários de avaliações reutilizáveis Critérios de Aceitação: - Template com nome único - Mínimo 1 questão obrigatória -- Suporta múltiplos tipos: múltipla escolha, Likert, texto, V/F +- Suporta múltiplos tipos: múltipla escolha, texto, V/F - Apenas criador pode editar - Edições não afetam formulários já criados ``` @@ -356,7 +356,6 @@ A fim de avaliar o desempenho das turmas no semestre atual Critérios de Aceitação: - Seleciona template - Escolhe múltiplas turmas (mínimo 1) -- Define datas de abertura/fechamento - Escolhe tipo de avaliador (docentes/discentes) - Formulário herda questões do template - Notificação enviada aos participantes @@ -370,15 +369,13 @@ Critérios de Aceitação: ``` Como Usuário do sistema -Quero acessar o sistema utilizando email ou matrícula e senha +Quero acessar o sistema utilizando email e senha A fim de responder formulários ou gerenciar o sistema Critérios de Aceitação: - Login com email funciona -- Login com matrícula funciona - Validação de credenciais - Redirecionamento para painel -- Bloqueio após 5 tentativas falhadas por 15 minutos ``` **Pontos:** 3 | **Responsável:** [Nome] @@ -393,7 +390,6 @@ Quero definir uma senha para o meu usuário a partir do email de cadastro A fim de acessar o sistema Critérios de Aceitação: -- Link de ativação válido por 48 horas - Senha deve cumprir critérios de segurança - Senhas devem conferir - Redirecionamento para login após sucesso @@ -414,7 +410,6 @@ Critérios de Aceitação: - Visualiza apenas turmas do departamento - Criar formulário apenas com turmas do departamento - Erro 403 para tentativas de acesso não autorizado -- Logs de tentativas não autorizadas ``` **Pontos:** 5 | **Responsável:** [Nome] @@ -429,7 +424,6 @@ Quero redefinir uma senha a partir do email recebido A fim de recuperar meu acesso ao sistema Critérios de Aceitação: -- Link de recuperação válido por 24 horas - Validação de senha - Email de confirmação enviado - Redirecionamento para login @@ -449,7 +443,6 @@ A fim de corrigir a base de dados do sistema Critérios de Aceitação: - Sincroniza dados existentes - Adiciona novos dados -- Marca turmas como inativas - Atualiza mudanças de turmas - Mantém histórico de atualizações ``` @@ -468,7 +461,6 @@ A fim de poder escolher qual irei responder Critérios de Aceitação: - Lista apenas formulários não respondidos - Mostra turma, disciplina, datas -- Filtra por turma - Formulários respondidos não aparecem ``` @@ -521,11 +513,8 @@ Quero editar e/ou deletar um template que eu criei sem afetar formulários já c A fim de organizar os templates existentes Critérios de Aceitação: -- Editar template não afeta formulários existentes -- Deletar template não deleta formulários - Confirmação de exclusão -- Apenas criador pode editar -- Histórico de versões mantido +- Apenas criador pode editar e excluir ``` **Pontos:** 3 | **Responsável:** [Nome] @@ -544,7 +533,6 @@ Critérios de Aceitação: - Professores veem apenas formulários para Docentes - Alunos veem apenas formulários para Discentes - Múltiplos formulários podem coexistir para mesma turma -- Notificação apenas para tipo correto ``` **Pontos:** 3 | **Responsável:** [Nome] From 2b3e1f03f0b025ab352e52b552574fac6d669a00 Mon Sep 17 00:00:00 2001 From: Filipe Marcelino <105222322+Fam21@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:47:43 -0300 Subject: [PATCH 14/63] Add files via upload --- features/cadastrar_usuario_do_sistema.feature | 53 ++++++++++++++++++ features/criar_formulario.feature | 56 +++++++++++++++++++ features/responder_formulario.feature | 38 +++++++++++++ features/sistema_login.feature | 46 +++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 features/cadastrar_usuario_do_sistema.feature create mode 100644 features/criar_formulario.feature create mode 100644 features/responder_formulario.feature create mode 100644 features/sistema_login.feature diff --git a/features/cadastrar_usuario_do_sistema.feature b/features/cadastrar_usuario_do_sistema.feature new file mode 100644 index 0000000000..a9ec6496ff --- /dev/null +++ b/features/cadastrar_usuario_do_sistema.feature @@ -0,0 +1,53 @@ +Feature: Importação de participantes do SIGAA + Como Administrador + Quero cadastrar participantes de turmas do SIGAA ao importar dados de usuários novos para o sistema + A fim de que eles acessem o sistema CAMAAR + + Scenario: Administrador importa participantes de uma turma do SIGAA + Given que estou autenticado como Administrador + And estou na seção "Gerenciamento" + When eu seleciono a opção "Importar Participantes do SIGAA" + And escolho a turma "Turma A" + And confirmo a importação + Then o sistema deve importar os dados dos usuários da turma "Turma A" + And deve registrar que os usuários importados estão pendentes de definição de senha + + Scenario: Sistema envia solicitação para definição de senha aos usuários importados + Given que usuários foram importados da turma "Turma A" + And estão marcados como pendentes de definição de senha + When a importação é concluída + Then o sistema deve enviar uma solicitação para definição de senha para cada usuário novo importado + + Scenario: Usuário define a senha pela primeira vez + Given que um usuário foi importado e está pendente de definição de senha + When o usuário acessa o link de definição de senha enviado pelo sistema + And informa uma nova senha válida + Then o sistema deve registrar a senha do usuário + And o cadastro do usuário deve ser efetivado + And o usuário deve estar apto a acessar o sistema CAMAAR + + Scenario: Usuário tenta acessar o sistema sem ter definido a senha + Given que um usuário foi importado e está pendente de definição de senha + When o usuário tenta acessar o sistema CAMAAR + Then o sistema deve impedir o acesso + And deve informar que a senha deve ser definida antes do primeiro acesso + + Scenario: Administrador importa usuários já existentes no sistema + Given que estou autenticado como Administrador + And estou na seção "Importar Participantes do SIGAA" + When eu importo uma turma que contém usuários já cadastrados no CAMAAR + Then o sistema não deve recriar esses usuários + And deve apenas associá-los à turma importada + + Scenario: Administrador tenta importar turma sem selecionar uma turma + Given que estou autenticado como Administrador + When eu tento iniciar a importação sem selecionar uma turma + Then o sistema deve informar que é necessário escolher uma turma + And não deve iniciar a importação + + Scenario: Importação falha devido a erro no SIGAA + Given que estou autenticado como Administrador + And estou na página de importação de participantes + When ocorre uma falha na comunicação com o SIGAA + Then o sistema deve informar que não foi possível completar a importação + And não deve registrar nenhum usuário como importado diff --git a/features/criar_formulario.feature b/features/criar_formulario.feature new file mode 100644 index 0000000000..6b99b2e06e --- /dev/null +++ b/features/criar_formulario.feature @@ -0,0 +1,56 @@ +Feature: Criação de formulários de avaliação + Como Administrador + Quero escolher criar um formulário para os docentes ou os discentes de uma turma + A fim de avaliar o desempenho de uma matéria + + Scenario: Administrador acessa a área de criação de formulários + Given que estou autenticado como Administrador + When eu acesso a seção de "Gerenciamento" no menu lateral + Then o sistema deve exibir a opção "Criar Formulário" + And deve permitir selecionar uma turma para criar o formulário + + Scenario: Criar formulário de avaliação para docentes + Given que estou autenticado como Administrador + And estou na página de criação de formulário + When eu seleciono a turma "Turma A" + And escolho o tipo de público "Docentes" + And preencho as perguntas do formulário + And confirmo a criação + Then o sistema deve registrar o formulário para os docentes da turma "Turma A" + And deve exibir uma mensagem de confirmação de criação + + Scenario: Criar formulário de avaliação para discentes + Given que estou autenticado como Administrador + And estou na página de criação de formulário + When eu seleciono a turma "Turma B" + And escolho o tipo de público "Discentes" + And preencho as perguntas do formulário + And confirmo a criação + Then o sistema deve registrar o formulário para os discentes da turma "Turma B" + And deve exibir uma mensagem de confirmação de criação + + Scenario: Administrador tenta criar formulário sem selecionar a turma + Given que estou autenticado como Administrador + And estou na página de criação de formulário + When eu deixo de selecionar uma turma + And tento confirmar a criação do formulário + Then o sistema deve informar que a seleção da turma é obrigatória + And não deve criar o formulário + + Scenario: Administrador tenta criar formulário sem definir o tipo de público + Given que estou autenticado como Administrador + And estou na página de criação de formulário + When eu seleciono uma turma + And não escolho se o formulário é para docentes ou discentes + And tento confirmar a criação + Then o sistema deve informar que o tipo de público deve ser selecionado + And não deve criar o formulário + + Scenario: Administrador tenta criar formulário sem perguntas + Given que estou autenticado como Administrador + And estou na página de criação de formulário + When eu seleciono uma turma + And escolho o tipo de público "Docentes" + And tento criar o formulário sem adicionar perguntas + Then o sistema deve informar que o formulário deve conter ao menos uma pergunta + And não deve criar o formulário diff --git a/features/responder_formulario.feature b/features/responder_formulario.feature new file mode 100644 index 0000000000..1790add42b --- /dev/null +++ b/features/responder_formulario.feature @@ -0,0 +1,38 @@ +Feature: Responder questionário de avaliação da turma + Como Participante de uma turma + Eu quero responder o questionário sobre a turma em que estou matriculado + A fim de submeter minha avaliação da turma + + Scenario: Acessar questionário disponível para avaliação + Given que estou autenticado no sistema como Participante + And estou matriculado em uma turma que possui um questionário de avaliação aberto + When eu acesso a área de avaliações da minha turma + Then o sistema deve exibir o questionário disponível para resposta + + Scenario: Responder questionário com sucesso + Given que estou autenticado no sistema como Participante + And existe um questionário de avaliação aberto para a turma em que estou matriculado + When eu preencho todas as perguntas obrigatórias do questionário + And submeto as respostas + Then o sistema deve registrar minhas respostas + And exibir uma confirmação de envio da avaliação + + Scenario: Tentativa de envio com respostas incompletas + Given que estou autenticado no sistema como Participante + And existe um questionário de avaliação aberto para a minha turma + When eu tento submeter o questionário deixando perguntas obrigatórias em branco + Then o sistema deve impedir a submissão + And exibir uma mensagem informando que há perguntas obrigatórias não respondidas + + Scenario: Participante tenta responder um questionário já enviado + Given que estou autenticado no sistema como Participante + And já submeti anteriormente minha avaliação da turma + When eu tento acessar novamente o questionário de avaliação + Then o sistema deve informar que minha avaliação já foi enviada + And não deve permitir uma nova submissão + + Scenario: Participante tenta responder questionário inexistente + Given que estou autenticado no sistema como Participante + And minha turma não possui questionário de avaliação disponível + When eu acesso a área de avaliações da turma + Then o sistema deve informar que não há avaliações disponíveis no momento diff --git a/features/sistema_login.feature b/features/sistema_login.feature new file mode 100644 index 0000000000..d436a75827 --- /dev/null +++ b/features/sistema_login.feature @@ -0,0 +1,46 @@ +Feature: Autenticação de Usuário + Como Usuário do sistema + Quero acessar o sistema utilizando um e-mail ou matrícula e uma senha já cadastrada + A fim de responder formulários ou gerenciar o sistema + + Scenario: Login com credenciais válidas usando e-mail + Given que existe um usuário cadastrado com email "usuario@exemplo.com" e senha "123456" + And estou na página de login + When eu informo o email "usuario@exemplo.com" e a senha "123456" + Then o sistema deve autenticar o usuário + And deve exibir a página inicial do sistema + + Scenario: Login com credenciais válidas usando matrícula + Given que existe um usuário cadastrado com matrícula "202312345" e senha "abc123" + And estou na página de login + When eu informo a matrícula "202312345" e a senha "abc123" + Then o sistema deve autenticar o usuário + And deve exibir a página inicial do sistema + + Scenario: Login com credenciais inválidas + Given que estou na página de login + When eu informo o identificador "usuario@exemplo.com" e a senha "senha_incorreta" + Then o sistema deve exibir uma mensagem de erro de autenticação + And não deve permitir o acesso ao sistema + + Scenario: Login de administrador exibe opções de gerenciamento + Given que existe um usuário administrador com email "admin@exemplo.com" e senha "admin123" + And estou na página de login + When eu informo o email "admin@exemplo.com" e a senha "admin123" + Then o sistema deve autenticar o usuário + And deve exibir a página inicial do sistema + And deve mostrar a opção "Gerenciamento" no menu lateral + + Scenario: Login de usuário comum não exibe opções de gerenciamento + Given que existe um usuário comum com email "user@exemplo.com" e senha "user123" + And estou na página de login + When eu informo o email "user@exemplo.com" e a senha "user123" + Then o sistema deve autenticar o usuário + And deve exibir a página inicial do sistema + And não deve mostrar a opção "Gerenciamento" no menu lateral + + Scenario: Tentativa de login sem preencher todos os campos + Given que estou na página de login + When eu tento entrar no sistema sem preencher o identificador ou a senha + Then o sistema deve informar que todos os campos são obrigatórios + And não deve prosseguir com a autenticação From cbad876b82b1bc450dfdd433e2e197c19b27fac6 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Tue, 18 Nov 2025 21:18:32 -0300 Subject: [PATCH 15/63] Ajustes finais ao markdown --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 4bbe7c5b93..8a35d0069c 100644 --- a/README.md +++ b/README.md @@ -78,10 +78,8 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Registros duplicados são ignorados (validação por ID único do SIGAA) - Ao importar novos usuários, criar contas com senha temporária - Dados importados não devem sobrescrever informações customizadas no sistema -- Cada importação deve ser registrada com: data, hora, usuário responsável, quantidade de registros processados - Participantes removidos do SIGAA devem ter acesso desativado - Atualização de mudanças de turmas de alunos/professores -- Máximo 1 importação simultânea (evitar condições de corrida) **Responsável:** [Nome do desenvolvedor] @@ -137,7 +135,7 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Regras de Negócio:** - Participante visualiza apenas formulários de turmas onde está matriculado - Todos os campos obrigatórios devem ser preenchidos antes de enviar -- Participante pode editar respostas enquanto rascunho não foi enviado +- Participante pode editar respostas enquanto formulário não foi enviado - Uma vez enviado, não pode ser alterado - Resposta não pode ser submetida duas vezes pelo mesmo participante - Sistema valida tipo de resposta conforme tipo de questão From e3a5fcfcade70740849bed86ab197a53dfaa5ded Mon Sep 17 00:00:00 2001 From: Bernardo Rodrigues Date: Tue, 18 Nov 2025 21:19:34 -0300 Subject: [PATCH 16/63] Ajustes as issues: cadastrar usuario do sistema, criar formulario, edicao e delecao de templates, formularios discentes e docentes, responder formularios e sistema login --- features/cadastrar_usuario_do_sistema.feature | 14 +++++++------- features/criar_formulario.feature | 12 ++++++------ features/edicao_e_delecao_templates.feature | 17 +++++++++++++++++ features/formularios_discentes_docentes.feature | 3 +-- features/responder_formulario.feature | 17 ++++++++++++----- features/sistema_login.feature | 12 ++++++------ 6 files changed, 49 insertions(+), 26 deletions(-) diff --git a/features/cadastrar_usuario_do_sistema.feature b/features/cadastrar_usuario_do_sistema.feature index a9ec6496ff..ccae729dfd 100644 --- a/features/cadastrar_usuario_do_sistema.feature +++ b/features/cadastrar_usuario_do_sistema.feature @@ -3,7 +3,7 @@ Feature: Importação de participantes do SIGAA Quero cadastrar participantes de turmas do SIGAA ao importar dados de usuários novos para o sistema A fim de que eles acessem o sistema CAMAAR - Scenario: Administrador importa participantes de uma turma do SIGAA + Scenario: Administrador importa participantes de uma turma do SIGAA (feliz) Given que estou autenticado como Administrador And estou na seção "Gerenciamento" When eu seleciono a opção "Importar Participantes do SIGAA" @@ -12,13 +12,13 @@ Feature: Importação de participantes do SIGAA Then o sistema deve importar os dados dos usuários da turma "Turma A" And deve registrar que os usuários importados estão pendentes de definição de senha - Scenario: Sistema envia solicitação para definição de senha aos usuários importados + Scenario: Sistema envia solicitação para definição de senha aos usuários importados (feliz) Given que usuários foram importados da turma "Turma A" And estão marcados como pendentes de definição de senha When a importação é concluída Then o sistema deve enviar uma solicitação para definição de senha para cada usuário novo importado - Scenario: Usuário define a senha pela primeira vez + Scenario: Usuário define a senha pela primeira vez (feliz) Given que um usuário foi importado e está pendente de definição de senha When o usuário acessa o link de definição de senha enviado pelo sistema And informa uma nova senha válida @@ -26,26 +26,26 @@ Feature: Importação de participantes do SIGAA And o cadastro do usuário deve ser efetivado And o usuário deve estar apto a acessar o sistema CAMAAR - Scenario: Usuário tenta acessar o sistema sem ter definido a senha + Scenario: Usuário tenta acessar o sistema sem ter definido a senha (triste) Given que um usuário foi importado e está pendente de definição de senha When o usuário tenta acessar o sistema CAMAAR Then o sistema deve impedir o acesso And deve informar que a senha deve ser definida antes do primeiro acesso - Scenario: Administrador importa usuários já existentes no sistema + Scenario: Administrador importa usuários já existentes no sistema (triste) Given que estou autenticado como Administrador And estou na seção "Importar Participantes do SIGAA" When eu importo uma turma que contém usuários já cadastrados no CAMAAR Then o sistema não deve recriar esses usuários And deve apenas associá-los à turma importada - Scenario: Administrador tenta importar turma sem selecionar uma turma + Scenario: Administrador tenta importar turma sem selecionar uma turma (triste) Given que estou autenticado como Administrador When eu tento iniciar a importação sem selecionar uma turma Then o sistema deve informar que é necessário escolher uma turma And não deve iniciar a importação - Scenario: Importação falha devido a erro no SIGAA + Scenario: Importação falha devido a erro no SIGAA (triste) Given que estou autenticado como Administrador And estou na página de importação de participantes When ocorre uma falha na comunicação com o SIGAA diff --git a/features/criar_formulario.feature b/features/criar_formulario.feature index 6b99b2e06e..7ffd474026 100644 --- a/features/criar_formulario.feature +++ b/features/criar_formulario.feature @@ -3,13 +3,13 @@ Feature: Criação de formulários de avaliação Quero escolher criar um formulário para os docentes ou os discentes de uma turma A fim de avaliar o desempenho de uma matéria - Scenario: Administrador acessa a área de criação de formulários + Scenario: Administrador acessa a área de criação de formulários (feliz) Given que estou autenticado como Administrador When eu acesso a seção de "Gerenciamento" no menu lateral Then o sistema deve exibir a opção "Criar Formulário" And deve permitir selecionar uma turma para criar o formulário - Scenario: Criar formulário de avaliação para docentes + Scenario: Criar formulário de avaliação para docentes (feliz) Given que estou autenticado como Administrador And estou na página de criação de formulário When eu seleciono a turma "Turma A" @@ -19,7 +19,7 @@ Feature: Criação de formulários de avaliação Then o sistema deve registrar o formulário para os docentes da turma "Turma A" And deve exibir uma mensagem de confirmação de criação - Scenario: Criar formulário de avaliação para discentes + Scenario: Criar formulário de avaliação para discentes (feliz) Given que estou autenticado como Administrador And estou na página de criação de formulário When eu seleciono a turma "Turma B" @@ -29,7 +29,7 @@ Feature: Criação de formulários de avaliação Then o sistema deve registrar o formulário para os discentes da turma "Turma B" And deve exibir uma mensagem de confirmação de criação - Scenario: Administrador tenta criar formulário sem selecionar a turma + Scenario: Administrador tenta criar formulário sem selecionar a turma (triste) Given que estou autenticado como Administrador And estou na página de criação de formulário When eu deixo de selecionar uma turma @@ -37,7 +37,7 @@ Feature: Criação de formulários de avaliação Then o sistema deve informar que a seleção da turma é obrigatória And não deve criar o formulário - Scenario: Administrador tenta criar formulário sem definir o tipo de público + Scenario: Administrador tenta criar formulário sem definir o tipo de público (triste) Given que estou autenticado como Administrador And estou na página de criação de formulário When eu seleciono uma turma @@ -46,7 +46,7 @@ Feature: Criação de formulários de avaliação Then o sistema deve informar que o tipo de público deve ser selecionado And não deve criar o formulário - Scenario: Administrador tenta criar formulário sem perguntas + Scenario: Administrador tenta criar formulário sem perguntas (triste) Given que estou autenticado como Administrador And estou na página de criação de formulário When eu seleciono uma turma diff --git a/features/edicao_e_delecao_templates.feature b/features/edicao_e_delecao_templates.feature index 8e9f41bb25..1a129159e2 100644 --- a/features/edicao_e_delecao_templates.feature +++ b/features/edicao_e_delecao_templates.feature @@ -45,3 +45,20 @@ Feature: Edição e deleção de templates When eu tento deletar o template Then a remoção deve ser bloqueada And eu devo ver a mensagem "Não é possível remover um template em uso" + + Scenario: Edições em template não afetam formulários já criados (feliz) + Given que criei Template V1 com 3 perguntas + And criei Formulário A baseado em Template V1 + When eu edito Template V1 adicionando 1 pergunta + And crio Formulário B baseado em Template V1 (agora com 4 perguntas) + Then Formulário A deve manter as 3 perguntas originais + And Formulário B deve ter as 4 novas perguntas + + Scenario: Admin de outro departamento não pode editar template (triste) + Given que Admin A criou um template no Dept. X + And Admin B pertence ao Dept. Y + When Admin B tenta editar template de Admin A + Then o sistema retorna erro 403 + And mensagem "Acesso negado" + + diff --git a/features/formularios_discentes_docentes.feature b/features/formularios_discentes_docentes.feature index 2accdc4f7d..5b58f02c5e 100644 --- a/features/formularios_discentes_docentes.feature +++ b/features/formularios_discentes_docentes.feature @@ -36,5 +36,4 @@ Feature: Escolher criar formulário para docentes ou discentes When eu crio um formulário para "Discentes" And eu crio outro formulário para "Docentes" Then os dois formulários devem coexistir - And cada grupo receberá apenas o formulário destinado a ele -``` \ No newline at end of file + And cada grupo receberá apenas o formulário destinado a ele \ No newline at end of file diff --git a/features/responder_formulario.feature b/features/responder_formulario.feature index 1790add42b..58a5e23781 100644 --- a/features/responder_formulario.feature +++ b/features/responder_formulario.feature @@ -3,13 +3,13 @@ Feature: Responder questionário de avaliação da turma Eu quero responder o questionário sobre a turma em que estou matriculado A fim de submeter minha avaliação da turma - Scenario: Acessar questionário disponível para avaliação + Scenario: Acessar questionário disponível para avaliação (feliz) Given que estou autenticado no sistema como Participante And estou matriculado em uma turma que possui um questionário de avaliação aberto When eu acesso a área de avaliações da minha turma Then o sistema deve exibir o questionário disponível para resposta - Scenario: Responder questionário com sucesso + Scenario: Responder questionário com sucesso (feliz) Given que estou autenticado no sistema como Participante And existe um questionário de avaliação aberto para a turma em que estou matriculado When eu preencho todas as perguntas obrigatórias do questionário @@ -17,22 +17,29 @@ Feature: Responder questionário de avaliação da turma Then o sistema deve registrar minhas respostas And exibir uma confirmação de envio da avaliação - Scenario: Tentativa de envio com respostas incompletas + Scenario: Tentativa de envio com respostas incompletas (triste) Given que estou autenticado no sistema como Participante And existe um questionário de avaliação aberto para a minha turma When eu tento submeter o questionário deixando perguntas obrigatórias em branco Then o sistema deve impedir a submissão And exibir uma mensagem informando que há perguntas obrigatórias não respondidas - Scenario: Participante tenta responder um questionário já enviado + Scenario: Participante tenta responder um questionário já enviado (triste) Given que estou autenticado no sistema como Participante And já submeti anteriormente minha avaliação da turma When eu tento acessar novamente o questionário de avaliação Then o sistema deve informar que minha avaliação já foi enviada And não deve permitir uma nova submissão - Scenario: Participante tenta responder questionário inexistente + Scenario: Participante tenta responder questionário inexistente (triste) Given que estou autenticado no sistema como Participante And minha turma não possui questionário de avaliação disponível When eu acesso a área de avaliações da turma Then o sistema deve informar que não há avaliações disponíveis no momento + + Scenario: Participante tenta enviar resposta duplicada na mesma sessão (triste) + Given que estou respondendo um formulário + When eu clico "Enviar" com sucesso + And imediatamente tenta enviar novamente + Then o sistema deve bloquear + And exibir "Esta resposta já foi submetida" diff --git a/features/sistema_login.feature b/features/sistema_login.feature index d436a75827..f50e22f58e 100644 --- a/features/sistema_login.feature +++ b/features/sistema_login.feature @@ -3,27 +3,27 @@ Feature: Autenticação de Usuário Quero acessar o sistema utilizando um e-mail ou matrícula e uma senha já cadastrada A fim de responder formulários ou gerenciar o sistema - Scenario: Login com credenciais válidas usando e-mail + Scenario: Login com credenciais válidas usando e-mail (feliz) Given que existe um usuário cadastrado com email "usuario@exemplo.com" e senha "123456" And estou na página de login When eu informo o email "usuario@exemplo.com" e a senha "123456" Then o sistema deve autenticar o usuário And deve exibir a página inicial do sistema - Scenario: Login com credenciais válidas usando matrícula + Scenario: Login com credenciais válidas usando matrícula (feliz) Given que existe um usuário cadastrado com matrícula "202312345" e senha "abc123" And estou na página de login When eu informo a matrícula "202312345" e a senha "abc123" Then o sistema deve autenticar o usuário And deve exibir a página inicial do sistema - Scenario: Login com credenciais inválidas + Scenario: Login com credenciais inválidas (triste) Given que estou na página de login When eu informo o identificador "usuario@exemplo.com" e a senha "senha_incorreta" Then o sistema deve exibir uma mensagem de erro de autenticação And não deve permitir o acesso ao sistema - Scenario: Login de administrador exibe opções de gerenciamento + Scenario: Login de administrador exibe opções de gerenciamento (feliz) Given que existe um usuário administrador com email "admin@exemplo.com" e senha "admin123" And estou na página de login When eu informo o email "admin@exemplo.com" e a senha "admin123" @@ -31,7 +31,7 @@ Feature: Autenticação de Usuário And deve exibir a página inicial do sistema And deve mostrar a opção "Gerenciamento" no menu lateral - Scenario: Login de usuário comum não exibe opções de gerenciamento + Scenario: Login de usuário comum não exibe opções de gerenciamento (feliz) Given que existe um usuário comum com email "user@exemplo.com" e senha "user123" And estou na página de login When eu informo o email "user@exemplo.com" e a senha "user123" @@ -39,7 +39,7 @@ Feature: Autenticação de Usuário And deve exibir a página inicial do sistema And não deve mostrar a opção "Gerenciamento" no menu lateral - Scenario: Tentativa de login sem preencher todos os campos + Scenario: Tentativa de login sem preencher todos os campos (triste) Given que estou na página de login When eu tento entrar no sistema sem preencher o identificador ou a senha Then o sistema deve informar que todos os campos são obrigatórios From 5528a67a9fe95a74ba8cf38ff4e0579989c0fd40 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:21:19 -0300 Subject: [PATCH 17/63] Update README.md Adequando o escopo do projeto e refinando. --- README.md | 118 +++++++++++------------------------------------------- 1 file changed, 24 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index 8a35d0069c..5a0dbd57cf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Informações do Projeto | Item | Descrição | -|------|-----------| +|------|-----------| | **Nome do Projeto** | CAMAAR - Plataforma de Avaliação de Cursos | | **Disciplina** | Engenharia de Software | | **Período** | 2025.2 | @@ -16,7 +16,7 @@ ## Papéis Scrum | Papel | Responsável | Matrícula | -|-------|-------------|-----------| +|-------|-------------|-----------| | **Scrum Master** | Bernardo Gomes Rodrigues | 231034190 | | **Product Owner** | Maria Carolina Burgum Abreu Jorge | 231013547 | @@ -26,14 +26,13 @@ ### Descrição Geral -O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** integrada com o SIGAA (Sistema Integrado de Gestão de Atividades Acadêmicas) por meio de arquivos JSON. O sistema permite que administradores (coordenadores de cursos) criem formulários de avaliação que são respondidos por alunos e professores, gerando relatórios sobre o desempenho das disciplinas e infraestrutura. +O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** integrada com o SIGAA (Sistema Integrado de Gestão de Atividades Acadêmicas) por meio de arquivos JSON. O sistema permite que administradores criem formulários de avaliação que são respondidos por alunos e professores, gerando relatórios sobre o desempenho das disciplinas e infraestrutura. ### Objetivos Principais - Automatizar o processo de coleta de feedback sobre disciplinas - Integrar dados do SIGAA para alimentar a plataforma - Permitir análise de dados através de exportação em CSV -- Fornecer controle de acesso baseado em departamento - Facilitar a gestão de templates reutilizáveis de formulários ### Stakeholders @@ -47,18 +46,15 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte ## Funcionalidades e Regras de Negócio -### 1. Autenticação de Usuários (5 pontos) +### 1. Autenticação de Usuários (2 pontos) **Funcionalidades:** - Login com email ou matrícula + senha - Definir senha após convite de cadastro -- Email de boas vindas informando a senha (se aplicável) -- Recuperar acesso através de email (redefinir senha) **Regras de Negócio:** - Senhas devem ter no mínimo 4 caracteres - Senhas devem conter pelo menos: 1 letra maiúscula, 1 letra minúscula, 1 número e 1 caractere especial -- Email de boas-vindas enviado automaticamente após criação de usuário - Usuário deve alterar senha temporária no primeiro acesso **Responsável:** [Nome do desenvolvedor] @@ -70,7 +66,6 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Funcionalidades:** - Importar turmas, disciplinas e participantes do SIGAA - Atualizar dados existentes com informações atualizadas -- Manter histórico de importações - Marcar turmas como inativas quando descontinuadas **Regras de Negócio:** @@ -95,11 +90,10 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte **Regras de Negócio:** - Template deve ter no mínimo 1 questão -- Nome do template deve ser único por administrador -- Apenas o criador do template pode editá-lo (ou outro admin do mesmo departamento) +- Nome do template deve ser único +- Apenas o criador do template pode editá-lo - Tipos de questões suportados: Múltipla escolha, Texto aberto, Verdadeiro/Falso - Edições em template afetam apenas novos formulários criados, não retroativamente -- Administrador só pode gerenciar templates do seu departamento **Responsável:** [Nome do desenvolvedor] @@ -117,7 +111,6 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Formulário herda todas as questões do template no momento da criação - Deve selecionar no mínimo 1 turma - Deve escolher tipo de avaliador (obrigatório) -- Apenas turmas do departamento do administrador podem ser selecionadas - Mesmo formulário não pode ser respondido 2 vezes pelo mesmo participante **Responsável:** [Nome do desenvolvedor] @@ -157,7 +150,6 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte - Formato CSV deve ser compatível com planilhas - Caracteres especiais devem estar codificados corretamente (UTF-8) - Arquivo só pode ser gerado se houver respostas -- Apenas respostas de formulários do departamento do admin são exportadas - Nomes de participantes anonimizados por padrão (apenas matrícula mostrada) - Arquivo gerado com timestamp: `formulario_[id]_[data_hora].csv` @@ -165,26 +157,7 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte --- -### 7. Controle de Acesso por Departamento (5 pontos) - -**Funcionalidades:** -- Visualizar apenas turmas do departamento -- Criar formulários apenas para turmas do departamento -- Gerenciar templates apenas do departamento -- Controle de permissão em operações - -**Regras de Negócio:** -- Administrador só vê/gerencia turmas do seu departamento -- Administrador não pode criar formulário para turma de outro departamento -- Erro 403 (Acesso Negado) deve ser retornado para tentativas não autorizadas -- Super Admin pode gerenciar múltiplos departamentos (se aplicável) -- Departamento é definido no perfil do usuário durante importação do SIGAA - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 8. Diferenciação de Avaliadores (3 pontos) +### 7. Diferenciação de Avaliadores (3 pontos) **Funcionalidades:** - Escolher tipo de avaliador (docentes ou discentes) @@ -203,20 +176,18 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte --- -### 9. Gestão de Usuários e Participantes (3 pontos) +### 8. Gestão de Usuários e Participantes (3 pontos) **Funcionalidades:** - Cadastro automático de participantes na importação - Desativação de participantes inativos -- Visualização de histórico de usuários **Regras de Negócio:** - Usuários são criados automaticamente durante importação do SIGAA - Usuário inativo não pode fazer login - Quando participante é removido do SIGAA, status muda para inativo -- Email de boas-vindas é enviado automaticamente (se aplicável) +- Email de boas-vindas é enviado automaticamente - Senha temporária é gerada e enviada no email de boas vindas -- Histórico de mudanças de status é mantido **Responsável:** [Nome do desenvolvedor] @@ -234,16 +205,14 @@ O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** inte | 6 | Criar formulário baseado em template | Criação de Formulários | 8 | [Nome] | | 7 | Acessar sistema com credenciais | Autenticação | 3 | [Nome] | | 8 | Definir senha após cadastro | Autenticação | 2 | [Nome] | -| 9 | Gerenciar turmas do departamento | Controle de Acesso | 5 | [Nome] | -| 10 | Redefinir senha | Autenticação | 3 | [Nome] | -| 11 | Atualizar base de dados existente | Importação de Dados | 5 | [Nome] | -| 12 | Visualizar formulários não respondidos | Resposta de Questionários | 3 | [Nome] | -| 13 | Visualizar formulários criados | Criação de Formulários | 3 | [Nome] | -| 14 | Visualizar templates criados | Gerenciamento de Templates | 2 | [Nome] | -| 15 | Editar e deletar template | Gerenciamento de Templates | 3 | [Nome] | -| 16 | Escolher tipo de avaliador | Diferenciação de Avaliadores | 3 | [Nome] | +| 9 | Atualizar base de dados existente | Importação de Dados | 5 | [Nome] | +| 10 | Visualizar formulários não respondidos | Resposta de Questionários | 3 | [Nome] | +| 11 | Visualizar formulários criados | Criação de Formulários | 3 | [Nome] | +| 12 | Visualizar templates criados | Gerenciamento de Templates | 2 | [Nome] | +| 13 | Editar e deletar template | Gerenciamento de Templates | 3 | [Nome] | +| 14 | Escolher tipo de avaliador | Diferenciação de Avaliadores | 3 | [Nome] | -**Total de Pontos: 62 pontos** +**Total de Pontos: 56 pontos** --- @@ -258,9 +227,8 @@ A fim de alimentar a base de dados do sistema Critérios de Aceitação: - Sistema importa com sucesso turmas, disciplinas e participantes do SIGAA -- Dados duplicados são ignorados e registrados em relatório +- Dados duplicados são ignorados - Novos usuários recebem email de boas-vindas -- Histórico de importação é salvo com timestamp - Erro de conexão é tratado graciosamente - Máximo 1 importação simultânea ``` @@ -296,7 +264,6 @@ A fim de que eles acessem o sistema CAMAAR Critérios de Aceitação: - Novos usuários são criados automaticamente -- Cada usuário recebe email de boas-vindas - Senha temporária é gerada - Usuário deve alterar senha no primeiro acesso - Usuários já existentes são associados às novas turmas @@ -317,7 +284,6 @@ Critérios de Aceitação: - Arquivo CSV é gerado com estrutura correta - Colunas: ID, Participante, Data, Respostas - Caracteres especiais codificados (UTF-8) -- Filtro por turma específica - Anonimização de nomes (apenas matrícula) ``` @@ -397,41 +363,7 @@ Critérios de Aceitação: --- -### HU-09: Gerenciar turmas do departamento (5 pontos) - -``` -Como Administrador -Quero gerenciar somente as turmas do departamento o qual eu pertenço -A fim de avaliar o desempenho das turmas no semestre atual - -Critérios de Aceitação: -- Visualiza apenas turmas do departamento -- Criar formulário apenas com turmas do departamento -- Erro 403 para tentativas de acesso não autorizado -``` - -**Pontos:** 5 | **Responsável:** [Nome] - ---- - -### HU-10: Redefinir senha (3 pontos) - -``` -Como Usuário -Quero redefinir uma senha a partir do email recebido -A fim de recuperar meu acesso ao sistema - -Critérios de Aceitação: -- Validação de senha -- Email de confirmação enviado -- Redirecionamento para login -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -### HU-11: Atualizar base de dados existente (5 pontos) +### HU-09: Atualizar base de dados existente (5 pontos) ``` Como Administrador @@ -442,14 +374,13 @@ Critérios de Aceitação: - Sincroniza dados existentes - Adiciona novos dados - Atualiza mudanças de turmas -- Mantém histórico de atualizações ``` **Pontos:** 5 | **Responsável:** [Nome] --- -### HU-12: Visualizar formulários não respondidos (3 pontos) +### HU-10: Visualizar formulários não respondidos (3 pontos) ``` Como Participante de uma turma @@ -466,7 +397,7 @@ Critérios de Aceitação: --- -### HU-13: Visualizar formulários criados (3 pontos) +### HU-11: Visualizar formulários criados (3 pontos) ``` Como Administrador @@ -477,7 +408,6 @@ Critérios de Aceitação: - Lista todos os formulários - Mostra nome, template, turmas, status, data - Quantidade de respostas exibida -- Acesso apenas a formulários do departamento - Opção de visualizar detalhes ``` @@ -485,7 +415,7 @@ Critérios de Aceitação: --- -### HU-14: Visualizar templates criados (2 pontos) +### HU-12: Visualizar templates criados (2 pontos) ``` Como Administrador @@ -503,7 +433,7 @@ Critérios de Aceitação: --- -### HU-15: Editar e deletar template (3 pontos) +### HU-13: Editar e deletar template (3 pontos) ``` Como Administrador @@ -519,7 +449,7 @@ Critérios de Aceitação: --- -### HU-16: Escolher tipo de avaliador (3 pontos) +### HU-14: Escolher tipo de avaliador (3 pontos) ``` Como Administrador @@ -595,7 +525,7 @@ feat(autenticacao): Implementar login com email e matrícula ### Cálculo de Velocity **Sprint Atual:** -- Total de Pontos: **62 pontos** +- Total de Pontos: **56 pontos** - Histórias por Sprint: [A definir conforme organização] **Fórmula:** From f17e43dd3f1068ca5eaa83a1a9c2760d3a0d1b00 Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:08:22 -0300 Subject: [PATCH 18/63] Update README.md: Adicionando estrutura do banco de dados --- README.md | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 254 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5a0dbd57cf..27e1b1b61c 100644 --- a/README.md +++ b/README.md @@ -550,12 +550,257 @@ Baseado na velocity média, para próxima sprint: --- -## Próximos Passos - -- [X] Preencher nomes e matrículas dos integrantes -- [ ] Definir duração de cada sprint -- [ ] Criar backlog com priorização -- [X] Configurar repositório Git -- [X] Configurar ambiente de desenvolvimento -- [X] Criar estrutura inicial do Rails -- [X] Iniciar Sprint 1 +### Estrutura do Banco de Dados: + +``` + +// CAMAAR - Database Schema (DbDiagram.io format) +// Plataforma de Avaliação de Cursos +// Versão: 1.0 (Pós-Remoções) +// Data: 07/12/2025 + +// ============================================ +// ENUMS +// ============================================ + +Enum user_role { + aluno + professor + admin +} + +Enum form_evaluator_type { + aluno + professor +} + +Enum form_status { + rascunho + ativo + finalizado +} + +Enum response_status { + rascunho + submetido +} + +// ============================================ +// TABELAS PRINCIPAIS +// ============================================ + +Table users { + id integer [pk, increment] + name varchar(255) [not null, note: 'Nome completo do usuário'] + email varchar(255) [not null, unique, note: 'Email único do usuário'] + matricula varchar(50) [not null, unique, note: 'Matrícula do SIGAA'] + password_digest varchar [not null, note: 'Senha criptografada (bcrypt)'] + role user_role [not null, note: 'Tipo de usuário: aluno, professor ou admin'] + department_id integer [not null, ref: > departments.id, note: 'Departamento do usuário'] + active boolean [not null, default: true, note: 'Usuário ativo no sistema'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (email) [unique, name: 'idx_users_email'] + (matricula) [unique, name: 'idx_users_matricula'] + (department_id) [name: 'idx_users_department_id'] + (role) [name: 'idx_users_role'] + } + + Note: 'Usuários do sistema (alunos, professores, administradores)' +} + + +Table departments { + id integer [pk, increment] + name varchar(255) [not null, note: 'Nome do departamento'] + code varchar(20) [not null, unique, note: 'Sigla/código do departamento (ex: CIC, ENE)'] + sigaa_id varchar(50) [note: 'ID do departamento no SIGAA (opcional)'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (code) [unique, name: 'idx_departments_code'] + (sigaa_id) [name: 'idx_departments_sigaa_id'] + } + + Note: 'Departamentos acadêmicos' +} + + +Table courses { + id integer [pk, increment] + name varchar(255) [not null, note: 'Nome da disciplina'] + code varchar(20) [not null, note: 'Código da disciplina (ex: FGA0208)'] + department_id integer [not null, ref: > departments.id, note: 'Departamento responsável'] + sigaa_id varchar(50) [note: 'ID da disciplina no SIGAA (opcional)'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (code, department_id) [unique, name: 'idx_courses_code_dept'] + (department_id) [name: 'idx_courses_department_id'] + (sigaa_id) [name: 'idx_courses_sigaa_id'] + } + + Note: 'Disciplinas/Cursos oferecidos' +} + + +Table classes { + id integer [pk, increment] + course_id integer [not null, ref: > courses.id, note: 'Disciplina da turma'] + professor_id integer [not null, ref: > users.id, note: 'Professor responsável (role=professor)'] + semester varchar(10) [not null, note: 'Semestre/período (ex: 2025.2)'] + turma_code varchar(10) [not null, note: 'Código da turma (ex: A, B, 01)'] + sigaa_id varchar(50) [note: 'ID da turma no SIGAA (opcional)'] + active boolean [not null, default: true, note: 'Turma ativa'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (course_id, semester, turma_code) [unique, name: 'idx_classes_unique'] + (course_id) [name: 'idx_classes_course_id'] + (professor_id) [name: 'idx_classes_professor_id'] + (semester) [name: 'idx_classes_semester'] + (sigaa_id) [name: 'idx_classes_sigaa_id'] + (active) [name: 'idx_classes_active'] + } + + Note: 'Turmas específicas de um curso em um semestre' +} + + +Table templates { + id integer [pk, increment] + title varchar(255) [not null, note: 'Título do template'] + description text [note: 'Descrição/instruções do template'] + questions jsonb [not null, note: 'Array JSON com questões estruturadas'] + creator_id integer [not null, ref: > users.id, note: 'Admin que criou o template (role=admin)'] + department_id integer [not null, ref: > departments.id, note: 'Departamento proprietário'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (creator_id) [name: 'idx_templates_creator_id'] + (department_id) [name: 'idx_templates_department_id'] + (title, department_id) [unique, name: 'idx_templates_title_dept'] + } + + Note: '''Template reutilizável de formulário de avaliação + + Estrutura esperada do campo questions: + [ + { + "id": "q1", + "text": "Pergunta?", + "type": "multiple_choice | text | boolean", + "required": true, + "options": ["Op1", "Op2"] // se multiple_choice + } + ]''' +} + + +Table forms { + id integer [pk, increment] + title varchar(255) [not null, note: 'Título do formulário'] + description text [note: 'Descrição/instruções do formulário'] + questions jsonb [not null, note: 'Array JSON com questões estruturadas (cópia do template)'] + evaluator_type form_evaluator_type [not null, note: 'Quem deve responder: aluno ou professor'] + creator_id integer [not null, ref: > users.id, note: 'Admin que criou o formulário (role=admin)'] + template_id integer [ref: > templates.id, note: 'Template usado como base (rastreamento)'] + status form_status [not null, default: 'rascunho', note: 'Estado do formulário'] + start_date date [note: 'Data de início da avaliação'] + end_date date [note: 'Data limite da avaliação'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (status) [name: 'idx_forms_status'] + (start_date, end_date) [name: 'idx_forms_date_range'] + (creator_id) [name: 'idx_forms_creator_id'] + (template_id) [name: 'idx_forms_template_id'] + } + + Note: '''Formulários de avaliação + + Estrutura esperada do campo questions: + [ + { + "id": "q1", + "text": "Pergunta?", + "type": "multiple_choice | text | boolean", + "required": true, + "options": ["Op1", "Op2"] // se multiple_choice + } + ]''' +} + + +Table responses { + id integer [pk, increment] + form_id integer [not null, ref: > forms.id, note: 'Formulário respondido'] + user_id integer [not null, ref: > users.id, note: 'Usuário que respondeu'] + class_id integer [ref: > classes.id, note: 'Turma avaliada (opcional)'] + answers jsonb [not null, note: 'Array JSON com respostas'] + status response_status [not null, default: 'rascunho', note: 'Estado da resposta'] + submitted_at timestamp [note: 'Data/hora de submissão'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (form_id, user_id) [unique, name: 'idx_responses_unique'] + (user_id) [name: 'idx_responses_user_id'] + (status) [name: 'idx_responses_status'] + (submitted_at) [name: 'idx_responses_submitted_at'] + } + + Note: '''Respostas dos participantes a formulários + + Estrutura esperada do campo answers: + [ + { + "question_id": "q1", + "value": "Resposta" + } + ] + + Constraint: um usuário responde apenas uma vez por formulário (unique em form_id, user_id)''' +} + + +// ============================================ +// TABELAS DE JUNÇÃO (Many-to-Many) +// ============================================ + + +Table classes_users { + class_id integer [not null, ref: > classes.id, note: 'Turma'] + user_id integer [not null, ref: > users.id, note: 'Aluno (role=aluno) matriculado'] + created_at timestamp [not null, default: 'now()'] + + indexes { + (class_id, user_id) [pk, unique, name: 'idx_classes_users_pk'] + (user_id) [name: 'idx_classes_users_user_id'] + } + + Note: 'Relacionamento muitos-para-muitos: alunos matriculados em turmas' +} + + +Table forms_classes { + form_id integer [not null, ref: > forms.id, note: 'Formulário de avaliação'] + class_id integer [not null, ref: > classes.id, note: 'Turma que deve responder'] + created_at timestamp [not null, default: 'now()'] + + indexes { + (form_id, class_id) [pk, unique, name: 'idx_forms_classes_pk'] + (class_id) [name: 'idx_forms_classes_class_id'] + } + + Note: 'Relacionamento muitos-para-muitos: turmas associadas a formulários' +} + +``` From eec9cda0c152eff0bf41cd313e375016c206512f Mon Sep 17 00:00:00 2001 From: bg! <99600828+bg1777@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:25:42 -0300 Subject: [PATCH 19/63] Update README.md: Add examples for classes and class members JSON files Added examples for classes.json and class_members.json files with detailed structures and sample data. --- README.md | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 457 insertions(+) diff --git a/README.md b/README.md index 27e1b1b61c..8a1af75dd1 100644 --- a/README.md +++ b/README.md @@ -804,3 +804,460 @@ Table forms_classes { } ``` +--- + +### Arquivo classes.json (exemplo) + +``` + +[ + { + "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" + } + } +] + +``` + +### Arquivo class_members.json (exemplo): + +``` + +[ + { + "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" + } + } +] + +``` From 1c679e946fdd7022837169bdf756ff810532df7f Mon Sep 17 00:00:00 2001 From: bg1777 Date: Sun, 7 Dec 2025 17:08:54 -0300 Subject: [PATCH 20/63] chore: update Gemfile --- Gemfile | 46 ++++++++++++++++----------------------- Gemfile.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/Gemfile b/Gemfile index 41a51bd086..45d1cdddef 100644 --- a/Gemfile +++ b/Gemfile @@ -1,66 +1,56 @@ source "https://rubygems.org" -# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 8.0.4" -# The modern asset pipeline for Rails [https://github.com/rails/propshaft] gem "propshaft" -# Use sqlite3 as the database for Active Record gem "sqlite3", ">= 2.1" -# Use the Puma web server [https://github.com/puma/puma] gem "puma", ">= 5.0" -# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] gem "importmap-rails" -# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] gem "turbo-rails" -# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] gem "stimulus-rails" -# Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" -# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" +# ============================================ +# Authentication +# ============================================ +gem "devise", "~> 4.9" + +gem "bcrypt", "~> 3.1.7" -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ windows jruby ] -# Use the database-backed adapters for Rails.cache, Active Job, and Action Cable gem "solid_cache" gem "solid_queue" gem "solid_cable" -# Reduces boot times through caching; required in config/boot.rb gem "bootsnap", require: false - -# Deploy this application anywhere as a Docker container [https://kamal-deploy.org] gem "kamal", require: false - -# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/] gem "thruster", require: false -# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -# gem "image_processing", "~> 1.2" - group :development, :test do - # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" - - # Static analysis for security vulnerabilities [https://brakemanscanner.org/] gem "brakeman", require: false - - # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] gem "rubocop-rails-omakase", require: false + + # ============================================ + # RSpec - Test-Driven Development (TDD) + # ============================================ + gem "rspec-rails", "~> 6.0.0" + gem "factory_bot_rails", "~> 6.2" + gem "faker", "~> 3.2" + gem "shoulda-matchers", "~> 5.1" end group :development do - # Use console on exceptions pages [https://github.com/rails/web-console] gem "web-console" end group :test do - # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem "capybara" gem "selenium-webdriver" + gem 'cucumber-rails', require: false - # database_cleaner is not mandatory, but highly recommended gem 'database_cleaner' + + gem "simplecov", "~> 0.22.0" + gem "simplecov-console" end diff --git a/Gemfile.lock b/Gemfile.lock index 88d9dd45db..8cc6ec9032 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -74,8 +74,10 @@ GEM uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) ast (2.4.3) base64 (0.3.0) + bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) benchmark (0.5.0) bigdecimal (3.3.1) @@ -136,7 +138,14 @@ GEM debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) + devise (4.9.4) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) diff-lcs (1.6.2) + docile (1.4.1) dotenv (3.1.8) drb (2.2.3) ed25519 (1.4.0) @@ -144,6 +153,13 @@ GEM 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-aarch64-linux-gnu) ffi (1.17.2-aarch64-linux-musl) ffi (1.17.2-arm-linux-gnu) @@ -227,6 +243,7 @@ GEM racc (~> 1.4) nokogiri (1.18.10-x86_64-linux-musl) racc (~> 1.4) + orm_adapter (0.5.0) ostruct (0.6.3) parallel (1.27.0) parser (3.3.10.0) @@ -295,7 +312,27 @@ GEM regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) 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 (6.0.4) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-support (3.13.6) rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -333,6 +370,18 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) + 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-console (0.9.4) + ansi + simplecov + terminal-table + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) solid_cable (3.0.12) actioncable (>= 7.2) activejob (>= 7.2) @@ -368,6 +417,8 @@ GEM sys-uname (1.4.1) ffi (~> 1.1) memoist3 (~> 1.0.0) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) thor (1.4.0) thruster (0.1.16) thruster (0.1.16-aarch64-linux) @@ -384,6 +435,8 @@ GEM unicode-emoji (4.1.0) uri (1.1.1) useragent (0.16.11) + warden (1.2.9) + rack (>= 2.0.9) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -409,20 +462,28 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + bcrypt (~> 3.1.7) bootsnap brakeman capybara cucumber-rails database_cleaner debug + devise (~> 4.9) + factory_bot_rails (~> 6.2) + faker (~> 3.2) importmap-rails jbuilder kamal propshaft puma (>= 5.0) rails (~> 8.0.4) + rspec-rails (~> 6.0.0) rubocop-rails-omakase selenium-webdriver + shoulda-matchers (~> 5.1) + simplecov (~> 0.22.0) + simplecov-console solid_cable solid_cache solid_queue From 208e187e762846c7b00107e6458b2b5ddb5e573c Mon Sep 17 00:00:00 2001 From: bg1777 Date: Sun, 7 Dec 2025 21:10:53 -0300 Subject: [PATCH 21/63] feat: Model de user implementada --- .rspec | 1 + Gemfile | 1 + Gemfile.lock | 2 + app/models/user.rb | 19 ++ config/initializers/devise.rb | 313 ++++++++++++++++++ config/locales/devise.en.yml | 65 ++++ config/routes.rb | 1 + .../20251207235542_devise_create_users.rb | 54 +++ db/schema.rb | 27 ++ spec/factories/users.rb | 11 + spec/models/user_spec.rb | 76 +++++ spec/rails_helper.rb | 80 +++++ spec/spec_helper.rb | 94 ++++++ 13 files changed, 744 insertions(+) create mode 100644 .rspec create mode 100644 app/models/user.rb create mode 100644 config/initializers/devise.rb create mode 100644 config/locales/devise.en.yml create mode 100644 db/migrate/20251207235542_devise_create_users.rb create mode 100644 db/schema.rb create mode 100644 spec/factories/users.rb create mode 100644 spec/models/user_spec.rb create mode 100644 spec/rails_helper.rb create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000000..c99d2e7396 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/Gemfile b/Gemfile index 45d1cdddef..2975b2f6b6 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,7 @@ group :development, :test do gem "factory_bot_rails", "~> 6.2" gem "faker", "~> 3.2" gem "shoulda-matchers", "~> 5.1" + gem "csv" end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 8cc6ec9032..61c88f2726 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,6 +99,7 @@ GEM concurrent-ruby (1.3.5) connection_pool (2.5.4) crass (1.0.6) + csv (3.3.5) cucumber (10.1.1) base64 (~> 0.2) builder (~> 3.2) @@ -466,6 +467,7 @@ DEPENDENCIES bootsnap brakeman capybara + csv cucumber-rails database_cleaner debug diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000000..a54b4f77e3 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,19 @@ +# app/models/user.rb + +class User < ApplicationRecord + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable + + # Enum para role + enum :role, { user: 0, admin: 1 } + + # Validações + validates :name, presence: true + validates :email, presence: true, uniqueness: true + validates :password, presence: true, length: { minimum: 6 }, on: :create + + # Métodos úteis + def admin? + role == 'admin' + end +end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb new file mode 100644 index 0000000000..6a80e52d1e --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '2ad97e09e6fff98164473ba02c88057fcf40f900ac26b54ead2301614d1f82e35cd938a1bdfdab1e8ab7734e87e09abd5a56219dc0e6d53055aa9995907d7f5a' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 12. If + # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 12 + + # Set up a pepper to generate the hashed password. + # config.pepper = 'b1bb2e7e71e5d6e712355e53017528d30755ba9f83596f33463863b9b1d7eafa3403aa09e95983e6daef3c80f6e6be03d7e3ca2d5c8250b34dad18b4f1d1dba5' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html, :turbo_stream] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Hotwire/Turbo configuration + # When using Devise with Hotwire/Turbo, the http status for error responses + # and some redirects must match the following. The default in Devise for existing + # apps is `200 OK` and `302 Found` respectively, but new apps are generated with + # these new defaults that match Hotwire/Turbo behavior. + # Note: These might become the new default in future versions of Devise. + config.responder.error_status = :unprocessable_entity + config.responder.redirect_status = :see_other + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true +end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 0000000000..260e1c4ba6 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,65 @@ +# Additional translations at https://github.com/heartcombo/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + email_changed: + subject: "Email Changed" + password_change: + subject: "Password Changed" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." + updated: "Your account has been updated successfully." + updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again." + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/routes.rb b/config/routes.rb index 48254e88ed..be25e8b391 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + devise_for :users # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. diff --git a/db/migrate/20251207235542_devise_create_users.rb b/db/migrate/20251207235542_devise_create_users.rb new file mode 100644 index 0000000000..547948148c --- /dev/null +++ b/db/migrate/20251207235542_devise_create_users.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + + +class DeviseCreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + + ## Rememberable + t.datetime :remember_created_at + + + ## Trackable + # t.integer :sign_in_count, default: 0, null: false + # t.datetime :current_sign_in_at + # t.datetime :last_sign_in_at + # t.string :current_sign_in_ip + # t.string :last_sign_in_ip + + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + ## Custom fields + t.string :name, null: false + t.integer :role, default: 0 # 0: user, 1: admin + + t.timestamps null: false + end + + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..e545d35e7d --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,27 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 2025_12_07_235542) do + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.string "name", null: false + t.integer "role", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000000..9a44b27c91 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,11 @@ +# spec/factories/users.rb + +FactoryBot.define do + factory :user do + name { Faker::Name.name } + email { Faker::Internet.email } + password { 'senha123' } + password_confirmation { 'senha123' } + role { :user } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000000..e532f3acdd --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,76 @@ +# spec/models/user_spec.rb + +require 'rails_helper' + +RSpec.describe User, type: :model do + describe 'validações' do + it { should validate_presence_of(:name) } + it { should validate_presence_of(:email) } + + it 'validates email uniqueness' do + user1 = create(:user, email: 'unique@example.com') + user2 = build(:user, email: 'unique@example.com') + + expect(user2).not_to be_valid + end + end + + describe 'criação de usuário' do + it 'cria um usuário com dados válidos' do + user = User.create( + name: 'João Silva', + email: 'joao@example.com', + password: 'senha123', + password_confirmation: 'senha123', + role: :user + ) + + expect(user).to be_persisted + expect(user.user?).to be true + end + + it 'não cria usuário sem nome' do + user = User.new( + email: 'teste@example.com', + password: 'senha123', + password_confirmation: 'senha123' + ) + + expect(user).not_to be_valid + expect(user.errors[:name]).to include("can't be blank") + end + + it 'não cria usuário com email duplicado' do + User.create( + name: 'João', + email: 'joao@example.com', + password: 'senha123', + password_confirmation: 'senha123' + ) + + user2 = User.new( + name: 'Maria', + email: 'joao@example.com', + password: 'senha123', + password_confirmation: 'senha123' + ) + + expect(user2).not_to be_valid + end + end + + describe 'roles' do + it 'cria um usuário admin' do + admin = User.create( + name: 'Administrador', + email: 'admin@example.com', + password: 'senha123', + password_confirmation: 'senha123', + role: :admin + ) + + expect(admin.admin?).to be true + expect(admin.role).to eq('admin') + end + end +end \ No newline at end of file diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000000..da9885e867 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,80 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +require 'factory_bot_rails' + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } + + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + abort e.to_s.strip +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # Use factories instead of fixtures + config.include FactoryBot::Syntax::Methods + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # [https://rspec.info/features/6-0/rspec-rails](https://rspec.info/features/6-0/rspec-rails) + config.infer_spec_type_from_file_location! + + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end + +# Configure shoulda-matchers +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000000..327b58ea1f --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,94 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end From 297b14b2aa17081fbda39eb5b6b897228839dde3 Mon Sep 17 00:00:00 2001 From: bg1777 Date: Sun, 7 Dec 2025 21:23:55 -0300 Subject: [PATCH 22/63] feat: tela de login + inicio da dashboard do admin --- app/controllers/admin/dashboard_controller.rb | 20 +++++++ app/controllers/admin/users_controller.rb | 46 ++++++++++++++++ app/controllers/home_controller.rb | 13 +++++ app/helpers/admin/dashboard_helper.rb | 2 + app/helpers/admin/users_helper.rb | 2 + app/helpers/home_helper.rb | 2 + app/views/admin/dashboard/index.html.erb | 53 +++++++++++++++++++ app/views/admin/users/destroy.html.erb | 2 + app/views/admin/users/edit.html.erb | 2 + app/views/admin/users/index.html.erb | 36 +++++++++++++ app/views/admin/users/show.html.erb | 2 + app/views/admin/users/update.html.erb | 2 + app/views/devise/confirmations/new.html.erb | 16 ++++++ .../mailer/confirmation_instructions.html.erb | 5 ++ .../devise/mailer/email_changed.html.erb | 7 +++ .../devise/mailer/password_change.html.erb | 3 ++ .../reset_password_instructions.html.erb | 8 +++ .../mailer/unlock_instructions.html.erb | 7 +++ app/views/devise/passwords/edit.html.erb | 25 +++++++++ app/views/devise/passwords/new.html.erb | 16 ++++++ app/views/devise/registrations/edit.html.erb | 43 +++++++++++++++ app/views/devise/registrations/new.html.erb | 29 ++++++++++ app/views/devise/sessions/new.html.erb | 26 +++++++++ .../devise/shared/_error_messages.html.erb | 15 ++++++ app/views/devise/shared/_links.html.erb | 25 +++++++++ app/views/devise/unlocks/new.html.erb | 16 ++++++ app/views/home/index.html.erb | 15 ++++++ config/routes.rb | 33 +++++++----- spec/helpers/admin/dashboard_helper_spec.rb | 15 ++++++ spec/helpers/admin/users_helper_spec.rb | 15 ++++++ spec/helpers/home_helper_spec.rb | 15 ++++++ spec/requests/admin/dashboard_spec.rb | 11 ++++ spec/requests/admin/users_spec.rb | 39 ++++++++++++++ spec/requests/home_spec.rb | 11 ++++ .../admin/dashboard/index.html.erb_spec.rb | 5 ++ .../admin/users/destroy.html.erb_spec.rb | 5 ++ spec/views/admin/users/edit.html.erb_spec.rb | 5 ++ spec/views/admin/users/index.html.erb_spec.rb | 5 ++ spec/views/admin/users/show.html.erb_spec.rb | 5 ++ .../views/admin/users/update.html.erb_spec.rb | 5 ++ spec/views/home/index.html.erb_spec.rb | 5 ++ 41 files changed, 600 insertions(+), 12 deletions(-) create mode 100644 app/controllers/admin/dashboard_controller.rb create mode 100644 app/controllers/admin/users_controller.rb create mode 100644 app/controllers/home_controller.rb create mode 100644 app/helpers/admin/dashboard_helper.rb create mode 100644 app/helpers/admin/users_helper.rb create mode 100644 app/helpers/home_helper.rb create mode 100644 app/views/admin/dashboard/index.html.erb create mode 100644 app/views/admin/users/destroy.html.erb create mode 100644 app/views/admin/users/edit.html.erb create mode 100644 app/views/admin/users/index.html.erb create mode 100644 app/views/admin/users/show.html.erb create mode 100644 app/views/admin/users/update.html.erb create mode 100644 app/views/devise/confirmations/new.html.erb create mode 100644 app/views/devise/mailer/confirmation_instructions.html.erb create mode 100644 app/views/devise/mailer/email_changed.html.erb create mode 100644 app/views/devise/mailer/password_change.html.erb create mode 100644 app/views/devise/mailer/reset_password_instructions.html.erb create mode 100644 app/views/devise/mailer/unlock_instructions.html.erb create mode 100644 app/views/devise/passwords/edit.html.erb create mode 100644 app/views/devise/passwords/new.html.erb create mode 100644 app/views/devise/registrations/edit.html.erb create mode 100644 app/views/devise/registrations/new.html.erb create mode 100644 app/views/devise/sessions/new.html.erb create mode 100644 app/views/devise/shared/_error_messages.html.erb create mode 100644 app/views/devise/shared/_links.html.erb create mode 100644 app/views/devise/unlocks/new.html.erb create mode 100644 app/views/home/index.html.erb create mode 100644 spec/helpers/admin/dashboard_helper_spec.rb create mode 100644 spec/helpers/admin/users_helper_spec.rb create mode 100644 spec/helpers/home_helper_spec.rb create mode 100644 spec/requests/admin/dashboard_spec.rb create mode 100644 spec/requests/admin/users_spec.rb create mode 100644 spec/requests/home_spec.rb create mode 100644 spec/views/admin/dashboard/index.html.erb_spec.rb create mode 100644 spec/views/admin/users/destroy.html.erb_spec.rb create mode 100644 spec/views/admin/users/edit.html.erb_spec.rb create mode 100644 spec/views/admin/users/index.html.erb_spec.rb create mode 100644 spec/views/admin/users/show.html.erb_spec.rb create mode 100644 spec/views/admin/users/update.html.erb_spec.rb create mode 100644 spec/views/home/index.html.erb_spec.rb diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb new file mode 100644 index 0000000000..b2fd25d3de --- /dev/null +++ b/app/controllers/admin/dashboard_controller.rb @@ -0,0 +1,20 @@ +# app/controllers/admin/dashboard_controller.rb + +module Admin + class DashboardController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + + def index + @users = User.all + @total_users = User.count + @admin_count = User.where(role: :admin).count + end + + private + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 0000000000..12147dafa1 --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -0,0 +1,46 @@ +# app/controllers/admin/users_controller.rb + +module Admin + class UsersController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + before_action :set_user, only: [:show, :edit, :update, :destroy] + + def index + @users = User.all + end + + def show + end + + def edit + end + + def update + if @user.update(user_params) + redirect_to admin_users_path, notice: 'Usuário atualizado com sucesso' + else + render :edit + end + end + + def destroy + @user.destroy + redirect_to admin_users_path, notice: 'Usuário deletado com sucesso' + end + + private + + def set_user + @user = User.find(params[:id]) + end + + def user_params + params.require(:user).permit(:name, :email, :role) + end + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 0000000000..06a6ec5f9b --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,13 @@ +# app/controllers/home_controller.rb + +class HomeController < ApplicationController + def index + if user_signed_in? + if current_user.admin? + redirect_to admin_root_path + else + @user = current_user + end + end + end +end diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb new file mode 100644 index 0000000000..4052b7c4b7 --- /dev/null +++ b/app/helpers/admin/dashboard_helper.rb @@ -0,0 +1,2 @@ +module Admin::DashboardHelper +end diff --git a/app/helpers/admin/users_helper.rb b/app/helpers/admin/users_helper.rb new file mode 100644 index 0000000000..5995c2aa82 --- /dev/null +++ b/app/helpers/admin/users_helper.rb @@ -0,0 +1,2 @@ +module Admin::UsersHelper +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb new file mode 100644 index 0000000000..23de56ac60 --- /dev/null +++ b/app/helpers/home_helper.rb @@ -0,0 +1,2 @@ +module HomeHelper +end diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb new file mode 100644 index 0000000000..50b5b6ca47 --- /dev/null +++ b/app/views/admin/dashboard/index.html.erb @@ -0,0 +1,53 @@ + + +
+

Painel Admin

+ +
+
+
+
+
Total de Usuários
+

<%= @total_users %>

+
+
+
+ +
+
+
+
Total de Admins
+

<%= @admin_count %>

+
+
+
+
+ +

Usuários Recentes

+ + + + + + + + + + + <% @users.each do |user| %> + + + + + + + <% end %> + +
NomeEmailRoleAções
<%= user.name %><%= user.email %><%= user.role %> + <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> + <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Deletar', admin_user_path(user), method: :delete, class: 'btn btn-sm btn-danger' %> +
+ + <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> +
diff --git a/app/views/admin/users/destroy.html.erb b/app/views/admin/users/destroy.html.erb new file mode 100644 index 0000000000..c80f2fdb02 --- /dev/null +++ b/app/views/admin/users/destroy.html.erb @@ -0,0 +1,2 @@ +

Admin::Users#destroy

+

Find me in app/views/admin/users/destroy.html.erb

diff --git a/app/views/admin/users/edit.html.erb b/app/views/admin/users/edit.html.erb new file mode 100644 index 0000000000..363be9579e --- /dev/null +++ b/app/views/admin/users/edit.html.erb @@ -0,0 +1,2 @@ +

Admin::Users#edit

+

Find me in app/views/admin/users/edit.html.erb

diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb new file mode 100644 index 0000000000..c2a6c281c8 --- /dev/null +++ b/app/views/admin/users/index.html.erb @@ -0,0 +1,36 @@ + + +
+

Gerenciar Usuários

+ + + + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + + + <% end %> + +
IDNomeEmailRoleCriado emAções
<%= user.id %><%= user.name %><%= user.email %><%= user.role %><%= user.created_at.strftime("%d/%m/%Y") %> + <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> + <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Deletar', admin_user_path(user), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> +
+ + <%= link_to 'Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb new file mode 100644 index 0000000000..9ce4ddb33b --- /dev/null +++ b/app/views/admin/users/show.html.erb @@ -0,0 +1,2 @@ +

Admin::Users#show

+

Find me in app/views/admin/users/show.html.erb

diff --git a/app/views/admin/users/update.html.erb b/app/views/admin/users/update.html.erb new file mode 100644 index 0000000000..2851bd8864 --- /dev/null +++ b/app/views/admin/users/update.html.erb @@ -0,0 +1,2 @@ +

Admin::Users#update

+

Find me in app/views/admin/users/update.html.erb

diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb new file mode 100644 index 0000000000..b12dd0cbeb --- /dev/null +++ b/app/views/devise/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 0000000000..dc55f64f69 --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 0000000000..32f4ba8038 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 0000000000..b41daf476a --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 0000000000..f667dc12fe --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 0000000000..41e148bf2a --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 0000000000..5fbb9ff0a7 --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,25 @@ +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb new file mode 100644 index 0000000000..9b486b81b9 --- /dev/null +++ b/app/views/devise/passwords/new.html.erb @@ -0,0 +1,16 @@ +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb new file mode 100644 index 0000000000..b82e3365a3 --- /dev/null +++ b/app/views/devise/registrations/edit.html.erb @@ -0,0 +1,43 @@ +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %> + <% if @minimum_password_length %> +
+ <%= @minimum_password_length %> characters minimum + <% end %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 0000000000..d655b66f6f --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,29 @@ +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb new file mode 100644 index 0000000000..5ede96489d --- /dev/null +++ b/app/views/devise/sessions/new.html.erb @@ -0,0 +1,26 @@ +

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ + <% if devise_mapping.rememberable? %> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end %> + +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb new file mode 100644 index 0000000000..cabfe307ef --- /dev/null +++ b/app/views/devise/shared/_error_messages.html.erb @@ -0,0 +1,15 @@ +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+
    + <% resource.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+<% end %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb new file mode 100644 index 0000000000..7a75304bad --- /dev/null +++ b/app/views/devise/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
+ <% end %> +<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb new file mode 100644 index 0000000000..ffc34de8d1 --- /dev/null +++ b/app/views/devise/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb new file mode 100644 index 0000000000..3344328562 --- /dev/null +++ b/app/views/home/index.html.erb @@ -0,0 +1,15 @@ + + +
+ <% if user_signed_in? %> +

Bem-vindo, <%= current_user.name %>!

+

Email: <%= current_user.email %>

+

Role: <%= current_user.role %>

+ + <%= link_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> + <% else %> +

Login necessário

+ <%= link_to 'Entrar', new_user_session_path, class: 'btn btn-primary' %> + <%= link_to 'Registrar', new_user_registration_path, class: 'btn btn-success' %> + <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index be25e8b391..bdf882f94a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,15 +1,24 @@ +# config/routes.rb + Rails.application.routes.draw do + get "home/index" devise_for :users - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. - # Can be used by load balancers and uptime monitors to verify that the app is live. - get "up" => "rails/health#show", as: :rails_health_check - - # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb) - # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest - # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker - - # Defines the root path route ("/") - # root "posts#index" + + # Root path + root "home#index" + + # Admin dashboard + namespace :admin do + get "users/index" + get "users/show" + get "users/edit" + get "users/update" + get "users/destroy" + get "dashboard/index" + root "dashboard#index" + resources :users + end + + # Home public + get "home", to: "home#index" end diff --git a/spec/helpers/admin/dashboard_helper_spec.rb b/spec/helpers/admin/dashboard_helper_spec.rb new file mode 100644 index 0000000000..628ccf8243 --- /dev/null +++ b/spec/helpers/admin/dashboard_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Admin::DashboardHelper. For example: +# +# describe Admin::DashboardHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Admin::DashboardHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/admin/users_helper_spec.rb b/spec/helpers/admin/users_helper_spec.rb new file mode 100644 index 0000000000..f26854ec30 --- /dev/null +++ b/spec/helpers/admin/users_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Admin::UsersHelper. For example: +# +# describe Admin::UsersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Admin::UsersHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb new file mode 100644 index 0000000000..e537d8d9a8 --- /dev/null +++ b/spec/helpers/home_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the HomeHelper. For example: +# +# describe HomeHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe HomeHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/admin/dashboard_spec.rb b/spec/requests/admin/dashboard_spec.rb new file mode 100644 index 0000000000..9c39b690a8 --- /dev/null +++ b/spec/requests/admin/dashboard_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +RSpec.describe "Admin::Dashboards", type: :request do + describe "GET /index" do + it "returns http success" do + get "/admin/dashboard/index" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/requests/admin/users_spec.rb b/spec/requests/admin/users_spec.rb new file mode 100644 index 0000000000..c4171b1c7e --- /dev/null +++ b/spec/requests/admin/users_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +RSpec.describe "Admin::Users", type: :request do + describe "GET /index" do + it "returns http success" do + get "/admin/users/index" + expect(response).to have_http_status(:success) + end + end + + describe "GET /show" do + it "returns http success" do + get "/admin/users/show" + expect(response).to have_http_status(:success) + end + end + + describe "GET /edit" do + it "returns http success" do + get "/admin/users/edit" + expect(response).to have_http_status(:success) + end + end + + describe "GET /update" do + it "returns http success" do + get "/admin/users/update" + expect(response).to have_http_status(:success) + end + end + + describe "GET /destroy" do + it "returns http success" do + get "/admin/users/destroy" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/requests/home_spec.rb b/spec/requests/home_spec.rb new file mode 100644 index 0000000000..fdbd64231d --- /dev/null +++ b/spec/requests/home_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +RSpec.describe "Homes", type: :request do + describe "GET /index" do + it "returns http success" do + get "/home/index" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/views/admin/dashboard/index.html.erb_spec.rb b/spec/views/admin/dashboard/index.html.erb_spec.rb new file mode 100644 index 0000000000..d9dbe041a3 --- /dev/null +++ b/spec/views/admin/dashboard/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "dashboard/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/destroy.html.erb_spec.rb b/spec/views/admin/users/destroy.html.erb_spec.rb new file mode 100644 index 0000000000..c5955f41c7 --- /dev/null +++ b/spec/views/admin/users/destroy.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "users/destroy.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/edit.html.erb_spec.rb b/spec/views/admin/users/edit.html.erb_spec.rb new file mode 100644 index 0000000000..7d1e936979 --- /dev/null +++ b/spec/views/admin/users/edit.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "users/edit.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/index.html.erb_spec.rb b/spec/views/admin/users/index.html.erb_spec.rb new file mode 100644 index 0000000000..3e5309f6d6 --- /dev/null +++ b/spec/views/admin/users/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "users/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/show.html.erb_spec.rb b/spec/views/admin/users/show.html.erb_spec.rb new file mode 100644 index 0000000000..34ad7a4225 --- /dev/null +++ b/spec/views/admin/users/show.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "users/show.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/update.html.erb_spec.rb b/spec/views/admin/users/update.html.erb_spec.rb new file mode 100644 index 0000000000..7d2a4b9c9d --- /dev/null +++ b/spec/views/admin/users/update.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "users/update.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/home/index.html.erb_spec.rb b/spec/views/home/index.html.erb_spec.rb new file mode 100644 index 0000000000..75bb045bc0 --- /dev/null +++ b/spec/views/home/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "home/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end From ae802d5339367a2a2f267018767361a4e8f2a297 Mon Sep 17 00:00:00 2001 From: bg1777 Date: Sun, 7 Dec 2025 21:55:14 -0300 Subject: [PATCH 23/63] Login de admin com sucesso --- Procfile.dev | 0 app/controllers/home_controller.rb | 12 ++++++------ app/models/user.rb | 10 +--------- app/views/admin/dashboard/index.html.erb | 5 ++++- app/views/home/index.html.erb | 16 +++++----------- config/routes.rb | 12 +----------- config/tailwind.config.js | 13 +++++++++++++ 7 files changed, 30 insertions(+), 38 deletions(-) create mode 100644 Procfile.dev create mode 100644 config/tailwind.config.js diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 06a6ec5f9b..957dbcf60b 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,13 +1,13 @@ # app/controllers/home_controller.rb class HomeController < ApplicationController + before_action :authenticate_user! + def index - if user_signed_in? - if current_user.admin? - redirect_to admin_root_path - else - @user = current_user - end + if current_user.admin? + redirect_to admin_root_path + else + @user = current_user end end end diff --git a/app/models/user.rb b/app/models/user.rb index a54b4f77e3..77b3ea325d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,19 +1,11 @@ # app/models/user.rb class User < ApplicationRecord - devise :database_authenticatable, :registerable, + devise :database_authenticatable, :recoverable, :rememberable, :validatable - # Enum para role enum :role, { user: 0, admin: 1 } - # Validações validates :name, presence: true validates :email, presence: true, uniqueness: true - validates :password, presence: true, length: { minimum: 6 }, on: :create - - # Métodos úteis - def admin? - role == 'admin' - end end diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index 50b5b6ca47..1b5f4c6257 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -1,7 +1,10 @@
-

Painel Admin

+
+

Painel Admin

+ <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> +
diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 3344328562..644586e5f3 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,15 +1,9 @@
- <% if user_signed_in? %> -

Bem-vindo, <%= current_user.name %>!

-

Email: <%= current_user.email %>

-

Role: <%= current_user.role %>

- - <%= link_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> - <% else %> -

Login necessário

- <%= link_to 'Entrar', new_user_session_path, class: 'btn btn-primary' %> - <%= link_to 'Registrar', new_user_registration_path, class: 'btn btn-success' %> - <% end %> +

Bem-vindo, <%= current_user.name %>!

+

Email: <%= current_user.email %>

+

Role: <%= current_user.role %>

+ + <%= link_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %>
diff --git a/config/routes.rb b/config/routes.rb index bdf882f94a..28ce881f8e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,24 +1,14 @@ # config/routes.rb Rails.application.routes.draw do - get "home/index" - devise_for :users + devise_for :users, skip: [:registrations] # Desabilita registro nas rotas - # Root path root "home#index" - # Admin dashboard namespace :admin do - get "users/index" - get "users/show" - get "users/edit" - get "users/update" - get "users/destroy" - get "dashboard/index" root "dashboard#index" resources :users end - # Home public get "home", to: "home#index" end diff --git a/config/tailwind.config.js b/config/tailwind.config.js new file mode 100644 index 0000000000..cfc8b0b13d --- /dev/null +++ b/config/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './public/*.html', + './app/helpers/**/*.rb', + './app/javascript/**/*.js', + './app/views/**/*.{erb,haml,html,slim}' + ], + theme: { + extend: {}, + }, + plugins: [], +} From 302e4b02b647f009897d1fdbf91a19dec4888054 Mon Sep 17 00:00:00 2001 From: bg1777 Date: Sun, 7 Dec 2025 22:17:36 -0300 Subject: [PATCH 24/63] feat: funcionalidade de importar turmas e usuarios no backend --- app/models/class_member.rb | 11 + app/models/klass.rb | 24 ++ app/models/user.rb | 16 +- app/services/import_service.rb | 101 +++++++ db/migrate/20251208005928_create_klasses.rb | 12 + .../20251208005929_create_class_members.rb | 11 + .../20251208005930_add_fields_to_users.rb | 8 + db/schema.rb | 28 +- spec/factories/class_members.rb | 9 + spec/factories/klasses.rb | 10 + spec/models/class_member_spec.rb | 51 ++++ spec/models/klass_spec.rb | 62 ++++ spec/services/import_service_spec.rb | 269 ++++++++++++++++++ 13 files changed, 610 insertions(+), 2 deletions(-) create mode 100644 app/models/class_member.rb create mode 100644 app/models/klass.rb create mode 100644 app/services/import_service.rb create mode 100644 db/migrate/20251208005928_create_klasses.rb create mode 100644 db/migrate/20251208005929_create_class_members.rb create mode 100644 db/migrate/20251208005930_add_fields_to_users.rb create mode 100644 spec/factories/class_members.rb create mode 100644 spec/factories/klasses.rb create mode 100644 spec/models/class_member_spec.rb create mode 100644 spec/models/klass_spec.rb create mode 100644 spec/services/import_service_spec.rb diff --git a/app/models/class_member.rb b/app/models/class_member.rb new file mode 100644 index 0000000000..dac66ffeb1 --- /dev/null +++ b/app/models/class_member.rb @@ -0,0 +1,11 @@ +# app/models/class_member.rb + +class ClassMember < ApplicationRecord + # Associações + belongs_to :user + belongs_to :klass + + # Validações + validates :user_id, uniqueness: { scope: :klass_id, message: 'já está inscrito nesta turma' } + validates :role, presence: true, inclusion: { in: %w(dicente docente), message: 'deve ser dicente ou docente' } +end \ No newline at end of file diff --git a/app/models/klass.rb b/app/models/klass.rb new file mode 100644 index 0000000000..efc2531097 --- /dev/null +++ b/app/models/klass.rb @@ -0,0 +1,24 @@ +# app/models/klass.rb + +class Klass < ApplicationRecord + # Associações + has_many :class_members, dependent: :destroy + has_many :users, through: :class_members + + # Validações + validates :code, presence: true, uniqueness: true + validates :name, presence: true + validates :semester, presence: true + + # Scopes + scope :active, -> { order(semester: :desc) } + + # Métodos úteis + def students + class_members.where(role: 'dicente').map(&:user) + end + + def teachers + class_members.where(role: 'docente').map(&:user) + end +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 77b3ea325d..c5d13a7b39 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,6 +6,20 @@ class User < ApplicationRecord enum :role, { user: 0, admin: 1 } + # Associações + has_many :class_members, dependent: :destroy + has_many :klasses, through: :class_members + + # Validações validates :name, presence: true validates :email, presence: true, uniqueness: true -end + + # Métodos auxiliares + def admin? + role == 'admin' + end + + def user? + role == 'user' + end +end \ No newline at end of file diff --git a/app/services/import_service.rb b/app/services/import_service.rb new file mode 100644 index 0000000000..c52e133951 --- /dev/null +++ b/app/services/import_service.rb @@ -0,0 +1,101 @@ +# app/services/import_service.rb + +class ImportService + attr_reader :file_path, :imported_count, :errors + + def initialize(file_path) + @file_path = file_path + @imported_count = 0 + @errors = [] + end + + # Importa turmas com seus estudantes + def import_klasses + data = parse_json_file + + data.each do |klass_data| + import_single_klass(klass_data) + end + + { success: true, imported: @imported_count, errors: @errors } + rescue StandardError => e + { success: false, error: e.message, imported: @imported_count, errors: @errors } + end + + private + + def parse_json_file + JSON.parse(File.read(@file_path)) + rescue JSON::ParserError => e + raise "Erro ao ler arquivo JSON: #{e.message}" + rescue Errno::ENOENT + raise "Arquivo não encontrado: #{@file_path}" + end + + def import_single_klass(klass_data) + klass = find_or_create_klass(klass_data) + + # Importar estudantes + import_students(klass, klass_data['dicente']) + + @imported_count += 1 + rescue StandardError => e + @errors << "Erro ao importar turma #{klass_data['code']}: #{e.message}" + end + + def find_or_create_klass(klass_data) + klass_info = klass_data['class'] + + Klass.find_or_create_by(code: klass_data['code']) do |klass| + klass.name = klass_data['name'] + klass.semester = klass_info['semester'] + klass.description = "Turma #{klass_info['classCode']} - #{klass_info['time']}" + end + end + + def import_students(klass, students_data) + return unless students_data.present? + + students_data.each do |student_data| + import_single_student(klass, student_data, 'dicente') + end + end + + def import_single_student(klass, student_data, role) + user = find_or_create_user(student_data) + + ClassMember.find_or_create_by(user: user, klass: klass) do |cm| + cm.role = role + end + rescue StandardError => e + @errors << "Erro ao importar estudante #{student_data['nome']}: #{e.message}" + end + + def find_or_create_user(user_data) + user = User.find_by(email: user_data['email']) + + if user.present? + return user + end + + password = SecureRandom.hex(12) + + user = User.new( + email: user_data['email'], + name: user_data['nome'], + matricula: user_data['matricula'], + curso: user_data['curso'], + formacao: user_data['formacao'], + ocupacao: user_data['ocupacao'], + password: password, + password_confirmation: password, + role: :user + ) + + if user.save + user + else + raise "Erro ao salvar usuário #{user_data['email']}: #{user.errors.full_messages.join(', ')}" + end + end +end \ No newline at end of file diff --git a/db/migrate/20251208005928_create_klasses.rb b/db/migrate/20251208005928_create_klasses.rb new file mode 100644 index 0000000000..f88ca856bf --- /dev/null +++ b/db/migrate/20251208005928_create_klasses.rb @@ -0,0 +1,12 @@ +class CreateKlasses < ActiveRecord::Migration[8.0] + def change + create_table :klasses do |t| + t.string :code + t.string :name + t.string :semester + t.text :description + + t.timestamps + end + end +end diff --git a/db/migrate/20251208005929_create_class_members.rb b/db/migrate/20251208005929_create_class_members.rb new file mode 100644 index 0000000000..b28a347480 --- /dev/null +++ b/db/migrate/20251208005929_create_class_members.rb @@ -0,0 +1,11 @@ +class CreateClassMembers < ActiveRecord::Migration[8.0] + def change + create_table :class_members do |t| + t.references :user, null: false, foreign_key: true + t.references :klass, null: false, foreign_key: true + t.string :role + + t.timestamps + end + end +end diff --git a/db/migrate/20251208005930_add_fields_to_users.rb b/db/migrate/20251208005930_add_fields_to_users.rb new file mode 100644 index 0000000000..9512a3a320 --- /dev/null +++ b/db/migrate/20251208005930_add_fields_to_users.rb @@ -0,0 +1,8 @@ +class AddFieldsToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :matricula, :string + add_column :users, :curso, :string + add_column :users, :formacao, :string + add_column :users, :ocupacao, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e545d35e7d..99430c3cb8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,26 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_12_07_235542) do +ActiveRecord::Schema[8.0].define(version: 2025_12_08_005930) do + create_table "class_members", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "klass_id", null: false + t.string "role" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["klass_id"], name: "index_class_members_on_klass_id" + t.index ["user_id"], name: "index_class_members_on_user_id" + end + + create_table "klasses", force: :cascade do |t| + t.string "code" + t.string "name" + t.string "semester" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -21,7 +40,14 @@ t.integer "role", default: 0 t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "matricula" + t.string "curso" + t.string "formacao" + t.string "ocupacao" t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end + + add_foreign_key "class_members", "klasses" + add_foreign_key "class_members", "users" end diff --git a/spec/factories/class_members.rb b/spec/factories/class_members.rb new file mode 100644 index 0000000000..4cc3e519c6 --- /dev/null +++ b/spec/factories/class_members.rb @@ -0,0 +1,9 @@ +# spec/factories/class_members.rb + +FactoryBot.define do + factory :class_member do + user + klass + role { 'dicente' } + end +end \ No newline at end of file diff --git a/spec/factories/klasses.rb b/spec/factories/klasses.rb new file mode 100644 index 0000000000..a4d5f3f302 --- /dev/null +++ b/spec/factories/klasses.rb @@ -0,0 +1,10 @@ +# spec/factories/klasses.rb + +FactoryBot.define do + factory :klass do + sequence(:code) { |n| "CIC#{1000 + n}" } + name { Faker::Lorem.sentence } + semester { "2021.2" } + description { Faker::Lorem.paragraph } + end +end \ No newline at end of file diff --git a/spec/models/class_member_spec.rb b/spec/models/class_member_spec.rb new file mode 100644 index 0000000000..4f3dfc6ded --- /dev/null +++ b/spec/models/class_member_spec.rb @@ -0,0 +1,51 @@ +# spec/models/class_member_spec.rb + +require 'rails_helper' + +RSpec.describe ClassMember, type: :model do + describe 'validações' do + it { should validate_presence_of(:role) } + + it 'não permite role inválido' do + user = create(:user) + klass = create(:klass) + + class_member = build(:class_member, user: user, klass: klass, role: 'invalido') + + expect(class_member).not_to be_valid + end + + it 'não permite mesmo usuário em mesma turma duas vezes' do + user = create(:user) + klass = create(:klass) + + create(:class_member, user: user, klass: klass, role: 'dicente') + + duplicate = build(:class_member, user: user, klass: klass, role: 'dicente') + + expect(duplicate).not_to be_valid + end + end + + describe 'associações' do + it { should belong_to(:user) } + it { should belong_to(:klass) } + end + + describe 'roles válidos' do + let(:user) { create(:user) } + let(:klass) { create(:klass) } + + it 'permite role dicente' do + class_member = build(:class_member, user: user, klass: klass, role: 'dicente') + + expect(class_member).to be_valid + end + + it 'permite role docente' do + class_member = build(:class_member, user: user, klass: klass, role: 'docente') + + expect(class_member).to be_valid + end + end +end \ No newline at end of file diff --git a/spec/models/klass_spec.rb b/spec/models/klass_spec.rb new file mode 100644 index 0000000000..e6d5650b24 --- /dev/null +++ b/spec/models/klass_spec.rb @@ -0,0 +1,62 @@ +# spec/models/klass_spec.rb + +require 'rails_helper' + +RSpec.describe Klass, type: :model do + describe 'validações' do + it { should validate_presence_of(:code) } + it { should validate_presence_of(:name) } + it { should validate_presence_of(:semester) } + + it 'valida unicidade de code' do + create(:klass, code: 'CIC0097') + klass2 = build(:klass, code: 'CIC0097') + + expect(klass2).not_to be_valid + end + end + + describe 'associações' do + it { should have_many(:class_members) } + it { should have_many(:users).through(:class_members) } + end + + describe '#students' do + it 'retorna apenas os alunos da turma' do + klass = create(:klass) + student = create(:user) + teacher = create(:user) + + create(:class_member, klass: klass, user: student, role: 'dicente') + create(:class_member, klass: klass, user: teacher, role: 'docente') + + expect(klass.students).to include(student) + expect(klass.students).not_to include(teacher) + expect(klass.students.count).to eq(1) + end + end + + describe '#teachers' do + it 'retorna apenas os professores da turma' do + klass = create(:klass) + student = create(:user) + teacher = create(:user) + + create(:class_member, klass: klass, user: student, role: 'dicente') + create(:class_member, klass: klass, user: teacher, role: 'docente') + + expect(klass.teachers).to include(teacher) + expect(klass.teachers).not_to include(student) + expect(klass.teachers.count).to eq(1) + end + end + + describe 'scopes' do + it 'ordena por semestre decrescente' do + klass1 = create(:klass, semester: '2021.1') + klass2 = create(:klass, code: 'CIC0098', semester: '2021.2') + + expect(Klass.active).to eq([klass2, klass1]) + end + end +end \ No newline at end of file diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb new file mode 100644 index 0000000000..4272d1d320 --- /dev/null +++ b/spec/services/import_service_spec.rb @@ -0,0 +1,269 @@ +# spec/services/import_service_spec.rb + +require 'rails_helper' + +RSpec.describe ImportService, type: :service do + let(:temp_dir) { Rails.root.join('tmp', 'test_imports') } + + before do + FileUtils.mkdir_p(temp_dir) unless Dir.exist?(temp_dir) + end + + after do + FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir) + end + + describe '#import_klasses' do + context 'com arquivo JSON válido' do + let(:file_path) { temp_dir.join('valid_klasses.json') } + + before do + File.write(file_path, JSON.generate([ + { + "code" => "CIC0097", + "name" => "BANCOS DE DADOS", + "class" => { + "classCode" => "TA", + "semester" => "2021.2", + "time" => "35T45" + }, + "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" + } + ] + } + ])) + end + + it 'importa uma turma com sucesso' do + service = ImportService.new(file_path) + result = service.import_klasses + + expect(result[:success]).to be true + expect(result[:imported]).to eq(1) + expect(result[:errors]).to be_empty + end + + it 'cria a turma corretamente' do + service = ImportService.new(file_path) + service.import_klasses + + klass = Klass.find_by(code: 'CIC0097') + + expect(klass).to be_present + expect(klass.name).to eq('BANCOS DE DADOS') + expect(klass.semester).to eq('2021.2') + end + + it 'cria os usuários estudantes' do + service = ImportService.new(file_path) + service.import_klasses + + expect(User.count).to eq(2) + expect(User.find_by(email: 'acjpjvjp@gmail.com')).to be_present + expect(User.find_by(email: 'andrecarvalhoroure@gmail.com')).to be_present + end + + it 'cria as associações ClassMember' do + service = ImportService.new(file_path) + service.import_klasses + + klass = Klass.find_by(code: 'CIC0097') + expect(klass.class_members.count).to eq(2) + expect(klass.class_members.all? { |cm| cm.role == 'dicente' }).to be true + end + + it 'popula campos do usuário corretamente' do + service = ImportService.new(file_path) + service.import_klasses + + user = User.find_by(email: 'acjpjvjp@gmail.com') + + expect(user.name).to eq('Ana Clara Jordao Perna') + expect(user.matricula).to eq('190084006') + expect(user.curso).to eq('CIÊNCIA DA COMPUTAÇÃO/CIC') + expect(user.formacao).to eq('graduando') + expect(user.ocupacao).to eq('dicente') + end + end + + context 'com múltiplas turmas' do + let(:file_path) { temp_dir.join('multiple_klasses.json') } + + before do + File.write(file_path, JSON.generate([ + { + "code" => "CIC0097", + "name" => "BANCOS DE DADOS", + "class" => { "classCode" => "TA", "semester" => "2021.2", "time" => "35T45" }, + "dicente" => [ + { + "nome" => "Student 1", + "curso" => "CIC", + "matricula" => "001", + "usuario" => "001", + "formacao" => "graduando", + "ocupacao" => "dicente", + "email" => "student1@example.com" + } + ] + }, + { + "code" => "CIC0105", + "name" => "ENGENHARIA DE SOFTWARE", + "class" => { "classCode" => "TB", "semester" => "2021.2", "time" => "35M12" }, + "dicente" => [ + { + "nome" => "Student 2", + "curso" => "CIC", + "matricula" => "002", + "usuario" => "002", + "formacao" => "graduando", + "ocupacao" => "dicente", + "email" => "student2@example.com" + } + ] + } + ])) + end + + it 'importa múltiplas turmas' do + service = ImportService.new(file_path) + result = service.import_klasses + + expect(result[:imported]).to eq(2) + expect(Klass.count).to eq(2) + end + end + + context 'com arquivo JSON inválido' do + let(:file_path) { temp_dir.join('invalid.json') } + + before do + File.write(file_path, 'invalid json {') + end + + it 'retorna erro' do + service = ImportService.new(file_path) + result = service.import_klasses + + expect(result[:success]).to be false + expect(result[:error]).to include('Erro ao ler arquivo JSON') + end + end + + context 'com arquivo não encontrado' do + let(:file_path) { temp_dir.join('nonexistent.json') } + + it 'retorna erro' do + service = ImportService.new(file_path) + result = service.import_klasses + + expect(result[:success]).to be false + expect(result[:error]).to include('Arquivo não encontrado') + end + end + + context 'quando estudante já existe' do + let(:file_path) { temp_dir.join('existing_student.json') } + + before do + # Criar usuário pré-existente + create(:user, email: 'existing@example.com', name: 'Existing Student') + + File.write(file_path, JSON.generate([ + { + "code" => "CIC0097", + "name" => "BANCOS DE DADOS", + "class" => { "classCode" => "TA", "semester" => "2021.2", "time" => "35T45" }, + "dicente" => [ + { + "nome" => "New Name", + "curso" => "CIC", + "matricula" => "999", + "usuario" => "999", + "formacao" => "graduando", + "ocupacao" => "dicente", + "email" => "existing@example.com" + } + ] + } + ])) + end + + it 'reutiliza o usuário existente' do + expect(User.count).to eq(1) + + service = ImportService.new(file_path) + service.import_klasses + + expect(User.count).to eq(1) + expect(User.find_by(email: 'existing@example.com').name).to eq('Existing Student') + end + + it 'cria ClassMember para usuário existente' do + service = ImportService.new(file_path) + service.import_klasses + + user = User.find_by(email: 'existing@example.com') + klass = Klass.find_by(code: 'CIC0097') + + expect(ClassMember.find_by(user: user, klass: klass)).to be_present + end + end + + context 'quando estudante já está na turma' do + let(:file_path) { temp_dir.join('duplicate_enrollment.json') } + + before do + File.write(file_path, JSON.generate([ + { + "code" => "CIC0097", + "name" => "BANCOS DE DADOS", + "class" => { "classCode" => "TA", "semester" => "2021.2", "time" => "35T45" }, + "dicente" => [ + { + "nome" => "Student", + "curso" => "CIC", + "matricula" => "001", + "usuario" => "001", + "formacao" => "graduando", + "ocupacao" => "dicente", + "email" => "student@example.com" + } + ] + } + ])) + end + + it 'não duplica ClassMember na segunda importação' do + service = ImportService.new(file_path) + service.import_klasses + + expect(ClassMember.count).to eq(1) + + # Segunda importação + service2 = ImportService.new(file_path) + service2.import_klasses + + expect(ClassMember.count).to eq(1) + end + end + end +end \ No newline at end of file From 91330dce2dff9a2eaeaf20c0dfc06a1379da538c Mon Sep 17 00:00:00 2001 From: bg1777 Date: Sun, 7 Dec 2025 22:46:23 -0300 Subject: [PATCH 25/63] =?UTF-8?q?feat:=20Importa=C3=A7=C3=A3o=20de=20dados?= =?UTF-8?q?=20do=20SIGAA=20completa!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/imports_controller.rb | 52 +++++++ app/services/import_service.rb | 3 +- app/views/admin/dashboard/index.html.erb | 11 +- app/views/admin/imports/index.html.erb | 163 ++++++++++++++++++++ class_members2.json | 22 +++ config/routes.rb | 9 +- 6 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 app/controllers/admin/imports_controller.rb create mode 100644 app/views/admin/imports/index.html.erb create mode 100644 class_members2.json diff --git a/app/controllers/admin/imports_controller.rb b/app/controllers/admin/imports_controller.rb new file mode 100644 index 0000000000..446fd9246c --- /dev/null +++ b/app/controllers/admin/imports_controller.rb @@ -0,0 +1,52 @@ +# app/controllers/admin/imports_controller.rb + +module Admin + class ImportsController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + + def index + @total_klasses = Klass.count + @total_users = User.where(role: :user).count + end + + def import_klasses + if params[:file].blank? + redirect_to admin_imports_path, alert: 'Por favor, selecione um arquivo' + return + end + + file = params[:file] + + # Validar tipo de arquivo + unless file.content_type == 'application/json' || file.original_filename.end_with?('.json') + redirect_to admin_imports_path, alert: 'Por favor, envie um arquivo JSON válido' + return + end + + # Executar importação + service = ImportService.new(file.path) + result = service.import_klasses + + if result[:success] + message = "✅ #{result[:imported]} turma(s) importada(s) com sucesso!" + + if result[:errors].present? + message += "\n\n⚠️ Aviso: #{result[:errors].count} erro(s) durante importação:" + result[:errors].each { |error| message += "\n• #{error}" } + redirect_to admin_imports_path, alert: message + else + redirect_to admin_imports_path, notice: message + end + else + redirect_to admin_imports_path, alert: "❌ Erro na importação: #{result[:error]}" + end + end + + private + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end \ No newline at end of file diff --git a/app/services/import_service.rb b/app/services/import_service.rb index c52e133951..9c51bfd4c0 100644 --- a/app/services/import_service.rb +++ b/app/services/import_service.rb @@ -78,7 +78,8 @@ def find_or_create_user(user_data) return user end - password = SecureRandom.hex(12) + # Usa matrícula como senha (Opção A) + password = user_data['matricula'] user = User.new( email: user_data['email'], diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index 1b5f4c6257..6afafbd887 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -6,7 +6,7 @@ <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %>
-
+
@@ -25,6 +25,15 @@
+ + +

Usuários Recentes

diff --git a/app/views/admin/imports/index.html.erb b/app/views/admin/imports/index.html.erb new file mode 100644 index 0000000000..41bfbf00d0 --- /dev/null +++ b/app/views/admin/imports/index.html.erb @@ -0,0 +1,163 @@ + + +
+
+

Importar Dados do SIGAA

+ <%= link_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> +
+ + + <% if notice.present? %> + + <% end %> + + <% if alert.present? %> + + <% end %> + + +
+
+
+
+
Total de Turmas
+

+ + <%= @total_klasses %> + +

+
+
+
+ +
+
+
+
Total de Alunos
+

+ + <%= @total_users %> + +

+
+
+
+
+ + +
+
+
📤 Importar Arquivo JSON
+
+
+ <%= form_with url: import_klasses_admin_imports_path, method: :post, local: true, multipart: true do |f| %> +
+ + <%= f.file_field :file, class: 'form-control', accept: '.json', required: true %> + + Formato: JSON com estrutura de turmas e alunos do SIGAA + +
+ +
+ +
+ +
+ <%= f.submit "🚀 Importar Turmas e Alunos", class: 'btn btn-primary btn-lg' %> +
+ <% end %> +
+
+ + +
+
+
📋 Instruções de Uso
+
+
+
Estrutura do arquivo JSON esperada:
+
[
+  {
+    "code": "CIC0097",
+    "name": "BANCOS DE DADOS",
+    "class": {
+      "classCode": "TA",
+      "semester": "2021.2",
+      "time": "35T45"
+    },
+    "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"
+      }
+    ]
+  }
+]
+ +
Após a importação:
+
    +
  • ✅ Turmas serão criadas/atualizadas
  • +
  • ✅ Alunos serão criados com sua matrícula como senha inicial
  • +
  • ✅ Alunos serão vinculados às turmas
  • +
  • ℹ️ Qualquer erro será reportado para verificação
  • +
+ +
Acesso dos alunos:
+
    +
  • Email: conforme especificado no JSON
  • +
  • Senha: sua matrícula (ex: 190084006)
  • +
  • ⚠️ Aluno pode mudar a senha após primeiro login
  • +
+
+
+ + +
+ <%= link_to '← Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
+
+ + diff --git a/class_members2.json b/class_members2.json new file mode 100644 index 0000000000..9092ece9f1 --- /dev/null +++ b/class_members2.json @@ -0,0 +1,22 @@ +[ + { + "code": "CIC0105", + "name": "ENGENHARIA DE SOFTWARE", + "class": { + "classCode": "TA", + "semester": "2025.2", + "time": "24N34" + }, + "dicente": [ + { + "nome": "Bernardo Gomes Rodrigues", + "curso": "ENGENHARIA DA COMPUTAÇÃO/CIC", + "matricula": "231034190", + "usuario": "231034190", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "bgrodrigues2005@gmail.com" + } + ] + } +] \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 28ce881f8e..7a098b8f20 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,16 @@ -# config/routes.rb - Rails.application.routes.draw do - devise_for :users, skip: [:registrations] # Desabilita registro nas rotas + devise_for :users, skip: [:registrations] root "home#index" namespace :admin do root "dashboard#index" resources :users + resources :imports, only: [:index] do + collection do + post :import_klasses + end + end end get "home", to: "home#index" From 8976c2922bd88387659fc3e8b370147c453638af Mon Sep 17 00:00:00 2001 From: bg1777 Date: Mon, 8 Dec 2025 00:26:26 -0300 Subject: [PATCH 26/63] feat: models implementados e testados com sucesso --- app/models/form.rb | 23 ++++ app/models/form_answer.rb | 10 ++ app/models/form_response.rb | 21 +++ app/models/form_template.rb | 13 ++ app/models/form_template_field.rb | 11 ++ app/models/klass.rb | 1 + app/models/user.rb | 2 + class_members2.json | 9 ++ class_members3.json | 31 +++++ .../20251208031921_create_form_templates.rb | 11 ++ ...51208031922_create_form_template_fields.rb | 14 ++ db/migrate/20251208031923_create_forms.rb | 14 ++ .../20251208031924_create_form_responses.rb | 11 ++ .../20251208031925_create_form_answers.rb | 11 ++ db/schema.rb | 64 ++++++++- spec/factories/form_answers.rb | 9 ++ spec/factories/form_responses.rb | 9 ++ spec/factories/form_template_fields.rb | 12 ++ spec/factories/form_templates.rb | 9 ++ spec/factories/forms.rb | 12 ++ spec/models/form_answer_spec.rb | 127 ++++++++++++++++++ spec/models/form_response_spec.rb | 83 ++++++++++++ spec/models/form_spec.rb | 94 +++++++++++++ spec/models/form_template_field_spec.rb | 77 +++++++++++ spec/models/form_template_spec.rb | 57 ++++++++ 25 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 app/models/form.rb create mode 100644 app/models/form_answer.rb create mode 100644 app/models/form_response.rb create mode 100644 app/models/form_template.rb create mode 100644 app/models/form_template_field.rb create mode 100644 class_members3.json create mode 100644 db/migrate/20251208031921_create_form_templates.rb create mode 100644 db/migrate/20251208031922_create_form_template_fields.rb create mode 100644 db/migrate/20251208031923_create_forms.rb create mode 100644 db/migrate/20251208031924_create_form_responses.rb create mode 100644 db/migrate/20251208031925_create_form_answers.rb create mode 100644 spec/factories/form_answers.rb create mode 100644 spec/factories/form_responses.rb create mode 100644 spec/factories/form_template_fields.rb create mode 100644 spec/factories/form_templates.rb create mode 100644 spec/factories/forms.rb create mode 100644 spec/models/form_answer_spec.rb create mode 100644 spec/models/form_response_spec.rb create mode 100644 spec/models/form_spec.rb create mode 100644 spec/models/form_template_field_spec.rb create mode 100644 spec/models/form_template_spec.rb diff --git a/app/models/form.rb b/app/models/form.rb new file mode 100644 index 0000000000..39d333e688 --- /dev/null +++ b/app/models/form.rb @@ -0,0 +1,23 @@ +# app/models/form.rb + +class Form < ApplicationRecord + belongs_to :form_template + belongs_to :klass + has_many :form_responses, dependent: :destroy + + validates :form_template_id, presence: true + validates :klass_id, presence: true + validates :title, presence: true + + enum :status, { draft: 0, published: 1, closed: 2 } + + # Retorna todos os alunos da turma que ainda não responderam o formulário + def pending_responses + klass.students - form_responses.map(&:user) + end + + # Retorna alunos que já responderam + def completed_responses + form_responses.map(&:user) + end +end \ No newline at end of file diff --git a/app/models/form_answer.rb b/app/models/form_answer.rb new file mode 100644 index 0000000000..530c75e1f8 --- /dev/null +++ b/app/models/form_answer.rb @@ -0,0 +1,10 @@ +# app/models/form_answer.rb + +class FormAnswer < ApplicationRecord + belongs_to :form_response + belongs_to :form_template_field + + validates :form_response_id, presence: true + validates :form_template_field_id, presence: true + validates :answer, presence: true +end \ No newline at end of file diff --git a/app/models/form_response.rb b/app/models/form_response.rb new file mode 100644 index 0000000000..5b4311f064 --- /dev/null +++ b/app/models/form_response.rb @@ -0,0 +1,21 @@ +# app/models/form_response.rb + +class FormResponse < ApplicationRecord + belongs_to :form + belongs_to :user + has_many :form_answers, dependent: :destroy + + validates :form_id, presence: true + validates :user_id, presence: true + validates :user_id, uniqueness: { scope: :form_id, message: 'já respondeu este formulário' } + + accepts_nested_attributes_for :form_answers + + def completed? + submitted_at.present? + end + + def pending? + !completed? + end +end \ No newline at end of file diff --git a/app/models/form_template.rb b/app/models/form_template.rb new file mode 100644 index 0000000000..53b7b867aa --- /dev/null +++ b/app/models/form_template.rb @@ -0,0 +1,13 @@ +# app/models/form_template.rb + +class FormTemplate < ApplicationRecord + belongs_to :user + has_many :form_template_fields, dependent: :destroy + has_many :forms, dependent: :destroy + + validates :name, presence: true + validates :description, presence: true + validates :user_id, presence: true + + accepts_nested_attributes_for :form_template_fields, allow_destroy: true +end \ No newline at end of file diff --git a/app/models/form_template_field.rb b/app/models/form_template_field.rb new file mode 100644 index 0000000000..38b3c2b7db --- /dev/null +++ b/app/models/form_template_field.rb @@ -0,0 +1,11 @@ +# app/models/form_template_field.rb + +class FormTemplateField < ApplicationRecord + belongs_to :form_template + has_many :form_answers, dependent: :destroy + + validates :field_type, presence: true, inclusion: { in: %w(text textarea number email multiple_choice), message: '%{value} is not a valid field type' } + validates :label, presence: true + validates :position, presence: true + validates :form_template_id, presence: true +end \ No newline at end of file diff --git a/app/models/klass.rb b/app/models/klass.rb index efc2531097..3de95b22c5 100644 --- a/app/models/klass.rb +++ b/app/models/klass.rb @@ -4,6 +4,7 @@ class Klass < ApplicationRecord # Associações has_many :class_members, dependent: :destroy has_many :users, through: :class_members + has_many :forms, dependent: :destroy # Validações validates :code, presence: true, uniqueness: true diff --git a/app/models/user.rb b/app/models/user.rb index c5d13a7b39..89dc46d837 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,8 @@ class User < ApplicationRecord # Associações has_many :class_members, dependent: :destroy has_many :klasses, through: :class_members + has_many :form_templates, dependent: :destroy + has_many :form_responses, dependent: :destroy # Validações validates :name, presence: true diff --git a/class_members2.json b/class_members2.json index 9092ece9f1..51b18547c3 100644 --- a/class_members2.json +++ b/class_members2.json @@ -16,6 +16,15 @@ "formacao": "graduando", "ocupacao": "dicente", "email": "bgrodrigues2005@gmail.com" + }, + { + "nome": "Ana Clara Jordao Perna", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190084006", + "usuario": "190084006", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "acjpjvjp@gmail.com" } ] } diff --git a/class_members3.json b/class_members3.json new file mode 100644 index 0000000000..7ea97adf0d --- /dev/null +++ b/class_members3.json @@ -0,0 +1,31 @@ +[ + { + "code": "CIC0104", + "name": "SOFTWARE BASICO", + "class": { + "classCode": "TA", + "semester": "2025.2", + "time": "24M34" + }, + "dicente": [ + { + "nome": "Bernardo Gomes Rodrigues", + "curso": "ENGENHARIA DA COMPUTAÇÃO/CIC", + "matricula": "231034190", + "usuario": "231034190", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "bgrodrigues2005@gmail.com" + }, + { + "nome": "Ana Clara Jordao Perna", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190084006", + "usuario": "190084006", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "acjpjvjp@gmail.com" + } + ] + } +] \ No newline at end of file diff --git a/db/migrate/20251208031921_create_form_templates.rb b/db/migrate/20251208031921_create_form_templates.rb new file mode 100644 index 0000000000..0bc2016630 --- /dev/null +++ b/db/migrate/20251208031921_create_form_templates.rb @@ -0,0 +1,11 @@ +class CreateFormTemplates < ActiveRecord::Migration[8.0] + def change + create_table :form_templates do |t| + t.string :name + t.text :description + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031922_create_form_template_fields.rb b/db/migrate/20251208031922_create_form_template_fields.rb new file mode 100644 index 0000000000..f25f2ab4aa --- /dev/null +++ b/db/migrate/20251208031922_create_form_template_fields.rb @@ -0,0 +1,14 @@ +class CreateFormTemplateFields < ActiveRecord::Migration[8.0] + def change + create_table :form_template_fields do |t| + t.references :form_template, null: false, foreign_key: true + t.string :field_type + t.string :label + t.boolean :required + t.json :options + t.integer :position + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031923_create_forms.rb b/db/migrate/20251208031923_create_forms.rb new file mode 100644 index 0000000000..617a29eec5 --- /dev/null +++ b/db/migrate/20251208031923_create_forms.rb @@ -0,0 +1,14 @@ +class CreateForms < ActiveRecord::Migration[8.0] + def change + create_table :forms do |t| + t.references :form_template, null: false, foreign_key: true + t.references :klass, null: false, foreign_key: true + t.string :title + t.text :description + t.datetime :due_date + t.integer :status + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031924_create_form_responses.rb b/db/migrate/20251208031924_create_form_responses.rb new file mode 100644 index 0000000000..7edf9dd3a3 --- /dev/null +++ b/db/migrate/20251208031924_create_form_responses.rb @@ -0,0 +1,11 @@ +class CreateFormResponses < ActiveRecord::Migration[8.0] + def change + create_table :form_responses do |t| + t.references :form, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + t.datetime :submitted_at + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031925_create_form_answers.rb b/db/migrate/20251208031925_create_form_answers.rb new file mode 100644 index 0000000000..6f8eae517a --- /dev/null +++ b/db/migrate/20251208031925_create_form_answers.rb @@ -0,0 +1,11 @@ +class CreateFormAnswers < ActiveRecord::Migration[8.0] + def change + create_table :form_answers do |t| + t.references :form_response, null: false, foreign_key: true + t.references :form_template_field, null: false, foreign_key: true + t.text :answer + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 99430c3cb8..577eba3086 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_12_08_005930) do +ActiveRecord::Schema[8.0].define(version: 2025_12_08_031925) do create_table "class_members", force: :cascade do |t| t.integer "user_id", null: false t.integer "klass_id", null: false @@ -21,6 +21,60 @@ t.index ["user_id"], name: "index_class_members_on_user_id" end + create_table "form_answers", force: :cascade do |t| + t.integer "form_response_id", null: false + t.integer "form_template_field_id", null: false + t.text "answer" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["form_response_id"], name: "index_form_answers_on_form_response_id" + t.index ["form_template_field_id"], name: "index_form_answers_on_form_template_field_id" + end + + create_table "form_responses", force: :cascade do |t| + t.integer "form_id", null: false + t.integer "user_id", null: false + t.datetime "submitted_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["form_id"], name: "index_form_responses_on_form_id" + t.index ["user_id"], name: "index_form_responses_on_user_id" + end + + create_table "form_template_fields", force: :cascade do |t| + t.integer "form_template_id", null: false + t.string "field_type" + t.string "label" + t.boolean "required" + t.json "options" + t.integer "position" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["form_template_id"], name: "index_form_template_fields_on_form_template_id" + end + + create_table "form_templates", force: :cascade do |t| + t.string "name" + t.text "description" + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_form_templates_on_user_id" + end + + create_table "forms", force: :cascade do |t| + t.integer "form_template_id", null: false + t.integer "klass_id", null: false + t.string "title" + t.text "description" + t.datetime "due_date" + t.integer "status" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["form_template_id"], name: "index_forms_on_form_template_id" + t.index ["klass_id"], name: "index_forms_on_klass_id" + end + create_table "klasses", force: :cascade do |t| t.string "code" t.string "name" @@ -50,4 +104,12 @@ add_foreign_key "class_members", "klasses" add_foreign_key "class_members", "users" + add_foreign_key "form_answers", "form_responses" + add_foreign_key "form_answers", "form_template_fields" + add_foreign_key "form_responses", "forms" + add_foreign_key "form_responses", "users" + add_foreign_key "form_template_fields", "form_templates" + add_foreign_key "form_templates", "users" + add_foreign_key "forms", "form_templates" + add_foreign_key "forms", "klasses" end diff --git a/spec/factories/form_answers.rb b/spec/factories/form_answers.rb new file mode 100644 index 0000000000..4dd59fc2da --- /dev/null +++ b/spec/factories/form_answers.rb @@ -0,0 +1,9 @@ +# spec/factories/form_answers.rb + +FactoryBot.define do + factory :form_answer do + association :form_response + association :form_template_field + answer { Faker::Lorem.sentence } + end +end \ No newline at end of file diff --git a/spec/factories/form_responses.rb b/spec/factories/form_responses.rb new file mode 100644 index 0000000000..a99a941798 --- /dev/null +++ b/spec/factories/form_responses.rb @@ -0,0 +1,9 @@ +# spec/factories/form_responses.rb + +FactoryBot.define do + factory :form_response do + association :form + association :user + submitted_at { nil } + end +end \ No newline at end of file diff --git a/spec/factories/form_template_fields.rb b/spec/factories/form_template_fields.rb new file mode 100644 index 0000000000..b8afe559c8 --- /dev/null +++ b/spec/factories/form_template_fields.rb @@ -0,0 +1,12 @@ +# spec/factories/form_template_fields.rb + +FactoryBot.define do + factory :form_template_field do + association :form_template + field_type { 'text' } + label { Faker::Lorem.word } + required { false } + options { [] } + position { 1 } + end +end \ No newline at end of file diff --git a/spec/factories/form_templates.rb b/spec/factories/form_templates.rb new file mode 100644 index 0000000000..cf66d49ad9 --- /dev/null +++ b/spec/factories/form_templates.rb @@ -0,0 +1,9 @@ +# spec/factories/form_templates.rb + +FactoryBot.define do + factory :form_template do + name { Faker::Lorem.sentence } + description { Faker::Lorem.paragraph } + association :user, factory: :user + end +end \ No newline at end of file diff --git a/spec/factories/forms.rb b/spec/factories/forms.rb new file mode 100644 index 0000000000..fbb05c9858 --- /dev/null +++ b/spec/factories/forms.rb @@ -0,0 +1,12 @@ +# spec/factories/forms.rb + +FactoryBot.define do + factory :form do + association :form_template + association :klass + title { Faker::Lorem.sentence } + description { Faker::Lorem.paragraph } + due_date { 7.days.from_now } + status { 0 } + end +end \ No newline at end of file diff --git a/spec/models/form_answer_spec.rb b/spec/models/form_answer_spec.rb new file mode 100644 index 0000000000..261a0d6357 --- /dev/null +++ b/spec/models/form_answer_spec.rb @@ -0,0 +1,127 @@ +# spec/models/form_answer_spec.rb + +require 'rails_helper' + +RSpec.describe FormAnswer, type: :model do + let(:user) { create(:user, role: :admin) } + let(:student) { create(:user, role: :user) } + let(:klass) { create(:klass) } + let(:form_template) { create(:form_template, user: user) } + let(:form) { create(:form, form_template: form_template, klass: klass) } + let(:field) { create(:form_template_field, form_template: form_template, field_type: 'text', position: 1) } + let(:form_response) { create(:form_response, form: form, user: student) } + let(:form_answer) { create(:form_answer, form_response: form_response, form_template_field: field) } + + describe 'associations' do + it { is_expected.to belong_to(:form_response) } + it { is_expected.to belong_to(:form_template_field) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:form_response_id) } + it { is_expected.to validate_presence_of(:form_template_field_id) } + it { is_expected.to validate_presence_of(:answer) } + end + + describe '#create' do + it 'cria resposta válida' do + expect { + create(:form_answer, + form_response: form_response, + form_template_field: field, + answer: 'Resposta do aluno') + }.to change(FormAnswer, :count).by(1) + end + + it 'não cria answer sem form_response' do + answer = build(:form_answer, + form_response: nil, + form_template_field: field) + expect(answer).not_to be_valid + end + + it 'não cria answer sem form_template_field' do + answer = build(:form_answer, + form_response: form_response, + form_template_field: nil) + expect(answer).not_to be_valid + end + + it 'não cria answer sem resposta' do + answer = build(:form_answer, + form_response: form_response, + form_template_field: field, + answer: nil) + expect(answer).not_to be_valid + end + end + + describe 'resposta de diferentes tipos de campo' do + it 'pode armazenar resposta de campo text' do + text_field = create(:form_template_field, + form_template: form_template, + field_type: 'text', + position: 1) + answer = create(:form_answer, + form_response: form_response, + form_template_field: text_field, + answer: 'Resposta simples') + + expect(answer.answer).to eq('Resposta simples') + end + + it 'pode armazenar resposta de campo textarea' do + textarea_field = create(:form_template_field, + form_template: form_template, + field_type: 'textarea', + position: 1) + long_answer = 'Esta é uma resposta muito longa que pode conter múltiplas linhas' + answer = create(:form_answer, + form_response: form_response, + form_template_field: textarea_field, + answer: long_answer) + + expect(answer.answer).to eq(long_answer) + end + + it 'pode armazenar resposta de campo email' do + email_field = create(:form_template_field, + form_template: form_template, + field_type: 'email', + position: 1) + answer = create(:form_answer, + form_response: form_response, + form_template_field: email_field, + answer: 'student@example.com') + + expect(answer.answer).to eq('student@example.com') + end + + it 'pode armazenar resposta de campo number' do + number_field = create(:form_template_field, + form_template: form_template, + field_type: 'number', + position: 1) + answer = create(:form_answer, + form_response: form_response, + form_template_field: number_field, + answer: '42') + + expect(answer.answer).to eq('42') + end + + it 'pode armazenar resposta de campo multiple_choice' do + choice_field = create(:form_template_field, + form_template: form_template, + field_type: 'multiple_choice', + position: 1, + options: ['Opção A', 'Opção B', 'Opção C']) + answer = create(:form_answer, + form_response: form_response, + form_template_field: choice_field, + answer: 'Opção B') + + expect(answer.answer).to eq('Opção B') + end + end +end \ No newline at end of file diff --git a/spec/models/form_response_spec.rb b/spec/models/form_response_spec.rb new file mode 100644 index 0000000000..0db5efd50b --- /dev/null +++ b/spec/models/form_response_spec.rb @@ -0,0 +1,83 @@ +# spec/models/form_response_spec.rb + +require 'rails_helper' + +RSpec.describe FormResponse, type: :model do + let(:user) { create(:user, role: :admin) } + let(:student) { create(:user, role: :user) } + let(:klass) { create(:klass) } + let(:form_template) { create(:form_template, user: user) } + let(:form) { create(:form, form_template: form_template, klass: klass) } + let(:form_response) { create(:form_response, form: form, user: student) } + + describe 'associations' do + it { is_expected.to belong_to(:form) } + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:form_answers).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:form_id) } + it { is_expected.to validate_presence_of(:user_id) } + end + + describe 'uniqueness validation' do + it 'não permite duplicar resposta de mesmo user para mesmo form' do + create(:form_response, form: form, user: student) + + duplicate = build(:form_response, form: form, user: student) + expect(duplicate).not_to be_valid + end + + it 'permite mesmo user responder forms diferentes' do + form2 = create(:form, form_template: form_template, klass: klass) + + response1 = create(:form_response, form: form, user: student) + response2 = build(:form_response, form: form2, user: student) + + expect(response2).to be_valid + end + end + + describe '#create' do + it 'cria resposta válida' do + expect { + create(:form_response, form: form, user: student) + }.to change(FormResponse, :count).by(1) + end + + it 'não cria resposta sem form' do + response = build(:form_response, form: nil, user: student) + expect(response).not_to be_valid + end + + it 'não cria resposta sem user' do + response = build(:form_response, form: form, user: nil) + expect(response).not_to be_valid + end + end + + describe '#completed?' do + it 'retorna false quando submitted_at é nil' do + response = create(:form_response, form: form, user: student, submitted_at: nil) + expect(response.completed?).to be false + end + + it 'retorna true quando submitted_at está presente' do + response = create(:form_response, form: form, user: student, submitted_at: Time.current) + expect(response.completed?).to be true + end + end + + describe '#pending?' do + it 'retorna true quando resposta não foi enviada' do + response = create(:form_response, form: form, user: student, submitted_at: nil) + expect(response.pending?).to be true + end + + it 'retorna false quando resposta foi enviada' do + response = create(:form_response, form: form, user: student, submitted_at: Time.current) + expect(response.pending?).to be false + end + end +end \ No newline at end of file diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb new file mode 100644 index 0000000000..6d6890b00e --- /dev/null +++ b/spec/models/form_spec.rb @@ -0,0 +1,94 @@ +# spec/models/form_spec.rb + +require 'rails_helper' + +RSpec.describe Form, type: :model do + let(:user) { create(:user, role: :admin) } + let(:klass) { create(:klass) } + let(:form_template) { create(:form_template, user: user) } + let(:form) { create(:form, form_template: form_template, klass: klass) } + + describe 'associations' do + it { is_expected.to belong_to(:form_template) } + it { is_expected.to belong_to(:klass) } + it { is_expected.to have_many(:form_responses).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:form_template_id) } + it { is_expected.to validate_presence_of(:klass_id) } + it { is_expected.to validate_presence_of(:title) } + end + + describe '#create' do + it 'cria um formulário válido' do + expect { + create(:form, form_template: form_template, klass: klass) + }.to change(Form, :count).by(1) + end + + it 'não cria form sem título' do + form_obj = build(:form, title: nil, form_template: form_template, klass: klass) + expect(form_obj).not_to be_valid + end + + it 'não cria form sem template' do + form_obj = build(:form, form_template: nil, klass: klass) + expect(form_obj).not_to be_valid + end + + it 'não cria form sem turma' do + form_obj = build(:form, form_template: form_template, klass: nil) + expect(form_obj).not_to be_valid + end + end + + describe 'enum status' do + it 'tem draft como status padrão' do + form_obj = create(:form, form_template: form_template, klass: klass) + expect(form_obj.status).to eq('draft') + end + + it 'pode ter status published' do + form.update(status: :published) + expect(form.published?).to be true + end + + it 'pode ter status closed' do + form.update(status: :closed) + expect(form.closed?).to be true + end + end + + describe '#pending_responses' do + it 'retorna alunos que não responderam' do + student1 = create(:user, role: :user) + student2 = create(:user, role: :user) + + create(:class_member, user: student1, klass: klass, role: 'dicente') + create(:class_member, user: student2, klass: klass, role: 'dicente') + + create(:form_response, form: form, user: student1) + + pending = form.pending_responses + expect(pending).to include(student2) + expect(pending).not_to include(student1) + end + end + + describe '#completed_responses' do + it 'retorna alunos que responderam' do + student1 = create(:user, role: :user) + student2 = create(:user, role: :user) + + create(:class_member, user: student1, klass: klass, role: 'dicente') + create(:class_member, user: student2, klass: klass, role: 'dicente') + + create(:form_response, form: form, user: student1) + + completed = form.completed_responses + expect(completed).to include(student1) + expect(completed).not_to include(student2) + end + end +end \ No newline at end of file diff --git a/spec/models/form_template_field_spec.rb b/spec/models/form_template_field_spec.rb new file mode 100644 index 0000000000..8a55e2bbd8 --- /dev/null +++ b/spec/models/form_template_field_spec.rb @@ -0,0 +1,77 @@ +# spec/models/form_template_field_spec.rb + +require 'rails_helper' + +RSpec.describe FormTemplateField, type: :model do + let(:user) { create(:user, role: :admin) } + let(:form_template) { create(:form_template, user: user) } + let(:field) do + create(:form_template_field, + form_template: form_template, + field_type: 'text', + label: 'Nome', + position: 1) + end + + describe 'associations' do + it { is_expected.to belong_to(:form_template) } + it { is_expected.to have_many(:form_answers).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:field_type) } + it { is_expected.to validate_presence_of(:label) } + it { is_expected.to validate_presence_of(:position) } + it { is_expected.to validate_presence_of(:form_template_id) } + end + + describe 'field_type validation' do + it 'aceita field_type válido' do + field = build(:form_template_field, + form_template: form_template, + field_type: 'text') + expect(field).to be_valid + end + + it 'rejeita field_type inválido' do + field = build(:form_template_field, + form_template: form_template, + field_type: 'invalid') + expect(field).not_to be_valid + end + + it 'aceita todos os tipos válidos' do + valid_types = %w(text textarea number email multiple_choice) + valid_types.each do |type| + field = build(:form_template_field, + form_template: form_template, + field_type: type) + expect(field).to be_valid + end + end + end + + describe '#create' do + it 'cria field com sucesso' do + expect { + create(:form_template_field, + form_template: form_template, + field_type: 'text') + }.to change(FormTemplateField, :count).by(1) + end + + it 'não cria field sem label' do + field = build(:form_template_field, + form_template: form_template, + label: nil) + expect(field).not_to be_valid + end + + it 'não cria field sem position' do + field = build(:form_template_field, + form_template: form_template, + position: nil) + expect(field).not_to be_valid + end + end +end \ No newline at end of file diff --git a/spec/models/form_template_spec.rb b/spec/models/form_template_spec.rb new file mode 100644 index 0000000000..9b1619e3e6 --- /dev/null +++ b/spec/models/form_template_spec.rb @@ -0,0 +1,57 @@ +# spec/models/form_template_spec.rb + +require 'rails_helper' + +RSpec.describe FormTemplate, type: :model do + let(:user) { create(:user, role: :admin) } + let(:form_template) { create(:form_template, user: user) } + + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:form_template_fields).dependent(:destroy) } + it { is_expected.to have_many(:forms).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:user_id) } + end + + describe '#create' do + it 'cria um template válido' do + expect { + create(:form_template, user: user) + }.to change(FormTemplate, :count).by(1) + end + + it 'não cria template sem nome' do + template = build(:form_template, name: nil, user: user) + expect(template).not_to be_valid + end + + it 'não cria template sem descrição' do + template = build(:form_template, description: nil, user: user) + expect(template).not_to be_valid + end + + it 'não cria template sem user' do + template = build(:form_template, user: nil) + expect(template).not_to be_valid + end + end + + describe '#accepts_nested_attributes_for' do + it 'permite criar fields aninhados' do + template = create(:form_template, user: user) + template.form_template_fields.create!( + field_type: 'text', + label: 'Nome Completo', + required: true, + position: 1 + ) + + expect(template.form_template_fields.count).to eq(1) + end + end +end \ No newline at end of file From 93d9e4471574613565f22be31ef9595ebeabeadc Mon Sep 17 00:00:00 2001 From: bg1777 Date: Mon, 8 Dec 2025 01:28:50 -0300 Subject: [PATCH 27/63] feat: inicio das implementacoes de views para templates --- app/assets/javascripts/form_fields.js | 14 +++ .../admin/form_templates_controller.rb | 65 ++++++++++++++ app/controllers/admin/forms_controller.rb | 90 +++++++++++++++++++ app/helpers/admin/form_templates_helper.rb | 15 ++++ app/views/admin/dashboard/index.html.erb | 8 ++ .../_form_template_field_form.html.erb | 46 ++++++++++ app/views/admin/form_templates/edit.html.erb | 48 ++++++++++ app/views/admin/form_templates/index.html.erb | 44 +++++++++ app/views/admin/form_templates/new.html.erb | 49 ++++++++++ app/views/admin/form_templates/show.html.erb | 67 ++++++++++++++ config/routes.rb | 28 +++++- 11 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/form_fields.js create mode 100644 app/controllers/admin/form_templates_controller.rb create mode 100644 app/controllers/admin/forms_controller.rb create mode 100644 app/helpers/admin/form_templates_helper.rb create mode 100644 app/views/admin/form_templates/_form_template_field_form.html.erb create mode 100644 app/views/admin/form_templates/edit.html.erb create mode 100644 app/views/admin/form_templates/index.html.erb create mode 100644 app/views/admin/form_templates/new.html.erb create mode 100644 app/views/admin/form_templates/show.html.erb diff --git a/app/assets/javascripts/form_fields.js b/app/assets/javascripts/form_fields.js new file mode 100644 index 0000000000..0c01fbec7c --- /dev/null +++ b/app/assets/javascripts/form_fields.js @@ -0,0 +1,14 @@ +// app/assets/javascripts/form_fields.js + +function add_fields(link, association, content) { + var new_id = new Date().getTime(); + var regexp = new RegExp("new_" + association, "g"); + var new_fields = content.replace(regexp, new_id); + + var container = link.closest('form').querySelector("#fields-container"); + if (container) { + var temp = document.createElement('div'); + temp.innerHTML = new_fields; + container.appendChild(temp.firstElementChild); + } +} diff --git a/app/controllers/admin/form_templates_controller.rb b/app/controllers/admin/form_templates_controller.rb new file mode 100644 index 0000000000..53d6a4df12 --- /dev/null +++ b/app/controllers/admin/form_templates_controller.rb @@ -0,0 +1,65 @@ +# app/controllers/admin/form_templates_controller.rb + +module Admin + class FormTemplatesController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + before_action :set_form_template, only: [:show, :edit, :update, :destroy] + + def index + @form_templates = current_user.form_templates.order(created_at: :desc) + end + + def show + end + + def new + @form_template = FormTemplate.new + 5.times { @form_template.form_template_fields.build } + end + + def create + @form_template = current_user.form_templates.build(form_template_params) + + if @form_template.save + redirect_to admin_form_template_path(@form_template), notice: 'Template criado com sucesso!' + else + render :new, status: :unprocessable_entity + end + end + + def edit + @form_template.form_template_fields.build if @form_template.form_template_fields.empty? + end + + def update + if @form_template.update(form_template_params) + redirect_to admin_form_template_path(@form_template), notice: 'Template atualizado com sucesso!' + else + render :edit, status: :unprocessable_entity + end + end + + def destroy + @form_template.destroy + redirect_to admin_form_templates_url, notice: 'Template deletado com sucesso!' + end + + private + + def set_form_template + @form_template = FormTemplate.find(params[:id]) + end + + def form_template_params + params.require(:form_template).permit( + :name, :description, + form_template_fields_attributes: [:id, :field_type, :label, :required, :options, :position, :_destroy] + ) + end + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end \ No newline at end of file diff --git a/app/controllers/admin/forms_controller.rb b/app/controllers/admin/forms_controller.rb new file mode 100644 index 0000000000..b8a762a07b --- /dev/null +++ b/app/controllers/admin/forms_controller.rb @@ -0,0 +1,90 @@ +# app/controllers/admin/forms_controller.rb + +module Admin + class FormsController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + before_action :set_form, only: [:show, :edit, :update, :destroy, :publish, :close] + + def index + @forms = Form.all.order(created_at: :desc) + end + + def show + @pending_count = @form.pending_responses.count + @completed_count = @form.completed_responses.count + end + + def new + @form = Form.new + @form_templates = FormTemplate.all + @klasses = Klass.all + end + + def create + template = FormTemplate.find(form_params[:form_template_id]) + klass = Klass.find(form_params[:klass_id]) + + @form = template.create_form_instance( + klass, + form_params[:title], + form_params[:description], + form_params[:due_date] + ) + + if @form.save + redirect_to admin_form_path(@form), notice: 'Formulário criado com sucesso!' + else + render :new, status: :unprocessable_entity + end + end + + def edit + @form_templates = FormTemplate.all + @klasses = Klass.all + end + + def update + if @form.update(form_params) + redirect_to admin_form_path(@form), notice: 'Formulário atualizado com sucesso!' + else + render :edit, status: :unprocessable_entity + end + end + + def destroy + @form.destroy + redirect_to admin_forms_url, notice: 'Formulário deletado com sucesso!' + end + + def publish + if @form.update(status: :published) + redirect_to admin_form_path(@form), notice: 'Formulário publicado com sucesso!' + else + redirect_to admin_form_path(@form), alert: 'Erro ao publicar formulário' + end + end + + def close + if @form.update(status: :closed) + redirect_to admin_form_path(@form), notice: 'Formulário fechado com sucesso!' + else + redirect_to admin_form_path(@form), alert: 'Erro ao fechar formulário' + end + end + + private + + def set_form + @form = Form.find(params[:id]) + end + + def form_params + params.require(:form).permit(:form_template_id, :klass_id, :title, :description, :due_date, :status) + end + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end \ No newline at end of file diff --git a/app/helpers/admin/form_templates_helper.rb b/app/helpers/admin/form_templates_helper.rb new file mode 100644 index 0000000000..30c146275c --- /dev/null +++ b/app/helpers/admin/form_templates_helper.rb @@ -0,0 +1,15 @@ +# app/helpers/admin/form_templates_helper.rb + +module Admin::FormTemplatesHelper + def link_to_add_fields(name, form, association, **options) + new_object = form.object.send(association).klass.new + id = new_object.object_id + fields = form.fields_for(association, new_object, child_index: id) do |builder| + render('form_template_field_form', f: builder) + end + + link_to name, '#', + onclick: "add_fields(this, '#{association}', '#{j(fields)}'); return false;", + class: options[:class] + end +end diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index 6afafbd887..a8346f6c73 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -34,6 +34,14 @@ <%= link_to 'Importar', admin_imports_path, class: 'btn btn-primary' %> + +

Usuários Recentes

diff --git a/app/views/admin/form_templates/_form_template_field_form.html.erb b/app/views/admin/form_templates/_form_template_field_form.html.erb new file mode 100644 index 0000000000..1641e5e571 --- /dev/null +++ b/app/views/admin/form_templates/_form_template_field_form.html.erb @@ -0,0 +1,46 @@ + + +
+
+
+
+
+ <%= f.label :position, "Posição" %> + <%= f.number_field :position, class: 'form-control', min: 1 %> +
+
+ +
+
+ <%= f.label :field_type, "Tipo de Campo" %> + <%= f.select :field_type, + ['text', 'textarea', 'email', 'number', 'date', 'select', 'radio', 'checkbox'], + { prompt: 'Selecione um tipo' }, + class: 'form-select' %> +
+
+
+ +
+ <%= f.label :label, "Rótulo do Campo" %> + <%= f.text_field :label, class: 'form-control', placeholder: 'Ex: Seu Nome' %> +
+ +
+ <%= f.label :options, "Opções (JSON - para select/radio/checkbox)" %> + <%= f.text_area :options, class: 'form-control', rows: 2, placeholder: '["Opção 1", "Opção 2"]' %> +
+ +
+ <%= f.check_box :required %> + <%= f.label :required, "Campo obrigatório?" %> +
+ + <% if f.object.persisted? %> +
+ <%= f.check_box :_destroy %> + <%= f.label :_destroy, "Remover este campo" %> +
+ <% end %> +
+
diff --git a/app/views/admin/form_templates/edit.html.erb b/app/views/admin/form_templates/edit.html.erb new file mode 100644 index 0000000000..bf396b9eaf --- /dev/null +++ b/app/views/admin/form_templates/edit.html.erb @@ -0,0 +1,48 @@ + + +
+
+
+

Editar Template

+ + <%= form_with(model: [:admin, @form_template], local: true) do |form| %> + <% if @form_template.errors.any? %> +
+

<%= pluralize(@form_template.errors.count, "erro") %> encontrado:

+
    + <% @form_template.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :name, "Nome do Template" %> + <%= form.text_field :name, class: 'form-control' %> +
+ +
+ <%= form.label :description, "Descrição" %> + <%= form.text_area :description, class: 'form-control', rows: 4 %> +
+ +

Campos

+
+ <%= form.fields_for :form_template_fields do |field_form| %> + <%= render 'field_form', f: field_form %> + <% end %> +
+ +
+ <%= link_to_add_fields "Adicionar Campo", form, :form_template_fields, class: 'btn btn-sm btn-info' %> +
+ +
+ <%= form.submit "Atualizar Template", class: 'btn btn-success' %> + <%= link_to 'Cancelar', admin_form_templates_path, class: 'btn btn-secondary' %> +
+ <% end %> +
+
+
diff --git a/app/views/admin/form_templates/index.html.erb b/app/views/admin/form_templates/index.html.erb new file mode 100644 index 0000000000..29409ad088 --- /dev/null +++ b/app/views/admin/form_templates/index.html.erb @@ -0,0 +1,44 @@ + + +
+
+

Templates de Formulários

+ <%= link_to 'Novo Template', new_admin_form_template_path, class: 'btn btn-primary' %> +
+ + <% if @form_templates.any? %> +
+
+ + + + + + + + + + + <% @form_templates.each do |template| %> + + + + + + + + <% end %> + +
NomeDescriçãoCamposCriado emAções
<%= template.name %><%= truncate(template.description, length: 50) %><%= template.form_template_fields.count %><%= l(template.created_at, format: :short) %> + <%= link_to 'Ver', admin_form_template_path(template), class: 'btn btn-sm btn-info' %> + <%= link_to 'Editar', edit_admin_form_template_path(template), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Deletar', admin_form_template_path(template), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> +
+
+ <% else %> +
+ Nenhum template criado ainda. + <%= link_to 'Criar seu primeiro template', new_admin_form_template_path, class: 'alert-link' %> +
+ <% end %> +
diff --git a/app/views/admin/form_templates/new.html.erb b/app/views/admin/form_templates/new.html.erb new file mode 100644 index 0000000000..eed7af0a68 --- /dev/null +++ b/app/views/admin/form_templates/new.html.erb @@ -0,0 +1,49 @@ + + +
+
+
+

Novo Template de Formulário

+ + <%= form_with(model: [:admin, @form_template], local: true) do |form| %> + <% if @form_template.errors.any? %> +
+

<%= pluralize(@form_template.errors.count, "erro") %> encontrado:

+
    + <% @form_template.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :name, "Nome do Template" %> + <%= form.text_field :name, class: 'form-control', placeholder: 'Ex: Avaliação de Desempenho' %> +
+ +
+ <%= form.label :description, "Descrição" %> + <%= form.text_area :description, class: 'form-control', rows: 4, placeholder: 'Descreva o propósito deste template...' %> +
+ +

Campos

+
+ <%= form.fields_for :form_template_fields do |field_form| %> + <%= render 'form_template_field_form', f: field_form %> + <% end %> + +
+ +
+ <%= link_to_add_fields "Adicionar Campo", form, :form_template_fields, class: 'btn btn-sm btn-info' %> +
+ +
+ <%= form.submit "Criar Template", class: 'btn btn-success' %> + <%= link_to 'Cancelar', admin_form_templates_path, class: 'btn btn-secondary' %> +
+ <% end %> +
+
+
diff --git a/app/views/admin/form_templates/show.html.erb b/app/views/admin/form_templates/show.html.erb new file mode 100644 index 0000000000..2743cb8175 --- /dev/null +++ b/app/views/admin/form_templates/show.html.erb @@ -0,0 +1,67 @@ + + +
+
+

<%= @form_template.name %>

+
+ <%= link_to 'Editar', edit_admin_form_template_path(@form_template), class: 'btn btn-warning' %> + <%= link_to 'Voltar', admin_form_templates_path, class: 'btn btn-secondary' %> +
+
+ +
+
+
Informações
+

+ Descrição: <%= @form_template.description %> +

+

+ Total de Campos: <%= @form_template.form_template_fields.count %> +

+

+ Criado em: <%= l(@form_template.created_at, format: :long) %> +

+
+
+ +

Campos do Template

+ <% if @form_template.form_template_fields.any? %> +
+ + + + + + + + + + + + + <% @form_template.form_template_fields.order(:position).each do |field| %> + + + + + + + + + <% end %> + +
PosiçãoLabelTipoObrigatórioOpçõesAções
<%= field.position %><%= field.label %><%= field.field_type %> + <% if field.required %> + Sim + <% else %> + Não + <% end %> + <%= field.options.present? ? truncate(field.options, length: 30) : '-' %> + <%= link_to 'Editar', edit_admin_form_template_path(@form_template, id: field.id), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Deletar', admin_form_template_form_template_field_path(@form_template, field), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> +
+
+ <% else %> +
Este template não possui campos.
+ <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index 7a098b8f20..2e674b1621 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,5 @@ +# config/routes.rb + Rails.application.routes.draw do devise_for :users, skip: [:registrations] @@ -11,7 +13,31 @@ post :import_klasses end end + + # Form Templates + resources :form_templates do + resources :form_template_fields, only: [:create, :update, :destroy] + end + + # Forms + resources :forms do + member do + patch :publish + patch :close + end + end + end + + # Student namespace + namespace :student do + root "dashboard#index" + resources :forms, only: [:index, :show] do + member do + get :answer + post :submit_answer + end + end end get "home", to: "home#index" -end +end \ No newline at end of file From a5ebbb4200e1c14a6c752f9c033c00f55f14561b Mon Sep 17 00:00:00 2001 From: bg1777 Date: Mon, 8 Dec 2025 01:57:42 -0300 Subject: [PATCH 28/63] fix: Correcoes na tela de criacao de template --- .../admin/form_templates_controller.rb | 5 +++-- app/models/form_template_field.rb | 19 +++++++++++++------ .../_form_template_field_form.html.erb | 5 +++-- app/views/admin/form_templates/edit.html.erb | 3 ++- app/views/admin/form_templates/index.html.erb | 9 ++++++++- app/views/admin/form_templates/new.html.erb | 4 ++-- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/controllers/admin/form_templates_controller.rb b/app/controllers/admin/form_templates_controller.rb index 53d6a4df12..b4b3ff7421 100644 --- a/app/controllers/admin/form_templates_controller.rb +++ b/app/controllers/admin/form_templates_controller.rb @@ -15,7 +15,7 @@ def show def new @form_template = FormTemplate.new - 5.times { @form_template.form_template_fields.build } + @form_template.form_template_fields.build # ← Apenas 1 campo end def create @@ -29,6 +29,7 @@ def create end def edit + # Se não tiver campos, adiciona 1 @form_template.form_template_fields.build if @form_template.form_template_fields.empty? end @@ -62,4 +63,4 @@ def check_admin redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? end end -end \ No newline at end of file +end diff --git a/app/models/form_template_field.rb b/app/models/form_template_field.rb index 38b3c2b7db..d52f0904f8 100644 --- a/app/models/form_template_field.rb +++ b/app/models/form_template_field.rb @@ -2,10 +2,17 @@ class FormTemplateField < ApplicationRecord belongs_to :form_template - has_many :form_answers, dependent: :destroy - - validates :field_type, presence: true, inclusion: { in: %w(text textarea number email multiple_choice), message: '%{value} is not a valid field type' } + validates :label, presence: true - validates :position, presence: true - validates :form_template_id, presence: true -end \ No newline at end of file + validates :field_type, presence: true + validates :position, presence: true, numericality: { only_integer: true } + + # Validar options apenas para tipos que precisam + validates :options, presence: true, if: :requires_options? + + private + + def requires_options? + ['select', 'radio', 'checkbox'].include?(field_type) + end +end diff --git a/app/views/admin/form_templates/_form_template_field_form.html.erb b/app/views/admin/form_templates/_form_template_field_form.html.erb index 1641e5e571..4bbc194a86 100644 --- a/app/views/admin/form_templates/_form_template_field_form.html.erb +++ b/app/views/admin/form_templates/_form_template_field_form.html.erb @@ -1,4 +1,4 @@ - +
@@ -27,7 +27,8 @@
- <%= f.label :options, "Opções (JSON - para select/radio/checkbox)" %> + <%= f.label :options, "Opções (JSON - apenas para select/radio/checkbox)" %> + Ex: ["Opção 1", "Opção 2"] - deixe em branco para outros tipos <%= f.text_area :options, class: 'form-control', rows: 2, placeholder: '["Opção 1", "Opção 2"]' %>
diff --git a/app/views/admin/form_templates/edit.html.erb b/app/views/admin/form_templates/edit.html.erb index bf396b9eaf..7b0a390697 100644 --- a/app/views/admin/form_templates/edit.html.erb +++ b/app/views/admin/form_templates/edit.html.erb @@ -6,6 +6,7 @@

Editar Template

<%= form_with(model: [:admin, @form_template], local: true) do |form| %> + <% if @form_template.errors.any? %>

<%= pluralize(@form_template.errors.count, "erro") %> encontrado:

@@ -30,7 +31,7 @@

Campos

<%= form.fields_for :form_template_fields do |field_form| %> - <%= render 'field_form', f: field_form %> + <%= render 'form_template_field_form', f: field_form %> <% end %>
diff --git a/app/views/admin/form_templates/index.html.erb b/app/views/admin/form_templates/index.html.erb index 29409ad088..1b133fb564 100644 --- a/app/views/admin/form_templates/index.html.erb +++ b/app/views/admin/form_templates/index.html.erb @@ -3,7 +3,10 @@

Templates de Formulários

- <%= link_to 'Novo Template', new_admin_form_template_path, class: 'btn btn-primary' %> +
+ <%= link_to 'Novo Template', new_admin_form_template_path, class: 'btn btn-primary' %> + <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
<% if @form_templates.any? %> @@ -41,4 +44,8 @@ <%= link_to 'Criar seu primeiro template', new_admin_form_template_path, class: 'alert-link' %>
<% end %> + +
+ <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
diff --git a/app/views/admin/form_templates/new.html.erb b/app/views/admin/form_templates/new.html.erb index eed7af0a68..59083d6d01 100644 --- a/app/views/admin/form_templates/new.html.erb +++ b/app/views/admin/form_templates/new.html.erb @@ -5,7 +5,8 @@

Novo Template de Formulário

- <%= form_with(model: [:admin, @form_template], local: true) do |form| %> + <%= form_with(model: [:admin, @form_template], local: true, method: :post) do |form| %> + <% if @form_template.errors.any? %>

<%= pluralize(@form_template.errors.count, "erro") %> encontrado:

@@ -32,7 +33,6 @@ <%= form.fields_for :form_template_fields do |field_form| %> <%= render 'form_template_field_form', f: field_form %> <% end %> -
From 529bf12258a0fb31953a6f3477c81ca6f6925f7a Mon Sep 17 00:00:00 2001 From: bg Date: Mon, 8 Dec 2025 13:53:30 -0300 Subject: [PATCH 29/63] feature: botao de adicionar campos no template funcional --- app/assets/application.js | 5 ++ .../controllers/form_fields_controller.js | 23 ++++++++ app/assets/javascripts/form_fields.js | 28 +++++---- app/views/admin/form_templates/new.html.erb | 57 ++++++++++++++++++- config/importmap.rb | 4 ++ 5 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 app/assets/application.js create mode 100644 app/assets/controllers/form_fields_controller.js diff --git a/app/assets/application.js b/app/assets/application.js new file mode 100644 index 0000000000..8efd6faf08 --- /dev/null +++ b/app/assets/application.js @@ -0,0 +1,5 @@ +// app/assets/application.js + +//= require rails-ujs +//= require turbo +//= require_tree . diff --git a/app/assets/controllers/form_fields_controller.js b/app/assets/controllers/form_fields_controller.js new file mode 100644 index 0000000000..e93b7882cb --- /dev/null +++ b/app/assets/controllers/form_fields_controller.js @@ -0,0 +1,23 @@ +// app/javascript/controllers/form_fields_controller.js + +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["container"] + + add_field(event) { + event.preventDefault() + + const link = event.currentTarget + const association = link.dataset.association + const content = link.dataset.fields + + const new_id = new Date().getTime() + const regexp = new RegExp("new_" + association, "g") + const new_fields = content.replace(regexp, new_id) + + const temp = document.createElement('div') + temp.innerHTML = new_fields + this.containerTarget.appendChild(temp.firstElementChild) + } +} diff --git a/app/assets/javascripts/form_fields.js b/app/assets/javascripts/form_fields.js index 0c01fbec7c..00c2e21e5c 100644 --- a/app/assets/javascripts/form_fields.js +++ b/app/assets/javascripts/form_fields.js @@ -1,14 +1,18 @@ // app/assets/javascripts/form_fields.js -function add_fields(link, association, content) { - var new_id = new Date().getTime(); - var regexp = new RegExp("new_" + association, "g"); - var new_fields = content.replace(regexp, new_id); - - var container = link.closest('form').querySelector("#fields-container"); - if (container) { - var temp = document.createElement('div'); - temp.innerHTML = new_fields; - container.appendChild(temp.firstElementChild); - } -} +document.addEventListener('DOMContentLoaded', function() { + window.add_fields = function(link, association, content) { + var new_id = new Date().getTime(); + var regexp = new RegExp("new_" + association, "g"); + var new_fields = content.replace(regexp, new_id); + + var container = link.closest('form').querySelector("#fields-container"); + if (container) { + var temp = document.createElement('div'); + temp.innerHTML = new_fields; + container.appendChild(temp.firstElementChild); + } + + return false; + }; +}); diff --git a/app/views/admin/form_templates/new.html.erb b/app/views/admin/form_templates/new.html.erb index 59083d6d01..1b03712d97 100644 --- a/app/views/admin/form_templates/new.html.erb +++ b/app/views/admin/form_templates/new.html.erb @@ -36,7 +36,9 @@
- <%= link_to_add_fields "Adicionar Campo", form, :form_template_fields, class: 'btn btn-sm btn-info' %> +
@@ -47,3 +49,56 @@
+ + \ No newline at end of file diff --git a/config/importmap.rb b/config/importmap.rb index 909dfc542d..1f3c43482a 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -5,3 +5,7 @@ pin "@hotwired/stimulus", to: "stimulus.min.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" pin_all_from "app/javascript/controllers", under: "controllers" + +pin "application", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" + From c760a844397a2d3faa2569d21fb1d5bca09ac679 Mon Sep 17 00:00:00 2001 From: bg Date: Mon, 8 Dec 2025 16:51:04 -0300 Subject: [PATCH 30/63] Views de criar formulario e gerenciar formularios para adm --- app/controllers/admin/forms_controller.rb | 21 ++-- app/views/admin/dashboard/index.html.erb | 10 ++ app/views/admin/forms/edit.html.erb | 55 +++++++++ app/views/admin/forms/index.html.erb | 74 ++++++++++++ app/views/admin/forms/new.html.erb | 58 ++++++++++ app/views/admin/forms/show.html.erb | 131 ++++++++++++++++++++++ db/schema.rb | 2 +- 7 files changed, 338 insertions(+), 13 deletions(-) create mode 100644 app/views/admin/forms/edit.html.erb create mode 100644 app/views/admin/forms/index.html.erb create mode 100644 app/views/admin/forms/new.html.erb create mode 100644 app/views/admin/forms/show.html.erb diff --git a/app/controllers/admin/forms_controller.rb b/app/controllers/admin/forms_controller.rb index b8a762a07b..0868536f4e 100644 --- a/app/controllers/admin/forms_controller.rb +++ b/app/controllers/admin/forms_controller.rb @@ -11,8 +11,8 @@ def index end def show - @pending_count = @form.pending_responses.count - @completed_count = @form.completed_responses.count + @pending_count = @form.form_responses.where(submitted_at: nil).count + @completed_count = @form.form_responses.where.not(submitted_at: nil).count end def new @@ -22,19 +22,13 @@ def new end def create - template = FormTemplate.find(form_params[:form_template_id]) - klass = Klass.find(form_params[:klass_id]) - - @form = template.create_form_instance( - klass, - form_params[:title], - form_params[:description], - form_params[:due_date] - ) + @form = Form.new(form_params) if @form.save redirect_to admin_form_path(@form), notice: 'Formulário criado com sucesso!' else + @form_templates = FormTemplate.all + @klasses = Klass.all render :new, status: :unprocessable_entity end end @@ -45,6 +39,9 @@ def edit end def update + @form_templates = FormTemplate.all + @klasses = Klass.all + if @form.update(form_params) redirect_to admin_form_path(@form), notice: 'Formulário atualizado com sucesso!' else @@ -87,4 +84,4 @@ def check_admin redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? end end -end \ No newline at end of file +end diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index a8346f6c73..58012ca86f 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -43,6 +43,16 @@ <%= link_to 'Acessar', admin_form_templates_path, class: 'btn btn-primary' %>
+ + + +

Usuários Recentes

diff --git a/app/views/admin/forms/edit.html.erb b/app/views/admin/forms/edit.html.erb new file mode 100644 index 0000000000..b8b0c4919e --- /dev/null +++ b/app/views/admin/forms/edit.html.erb @@ -0,0 +1,55 @@ + + +
+
+
+

Editar Formulário

+ + <%= form_with(model: [:admin, @form], local: true) do |form| %> + + <% if @form.errors.any? %> +
+

<%= pluralize(@form.errors.count, "erro") %> encontrado:

+
    + <% @form.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :form_template_id, "Template" %> + <%= form.collection_select :form_template_id, @form_templates, :id, :name, + {}, class: 'form-select' %> +
+ +
+ <%= form.label :klass_id, "Turma" %> + <%= form.collection_select :klass_id, @klasses, :id, :name, + {}, class: 'form-select' %> +
+ +
+ <%= form.label :title, "Título do Formulário" %> + <%= form.text_field :title, class: 'form-control' %> +
+ +
+ <%= form.label :description, "Descrição" %> + <%= form.text_area :description, class: 'form-control', rows: 4 %> +
+ +
+ <%= form.label :due_date, "Prazo (opcional)" %> + <%= form.datetime_select :due_date, class: 'form-control' %> +
+ +
+ <%= form.submit "Atualizar Formulário", class: 'btn btn-success' %> + <%= link_to 'Cancelar', admin_forms_path, class: 'btn btn-secondary' %> +
+ <% end %> +
+
+
diff --git a/app/views/admin/forms/index.html.erb b/app/views/admin/forms/index.html.erb new file mode 100644 index 0000000000..b78fe0fa72 --- /dev/null +++ b/app/views/admin/forms/index.html.erb @@ -0,0 +1,74 @@ + + +
+
+

Formulários

+
+ <%= link_to 'Novo Formulário', new_admin_form_path, class: 'btn btn-primary' %> + <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
+
+ + <% if @forms.any? %> +
+
+ + + + + + + + + + + + + <% @forms.each do |form| %> + + + + + + + + + + <% end %> + +
TítuloTemplateTurmaStatusRespostasPrazoAções
<%= form.title %><%= form.form_template.name %><%= form.klass.name %> + <% case form.status %> + <% when 'draft' %> + Rascunho + <% when 'published' %> + Publicado + <% when 'closed' %> + Fechado + <% end %> + + <%= form.completed_responses.count %> + / + <%= form.klass.students.count %> + <%= form.due_date.present? ? l(form.due_date, format: :short) : '-' %> + <%= link_to 'Ver', admin_form_path(form), class: 'btn btn-sm btn-info' %> + <% if form.draft? %> + <%= link_to 'Editar', edit_admin_form_path(form), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Publicar', publish_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-success' %> + <% end %> + <% if form.published? %> + <%= link_to 'Fechar', close_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-danger' %> + <% end %> + <%= link_to 'Deletar', admin_form_path(form), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-outline-danger' %> +
+
+ <% else %> +
+ Nenhum formulário criado ainda. + <%= link_to 'Criar seu primeiro formulário', new_admin_form_path, class: 'alert-link' %> +
+ <% end %> + +
+ <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
+ diff --git a/app/views/admin/forms/new.html.erb b/app/views/admin/forms/new.html.erb new file mode 100644 index 0000000000..a713ba4788 --- /dev/null +++ b/app/views/admin/forms/new.html.erb @@ -0,0 +1,58 @@ + + +
+
+
+

Novo Formulário

+ + <%= form_with(model: [:admin, @form], local: true, method: :post) do |form| %> + + <% if @form.errors.any? %> +
+

<%= pluralize(@form.errors.count, "erro") %> encontrado:

+
    + <% @form.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :form_template_id, "Template" %> + <%= form.collection_select :form_template_id, @form_templates, :id, :name, + { prompt: 'Selecione um template' }, class: 'form-select' %> + Escolha um template para criar o formulário +
+ +
+ <%= form.label :klass_id, "Turma" %> + <%= form.collection_select :klass_id, @klasses, :id, :name, + { prompt: 'Selecione uma turma' }, class: 'form-select' %> + Selecione a turma que responderá este formulário +
+ +
+ <%= form.label :title, "Título do Formulário" %> + <%= form.text_field :title, class: 'form-control', placeholder: 'Ex: Avaliação de Desempenho' %> +
+ +
+ <%= form.label :description, "Descrição" %> + <%= form.text_area :description, class: 'form-control', rows: 4, + placeholder: 'Descreva o propósito deste formulário...' %> +
+ +
+ <%= form.label :due_date, "Prazo (opcional)" %> + <%= form.datetime_select :due_date, class: 'form-control' %> +
+ +
+ <%= form.submit "Criar Formulário", class: 'btn btn-success' %> + <%= link_to 'Cancelar', admin_forms_path, class: 'btn btn-secondary' %> +
+ <% end %> +
+
+
diff --git a/app/views/admin/forms/show.html.erb b/app/views/admin/forms/show.html.erb new file mode 100644 index 0000000000..575001a824 --- /dev/null +++ b/app/views/admin/forms/show.html.erb @@ -0,0 +1,131 @@ + + +
+
+
+

<%= @form.title %>

+

<%= @form.form_template.name %> • <%= @form.klass.name %>

+
+
+ <% if @form.draft? %> + <%= link_to 'Editar', edit_admin_form_path(@form), class: 'btn btn-warning' %> + <%= link_to 'Publicar', publish_admin_form_path(@form), method: :patch, class: 'btn btn-success' %> + <% end %> + <% if @form.published? %> + <%= link_to 'Fechar', close_admin_form_path(@form), method: :patch, class: 'btn btn-danger' %> + <% end %> + <%= link_to 'Voltar', admin_forms_path, class: 'btn btn-secondary' %> +
+
+ +
+
+
+
+
Status
+ <% case @form.status %> + <% when 'draft' %> + Rascunho + <% when 'published' %> + Publicado + <% when 'closed' %> + Fechado + <% end %> +
+
+
+
+
+
+
Respondidos
+

<%= @completed_count %> / <%= @form.klass.students.count %>

+
+
+
+
+
+
+
Pendentes
+

<%= @pending_count %>

+
+
+
+
+
+
+
Prazo
+

<%= @form.due_date.present? ? l(@form.due_date, format: :long) : 'Sem prazo' %>

+
+
+
+
+ +
+
+
Descrição
+

<%= @form.description %>

+
+
+ +

Campos do Formulário

+
+ + + + + + + + + + + <% @form.form_template.form_template_fields.order(:position).each do |field| %> + + + + + + + <% end %> + +
PosiçãoLabelTipoObrigatório
<%= field.position %><%= field.label %><%= field.field_type %> + <% if field.required %> + Sim + <% else %> + Não + <% end %> +
+
+ +

Respostas

+
+ + + + + + + + + + + <% @form.form_responses.each do |response| %> + + + + + + + <% end %> + +
AlunoStatusRespondido emAções
<%= response.user.name %> + <% if response.completed? %> + Respondido + <% else %> + Pendente + <% end %> + <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> + <%= link_to 'Ver Respostas', '#', class: 'btn btn-sm btn-info' %> +
+
+
diff --git a/db/schema.rb b/db/schema.rb index 577eba3086..99b0b4e065 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_12_08_031925) do +ActiveRecord::Schema[8.0].define(version: 2025_12_08_162100) do create_table "class_members", force: :cascade do |t| t.integer "user_id", null: false t.integer "klass_id", null: false From f8825304f5a639a045b7220f22b78e882adbc8a0 Mon Sep 17 00:00:00 2001 From: bg Date: Mon, 8 Dec 2025 17:40:14 -0300 Subject: [PATCH 31/63] feat: publicar forms e ver forms para responder --- app/controllers/home_controller.rb | 5 +- app/controllers/student/student_controller.rb | 68 +++++++ app/models/form.rb | 11 +- app/models/user.rb | 21 ++- app/views/admin/form_templates/show.html.erb | 168 ++++++++++++------ app/views/admin/forms/index.html.erb | 4 +- app/views/home/index.html.erb | 95 +++++++++- app/views/student/forms/answer.html.erb | 104 +++++++++++ app/views/student/forms/show.html.erb | 57 ++++++ er.pending_forms.count | 21 +++ 10 files changed, 491 insertions(+), 63 deletions(-) create mode 100644 app/controllers/student/student_controller.rb create mode 100644 app/views/student/forms/answer.html.erb create mode 100644 app/views/student/forms/show.html.erb create mode 100644 er.pending_forms.count diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 957dbcf60b..192b3c908a 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,12 +2,13 @@ class HomeController < ApplicationController before_action :authenticate_user! - + def index if current_user.admin? redirect_to admin_root_path else - @user = current_user + @pending_forms = current_user.pending_forms.order(due_date: :asc) + @completed_forms = current_user.completed_forms.order(created_at: :desc) end end end diff --git a/app/controllers/student/student_controller.rb b/app/controllers/student/student_controller.rb new file mode 100644 index 0000000000..5cc7c4ae41 --- /dev/null +++ b/app/controllers/student/student_controller.rb @@ -0,0 +1,68 @@ +# app/controllers/student/forms_controller.rb + +module Student + class FormsController < ApplicationController + before_action :authenticate_user! + before_action :check_student + before_action :set_form, only: [:show, :answer, :submit_answer] + before_action :check_form_accessible, only: [:show, :answer, :submit_answer] + + def index + @pending_forms = current_user.pending_forms.order(due_date: :asc) + @completed_forms = current_user.completed_forms.order(created_at: :desc) + end + + def show + @form_response = @form.form_responses.find_by(user: current_user) + @form_response ||= FormResponse.new(form: @form, user: current_user) + end + + def answer + @form_response = @form.form_responses.find_by(user: current_user) + + if @form_response.nil? + @form_response = FormResponse.create(form: @form, user: current_user) + @form_response.build_answers_for_fields + @form_response.save + end + + @form_response.reload + end + + def submit_answer + @form_response = @form.form_responses.find_by(user: current_user) + + if @form_response.nil? + redirect_to student_form_path(@form), alert: 'Resposta não encontrada' + return + end + + if update_answers && @form_response.submit! + redirect_to student_forms_path, notice: 'Formulário respondido com sucesso!' + else + redirect_to answer_student_form_path(@form), alert: 'Erro ao salvar respostas' + end + end + + private + + def set_form + @form = Form.find(params[:id]) + end + + def check_student + redirect_to admin_root_path if current_user.admin? + end + + def check_form_accessible + unless current_user.klasses.include?(@form.klass) && @form.published? + redirect_to student_forms_path, alert: 'Acesso negado a este formulário' + end + end + + def update_answers + form_answers_params = params.require(:form_response).permit(form_answers_attributes: [:id, :answer]) + @form_response.update(form_answers_params) + end + end +end diff --git a/app/models/form.rb b/app/models/form.rb index 39d333e688..f7fe723dce 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -10,6 +10,9 @@ class Form < ApplicationRecord validates :title, presence: true enum :status, { draft: 0, published: 1, closed: 2 } + + # Definir status padrão + before_create :set_default_status # Retorna todos os alunos da turma que ainda não responderam o formulário def pending_responses @@ -20,4 +23,10 @@ def pending_responses def completed_responses form_responses.map(&:user) end -end \ No newline at end of file + + private + + def set_default_status + self.status ||= :draft + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 89dc46d837..64f2978d44 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -24,4 +24,23 @@ def admin? def user? role == 'user' end -end \ No newline at end of file + + # Formulários pendentes (não respondidos e no prazo) + def pending_forms + # Pega todos os forms publicados das turmas do usuário + all_forms = Form + .where(klass_id: klasses.pluck(:id), status: :published) + .where('due_date > ? OR due_date IS NULL', Time.current) + + # Remove apenas os que foram RESPONDIDOS (submitted_at NOT NULL) + all_forms.where.not( + id: form_responses.where.not(submitted_at: nil).pluck(:form_id) + ) + end + + # Formulários já respondidos + def completed_forms + Form + .where(id: form_responses.where.not(submitted_at: nil).pluck(:form_id)) + end +end diff --git a/app/views/admin/form_templates/show.html.erb b/app/views/admin/form_templates/show.html.erb index 2743cb8175..75ddb4c15d 100644 --- a/app/views/admin/form_templates/show.html.erb +++ b/app/views/admin/form_templates/show.html.erb @@ -1,67 +1,131 @@ - +
-

<%= @form_template.name %>

- <%= link_to 'Editar', edit_admin_form_template_path(@form_template), class: 'btn btn-warning' %> - <%= link_to 'Voltar', admin_form_templates_path, class: 'btn btn-secondary' %> +

<%= @form.title %>

+

<%= @form.form_template.name %> • <%= @form.klass.name %>

+
+
+ <% if @form.draft? %> + <%= link_to 'Editar', edit_admin_form_path(@form), class: 'btn btn-warning' %> + <%= button_to 'Publicar', publish_admin_form_path(@form), method: :patch, class: 'btn btn-success' %> + <% end %> + <% if @form.published? %> + <%= button_to 'Fechar', close_admin_form_path(@form), method: :patch, class: 'btn btn-danger' %> + <% end %> + <%= link_to 'Voltar', admin_forms_path, class: 'btn btn-secondary' %> +
+
+ +
+
+
+
+
Status
+ <% case @form.status %> + <% when 'draft' %> + Rascunho + <% when 'published' %> + Publicado + <% when 'closed' %> + Fechado + <% end %> +
+
+
+
+
+
+
Respondidos
+

<%= @completed_count %> / <%= @form.klass.students.count %>

+
+
+
+
+
+
+
Pendentes
+

<%= @pending_count %>

+
+
+
+
+
+
+
Prazo
+

<%= @form.due_date.present? ? l(@form.due_date, format: :long) : 'Sem prazo' %>

+
+
-
Informações
-

- Descrição: <%= @form_template.description %> -

-

- Total de Campos: <%= @form_template.form_template_fields.count %> -

-

- Criado em: <%= l(@form_template.created_at, format: :long) %> -

+
Descrição
+

<%= @form.description %>

-

Campos do Template

- <% if @form_template.form_template_fields.any? %> -
- - +

Campos do Formulário

+
+
+ + + + + + + + + + <% @form.form_template.form_template_fields.order(:position).each do |field| %> - - - - - - + + + + - - - <% @form_template.form_template_fields.order(:position).each do |field| %> - - - - - - - - - <% end %> - -
PosiçãoLabelTipoObrigatório
PosiçãoLabelTipoObrigatórioOpçõesAções<%= field.position %><%= field.label %><%= field.field_type %> + <% if field.required %> + Sim + <% else %> + Não + <% end %> +
<%= field.position %><%= field.label %><%= field.field_type %> - <% if field.required %> - Sim - <% else %> - Não - <% end %> - <%= field.options.present? ? truncate(field.options, length: 30) : '-' %> - <%= link_to 'Editar', edit_admin_form_template_path(@form_template, id: field.id), class: 'btn btn-sm btn-warning' %> - <%= link_to 'Deletar', admin_form_template_form_template_field_path(@form_template, field), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> -
-
- <% else %> -
Este template não possui campos.
- <% end %> + <% end %> + + +
+ +

Respostas

+
+ + + + + + + + + + + <% @form.form_responses.each do |response| %> + + + + + + + <% end %> + +
AlunoStatusRespondido emAções
<%= response.user.name %> + <% if response.completed? %> + Respondido + <% else %> + Pendente + <% end %> + <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> + <%= link_to 'Ver Respostas', '#', class: 'btn btn-sm btn-info' %> +
+
diff --git a/app/views/admin/forms/index.html.erb b/app/views/admin/forms/index.html.erb index b78fe0fa72..8ec3aba439 100644 --- a/app/views/admin/forms/index.html.erb +++ b/app/views/admin/forms/index.html.erb @@ -49,10 +49,10 @@ <%= link_to 'Ver', admin_form_path(form), class: 'btn btn-sm btn-info' %> <% if form.draft? %> <%= link_to 'Editar', edit_admin_form_path(form), class: 'btn btn-sm btn-warning' %> - <%= link_to 'Publicar', publish_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-success' %> + <%= button_to 'Publicar', publish_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-success', form_class: 'd-inline' %> <% end %> <% if form.published? %> - <%= link_to 'Fechar', close_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-danger' %> + <%= button_to 'Fechar', close_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-danger', form_class: 'd-inline' %> <% end %> <%= link_to 'Deletar', admin_form_path(form), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-outline-danger' %> diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 644586e5f3..4701c60ffc 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,9 +1,94 @@
-

Bem-vindo, <%= current_user.name %>!

-

Email: <%= current_user.email %>

-

Role: <%= current_user.role %>

- - <%= link_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> +
+
+

Bem-vindo, <%= current_user.name %>!

+

Email: <%= current_user.email %>

+
+ <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> +
+ + +
+

📋 Formulários Pendentes

+ + <% if @pending_forms.any? %> +
+ <% @pending_forms.each do |form| %> +
+
+
+
<%= form.title %>
+

+ Turma: <%= form.klass.name %> +

+

+ <%= truncate(form.description, length: 100) %> +

+ + <% if form.due_date.present? %> +

+ + Prazo: + + <%= l(form.due_date, format: :short) %> + + +

+ <% end %> + +
+ <%= link_to 'Responder Formulário', answer_student_form_path(form), class: 'btn btn-primary btn-sm' %> +
+
+
+
+ <% end %> +
+ <% else %> +
+ ✓ Parabéns! Você respondeu todos os formulários pendentes. +
+ <% end %> +
+ + +
+

✓ Formulários Respondidos

+ + <% if @completed_forms.any? %> +
+ + + + + + + + + + + <% @completed_forms.each do |form| %> + + + + + + + <% end %> + +
TítuloTurmaRespondido emAções
<%= form.title %><%= form.klass.name %> + <% response = form.form_responses.find_by(user: current_user) %> + <%= l(response.submitted_at, format: :short) if response&.submitted_at %> + + <%= link_to 'Ver', student_form_path(form), class: 'btn btn-sm btn-info' %> +
+
+ <% else %> +
+ Nenhum formulário respondido ainda. +
+ <% end %> +
diff --git a/app/views/student/forms/answer.html.erb b/app/views/student/forms/answer.html.erb new file mode 100644 index 0000000000..88bc9a6e11 --- /dev/null +++ b/app/views/student/forms/answer.html.erb @@ -0,0 +1,104 @@ + + +
+
+
+

<%= @form.title %>

+

<%= @form.klass.name %>

+
+ <%= link_to 'Voltar', student_forms_path, class: 'btn btn-secondary' %> +
+ + <% if @form.description.present? %> +
+ Instruções:
+ <%= simple_format(@form.description) %> +
+ <% end %> + + <% if @form.due_date.present? %> +
+ ⏰ Prazo: <%= l(@form.due_date, format: :long) %> +
+ <% end %> + + <%= form_with(model: [@form_response], local: true, url: submit_answer_student_form_path(@form), method: :post) do |form| %> + + <% if @form_response.errors.any? %> +
+

<%= pluralize(@form_response.errors.count, "erro") %> encontrado:

+
    + <% @form_response.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + + <%= form.fields_for :form_answers do |answer_form| %> + <% field = answer_form.object.form_template_field %> + +
+
+ + + <% case field.field_type %> + <% when 'text' %> + <%= answer_form.text_field :answer, class: 'form-control', placeholder: field.label %> + + <% when 'textarea' %> + <%= answer_form.text_area :answer, class: 'form-control', rows: 4, placeholder: field.label %> + + <% when 'email' %> + <%= answer_form.email_field :answer, class: 'form-control', placeholder: field.label %> + + <% when 'number' %> + <%= answer_form.number_field :answer, class: 'form-control', placeholder: field.label %> + + <% when 'date' %> + <%= answer_form.date_field :answer, class: 'form-control' %> + + <% when 'select' %> + <% options = field.options.present? ? JSON.parse(field.options) : [] %> + <%= answer_form.select :answer, + options, + { prompt: 'Selecione uma opção' }, + class: 'form-select' %> + + <% when 'radio' %> + <% options = field.options.present? ? JSON.parse(field.options) : [] %> +
+ <% options.each do |option| %> +
+ <%= answer_form.radio_button :answer, option, class: 'form-check-input' %> + <%= answer_form.label :answer, option, class: 'form-check-label' %> +
+ <% end %> +
+ + <% when 'checkbox' %> + <% options = field.options.present? ? JSON.parse(field.options) : [] %> +
+ <% options.each do |option| %> +
+ <%= answer_form.check_box :answer, { multiple: true }, option, nil, class: 'form-check-input' %> + <%= answer_form.label :answer, option, class: 'form-check-label' %> +
+ <% end %> +
+ <% end %> +
+
+ <% end %> + +
+ <%= form.submit "Enviar Respostas", class: 'btn btn-success btn-lg' %> + <%= link_to 'Cancelar', student_forms_path, class: 'btn btn-secondary btn-lg' %> +
+ <% end %> +
diff --git a/app/views/student/forms/show.html.erb b/app/views/student/forms/show.html.erb new file mode 100644 index 0000000000..a539983c23 --- /dev/null +++ b/app/views/student/forms/show.html.erb @@ -0,0 +1,57 @@ + + +
+
+
+

<%= @form.title %>

+

<%= @form.klass.name %>

+
+ <%= link_to 'Voltar', student_forms_path, class: 'btn btn-secondary' %> +
+ +
+
+
Informações
+

Descrição: <%= @form.description %>

+

+ Prazo: + <% if @form.due_date.present? %> + <%= l(@form.due_date, format: :long) %> + <% else %> + Sem prazo + <% end %> +

+
+
+ + <% if @form_response&.completed? %> +
+ ✓ Formulário respondido em: <%= l(@form_response.submitted_at, format: :long) %> +
+ +

Suas Respostas

+
+ + + + + + + + + <% @form_response.form_answers.each do |answer| %> + + + + + <% end %> + +
PerguntaSua Resposta
<%= answer.form_template_field.label %><%= answer.answer %>
+
+ <% else %> +
+ Este formulário ainda não foi respondido. + <%= link_to 'Responder agora', answer_student_form_path(@form), class: 'btn btn-primary' %> +
+ <% end %> +
diff --git a/er.pending_forms.count b/er.pending_forms.count new file mode 100644 index 0000000000..e6d7a0973b --- /dev/null +++ b/er.pending_forms.count @@ -0,0 +1,21 @@ +=> +[#, + #] From 210d20227fe28081bd2c6261594fe79eb90c7f48 Mon Sep 17 00:00:00 2001 From: bg1777 Date: Mon, 8 Dec 2025 21:09:28 -0300 Subject: [PATCH 32/63] feat: fluxo de forms completo --- app/controllers/admin/forms_controller.rb | 32 +++- app/controllers/student/forms_controller.rb | 73 +++++++++ app/models/form_response.rb | 18 ++- app/views/admin/forms/show.html.erb | 158 ++++--------------- app/views/admin/forms/view_response.html.erb | 66 ++++++++ app/views/student/forms/answer.html.erb | 152 +++++++++--------- config/routes.rb | 3 +- 7 files changed, 293 insertions(+), 209 deletions(-) create mode 100644 app/controllers/student/forms_controller.rb create mode 100644 app/views/admin/forms/view_response.html.erb diff --git a/app/controllers/admin/forms_controller.rb b/app/controllers/admin/forms_controller.rb index 0868536f4e..b73b46ec85 100644 --- a/app/controllers/admin/forms_controller.rb +++ b/app/controllers/admin/forms_controller.rb @@ -4,15 +4,16 @@ module Admin class FormsController < ApplicationController before_action :authenticate_user! before_action :check_admin - before_action :set_form, only: [:show, :edit, :update, :destroy, :publish, :close] + before_action :set_form, only: [:show, :edit, :update, :destroy, :publish, :close, :view_response] + before_action :set_form_response, only: [:view_response] def index @forms = Form.all.order(created_at: :desc) end def show - @pending_count = @form.form_responses.where(submitted_at: nil).count - @completed_count = @form.form_responses.where.not(submitted_at: nil).count + @pending_count = @form.pending_responses.count + @completed_count = @form.completed_responses.count end def new @@ -22,7 +23,17 @@ def new end def create - @form = Form.new(form_params) + template = FormTemplate.find(form_params[:form_template_id]) + klass = Klass.find(form_params[:klass_id]) + + @form = Form.new( + form_template: template, + klass: klass, + title: form_params[:title], + description: form_params[:description], + due_date: form_params[:due_date], + status: :draft + ) if @form.save redirect_to admin_form_path(@form), notice: 'Formulário criado com sucesso!' @@ -39,12 +50,11 @@ def edit end def update - @form_templates = FormTemplate.all - @klasses = Klass.all - if @form.update(form_params) redirect_to admin_form_path(@form), notice: 'Formulário atualizado com sucesso!' else + @form_templates = FormTemplate.all + @klasses = Klass.all render :edit, status: :unprocessable_entity end end @@ -70,12 +80,20 @@ def close end end + def view_response + # @form_response já é setado pelo before_action + end + private def set_form @form = Form.find(params[:id]) end + def set_form_response + @form_response = FormResponse.find(params[:response_id]) + end + def form_params params.require(:form).permit(:form_template_id, :klass_id, :title, :description, :due_date, :status) end diff --git a/app/controllers/student/forms_controller.rb b/app/controllers/student/forms_controller.rb new file mode 100644 index 0000000000..af8f375f3f --- /dev/null +++ b/app/controllers/student/forms_controller.rb @@ -0,0 +1,73 @@ +# app/controllers/student/forms_controller.rb + +module Student + class FormsController < ApplicationController + before_action :authenticate_user! + before_action :check_student + before_action :set_form, only: [:show, :answer, :submit_answer] + before_action :check_form_accessible, only: [:show, :answer, :submit_answer] + + def index + @pending_forms = current_user.pending_forms.order(due_date: :asc) + @completed_forms = current_user.completed_forms.order(created_at: :desc) + end + + def show + @form_response = @form.form_responses.find_by(user: current_user) + @form_response ||= FormResponse.new(form: @form, user: current_user) + end + + def answer + @form_response = @form.form_responses.find_by(user: current_user) + + if @form_response.nil? + @form_response = FormResponse.create(form: @form, user: current_user) + @form_response.build_answers_for_fields + @form_response.save + end + + @form_response.reload + + # Se não há respostas, criar agora + if @form_response.form_answers.empty? + @form_response.build_answers_for_fields + end + end + + def submit_answer + @form_response = @form.form_responses.find_by(user: current_user) + + if @form_response.nil? + redirect_to student_form_path(@form), alert: 'Resposta não encontrada' + return + end + + if update_answers && @form_response.submit! + redirect_to root_path, notice: 'Formulário respondido com sucesso!' + else + redirect_to answer_student_form_path(@form), alert: 'Erro ao salvar respostas' + end + end + + private + + def set_form + @form = Form.find(params[:id]) + end + + def check_student + redirect_to admin_root_path if current_user.admin? + end + + def check_form_accessible + unless current_user.klasses.include?(@form.klass) && @form.published? + redirect_to root_path, alert: 'Acesso negado a este formulário' + end + end + + def update_answers + form_answers_params = params.require(:form_response).permit(form_answers_attributes: [:id, :answer]) + @form_response.update(form_answers_params) + end + end +end diff --git a/app/models/form_response.rb b/app/models/form_response.rb index 5b4311f064..bf4a87f0ae 100644 --- a/app/models/form_response.rb +++ b/app/models/form_response.rb @@ -18,4 +18,20 @@ def completed? def pending? !completed? end -end \ No newline at end of file + + # Inicializar answers para todos os fields (sem validação) + def build_answers_for_fields + form.form_template.form_template_fields.order(:position).each do |field| + # Só adicionar se ainda não existe + unless form_answers.exists?(form_template_field_id: field.id) + form_answers.build(form_template_field: field, answer: '') + end + end + save(validate: false) if persisted? + end + + # Marcar como submetido + def submit! + update(submitted_at: Time.current) + end +end diff --git a/app/views/admin/forms/show.html.erb b/app/views/admin/forms/show.html.erb index 575001a824..2e00d7fa15 100644 --- a/app/views/admin/forms/show.html.erb +++ b/app/views/admin/forms/show.html.erb @@ -1,131 +1,35 @@ - + -
-
-
-

<%= @form.title %>

-

<%= @form.form_template.name %> • <%= @form.klass.name %>

-
-
- <% if @form.draft? %> - <%= link_to 'Editar', edit_admin_form_path(@form), class: 'btn btn-warning' %> - <%= link_to 'Publicar', publish_admin_form_path(@form), method: :patch, class: 'btn btn-success' %> - <% end %> - <% if @form.published? %> - <%= link_to 'Fechar', close_admin_form_path(@form), method: :patch, class: 'btn btn-danger' %> - <% end %> - <%= link_to 'Voltar', admin_forms_path, class: 'btn btn-secondary' %> -
-
- -
-
-
-
-
Status
- <% case @form.status %> - <% when 'draft' %> - Rascunho - <% when 'published' %> - Publicado - <% when 'closed' %> - Fechado - <% end %> -
-
-
-
-
-
-
Respondidos
-

<%= @completed_count %> / <%= @form.klass.students.count %>

-
-
-
-
-
-
-
Pendentes
-

<%= @pending_count %>

-
-
-
-
-
-
-
Prazo
-

<%= @form.due_date.present? ? l(@form.due_date, format: :long) : 'Sem prazo' %>

-
-
-
-
- -
-
-
Descrição
-

<%= @form.description %>

-
-
- -

Campos do Formulário

-
- - +

Respostas

+
+
+ + + + + + + + + + <% @form.form_responses.each do |response| %> - - - - + + + + - - - <% @form.form_template.form_template_fields.order(:position).each do |field| %> - - - - - - - <% end %> - -
AlunoStatusRespondido emAções
PosiçãoLabelTipoObrigatório<%= response.user.name %> + <% if response.completed? %> + Respondido + <% else %> + Pendente + <% end %> + <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> + <% if response.completed? %> + <%= link_to 'Ver Respostas', view_response_admin_form_path(@form, response_id: response.id), class: 'btn btn-sm btn-info' %> + <% end %> +
<%= field.position %><%= field.label %><%= field.field_type %> - <% if field.required %> - Sim - <% else %> - Não - <% end %> -
-
- -

Respostas

-
- - - - - - - - - - - <% @form.form_responses.each do |response| %> - - - - - - - <% end %> - -
AlunoStatusRespondido emAções
<%= response.user.name %> - <% if response.completed? %> - Respondido - <% else %> - Pendente - <% end %> - <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> - <%= link_to 'Ver Respostas', '#', class: 'btn btn-sm btn-info' %> -
-
+ <% end %> + +
diff --git a/app/views/admin/forms/view_response.html.erb b/app/views/admin/forms/view_response.html.erb new file mode 100644 index 0000000000..6f67b197f3 --- /dev/null +++ b/app/views/admin/forms/view_response.html.erb @@ -0,0 +1,66 @@ + + +
+
+
+

Respostas de <%= @form_response.user.name %>

+

<%= @form.title %>

+
+ <%= link_to 'Voltar', admin_form_path(@form), class: 'btn btn-secondary' %> +
+ +
+
+
+
+
Aluno
+

<%= @form_response.user.name %>

+

<%= @form_response.user.email %>

+
+
+
+
+
+
+
Data de Resposta
+

<%= l(@form_response.submitted_at, format: :long) %>

+
+
+
+
+ +

Respostas

+
+ + + + + + + + + <% @form_response.form_answers.each do |answer| %> + + + + + <% end %> + +
PerguntaResposta
+ <%= answer.form_template_field.label %> + <% if answer.form_template_field.required %> + * + <% end %> + + <% if answer.answer.present? %> + <%= answer.answer %> + <% else %> + - + <% end %> +
+
+ +
+ <%= link_to 'Voltar', admin_form_path(@form), class: 'btn btn-secondary' %> +
+
diff --git a/app/views/student/forms/answer.html.erb b/app/views/student/forms/answer.html.erb index 88bc9a6e11..38d5f2847c 100644 --- a/app/views/student/forms/answer.html.erb +++ b/app/views/student/forms/answer.html.erb @@ -6,7 +6,7 @@

<%= @form.title %>

<%= @form.klass.name %>

- <%= link_to 'Voltar', student_forms_path, class: 'btn btn-secondary' %> + <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> <% if @form.description.present? %> @@ -22,83 +22,89 @@ <% end %> - <%= form_with(model: [@form_response], local: true, url: submit_answer_student_form_path(@form), method: :post) do |form| %> - - <% if @form_response.errors.any? %> -
-

<%= pluralize(@form_response.errors.count, "erro") %> encontrado:

-
    - <% @form_response.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - - <%= form.fields_for :form_answers do |answer_form| %> - <% field = answer_form.object.form_template_field %> + <% if @form_response.form_answers.empty? %> +
+ Erro: Não há campos para preencher neste formulário. +
+ <% else %> + <%= form_with(model: @form_response, local: true, url: submit_answer_student_form_path(@form), method: :post) do |form| %> -
-
-
diff --git a/config/routes.rb b/config/routes.rb index 2e674b1621..da4905938e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,7 @@ member do patch :publish patch :close + get :view_response end end end @@ -40,4 +41,4 @@ end get "home", to: "home#index" -end \ No newline at end of file +end From c4c07158b56f73e75ccb531e3b0b33724879b0d9 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 8 Dec 2025 22:16:40 -0300 Subject: [PATCH 33/63] feat/remocao do botao remember me da tela de login --- .dockerignore | 102 +- .gitattributes | 18 +- .github/dependabot.yml | 24 +- .github/workflows/ci.yml | 180 +- .gitignore | 68 +- .kamal/hooks/docker-setup.sample | 6 +- .kamal/hooks/post-app-boot.sample | 6 +- .kamal/hooks/post-deploy.sample | 28 +- .kamal/hooks/post-proxy-reboot.sample | 6 +- .kamal/hooks/pre-app-boot.sample | 6 +- .kamal/hooks/pre-build.sample | 102 +- .kamal/hooks/pre-connect.sample | 94 +- .kamal/hooks/pre-deploy.sample | 244 +- .kamal/hooks/pre-proxy-reboot.sample | 6 +- .kamal/secrets | 34 +- .rspec | 2 +- .rubocop.yml | 16 +- .ruby-version | 2 +- Dockerfile | 144 +- Gemfile | 114 +- Gemfile.lock | 1000 +++---- README.md | 2526 ++++++++--------- Rakefile | 12 +- app/assets/application.js | 10 +- .../controllers/form_fields_controller.js | 46 +- app/assets/stylesheets/application.css | 20 +- app/controllers/admin/dashboard_controller.rb | 40 +- app/controllers/admin/users_controller.rb | 92 +- app/controllers/application_controller.rb | 8 +- app/controllers/home_controller.rb | 28 +- app/controllers/student/student_controller.rb | 136 +- app/helpers/admin/dashboard_helper.rb | 4 +- app/helpers/admin/users_helper.rb | 4 +- app/helpers/application_helper.rb | 4 +- app/helpers/home_helper.rb | 4 +- app/javascript/application.js | 6 +- app/javascript/controllers/application.js | 18 +- .../controllers/hello_controller.js | 14 +- app/javascript/controllers/index.js | 8 +- app/jobs/application_job.rb | 14 +- app/mailers/application_mailer.rb | 8 +- app/models/application_record.rb | 6 +- app/models/class_member.rb | 20 +- app/models/form.rb | 64 +- app/models/form_answer.rb | 18 +- app/models/form_response.rb | 74 +- app/models/form_template.rb | 24 +- app/models/form_template_field.rb | 36 +- app/models/klass.rb | 48 +- app/models/user.rb | 92 +- app/views/admin/dashboard/index.html.erb | 166 +- app/views/admin/forms/edit.html.erb | 110 +- app/views/admin/forms/index.html.erb | 148 +- app/views/admin/forms/new.html.erb | 116 +- app/views/admin/forms/show.html.erb | 70 +- app/views/admin/users/destroy.html.erb | 4 +- app/views/admin/users/edit.html.erb | 4 +- app/views/admin/users/index.html.erb | 72 +- app/views/admin/users/show.html.erb | 4 +- app/views/admin/users/update.html.erb | 4 +- app/views/devise/confirmations/new.html.erb | 32 +- .../mailer/confirmation_instructions.html.erb | 10 +- .../devise/mailer/email_changed.html.erb | 14 +- .../devise/mailer/password_change.html.erb | 6 +- .../reset_password_instructions.html.erb | 16 +- .../mailer/unlock_instructions.html.erb | 14 +- app/views/devise/passwords/edit.html.erb | 50 +- app/views/devise/passwords/new.html.erb | 32 +- app/views/devise/registrations/edit.html.erb | 86 +- app/views/devise/registrations/new.html.erb | 58 +- app/views/devise/sessions/new.html.erb | 52 +- .../devise/shared/_error_messages.html.erb | 30 +- app/views/devise/shared/_links.html.erb | 50 +- app/views/devise/unlocks/new.html.erb | 32 +- app/views/home/index.html.erb | 188 +- app/views/layouts/application.html.erb | 56 +- app/views/layouts/mailer.html.erb | 26 +- app/views/layouts/mailer.text.erb | 2 +- app/views/pwa/manifest.json.erb | 44 +- app/views/pwa/service-worker.js | 52 +- app/views/student/forms/answer.html.erb | 220 +- app/views/student/forms/show.html.erb | 114 +- bin/brakeman | 14 +- bin/bundle | 218 +- bin/cucumber | 22 +- bin/dev | 4 +- bin/docker-entrypoint | 28 +- bin/importmap | 8 +- bin/jobs | 12 +- bin/kamal | 54 +- bin/rails | 8 +- bin/rake | 8 +- bin/rubocop | 16 +- bin/setup | 68 +- bin/thrust | 10 +- class_members.json | 826 +++--- classes.json | 58 +- config.ru | 12 +- config/application.rb | 54 +- config/boot.rb | 8 +- config/cable.yml | 34 +- config/cache.yml | 32 +- config/cucumber.yml | 16 +- config/database.yml | 82 +- config/deploy.yml | 232 +- config/environment.rb | 10 +- config/environments/development.rb | 152 +- config/environments/production.rb | 180 +- config/environments/test.rb | 114 +- config/importmap.rb | 22 +- config/initializers/assets.rb | 14 +- .../initializers/content_security_policy.rb | 50 +- config/initializers/devise.rb | 626 ++-- .../initializers/filter_parameter_logging.rb | 16 +- config/initializers/inflections.rb | 32 +- config/locales/devise.en.yml | 130 +- config/locales/en.yml | 62 +- config/puma.rb | 82 +- config/queue.yml | 36 +- config/recurring.yml | 30 +- config/routes.rb | 88 +- config/storage.yml | 68 +- db/cable_schema.rb | 22 +- db/cache_schema.rb | 24 +- .../20251207235542_devise_create_users.rb | 106 +- db/migrate/20251208005928_create_klasses.rb | 24 +- .../20251208005929_create_class_members.rb | 22 +- .../20251208005930_add_fields_to_users.rb | 16 +- .../20251208031921_create_form_templates.rb | 22 +- ...51208031922_create_form_template_fields.rb | 28 +- db/migrate/20251208031923_create_forms.rb | 28 +- .../20251208031924_create_form_responses.rb | 22 +- .../20251208031925_create_form_answers.rb | 22 +- db/queue_schema.rb | 258 +- db/seeds.rb | 18 +- er.pending_forms.count | 42 +- features/definir_senha_cadastro.feature | 78 +- features/exportar_relatorios_csv.feature | 66 +- .../formularios_discentes_docentes.feature | 76 +- .../gerenciar_turmas_departamento.feature | 62 +- features/redefinir_senha.feature | 74 +- features/support/env.rb | 106 +- .../visualizar_formularios_criados.feature | 60 +- ...alizar_formularios_nao_respondidos.feature | 60 +- lib/tasks/cucumber.rake | 138 +- public/400.html | 228 +- public/404.html | 228 +- public/406-unsupported-browser.html | 228 +- public/422.html | 228 +- public/500.html | 228 +- public/icon.svg | 6 +- public/robots.txt | 2 +- spec/factories/class_members.rb | 16 +- spec/factories/form_answers.rb | 16 +- spec/factories/form_responses.rb | 16 +- spec/factories/form_template_fields.rb | 22 +- spec/factories/form_templates.rb | 16 +- spec/factories/forms.rb | 22 +- spec/factories/klasses.rb | 18 +- spec/factories/users.rb | 22 +- spec/helpers/admin/dashboard_helper_spec.rb | 30 +- spec/helpers/admin/users_helper_spec.rb | 30 +- spec/helpers/home_helper_spec.rb | 30 +- spec/models/class_member_spec.rb | 100 +- spec/models/form_answer_spec.rb | 252 +- spec/models/form_response_spec.rb | 164 +- spec/models/form_spec.rb | 186 +- spec/models/form_template_field_spec.rb | 152 +- spec/models/form_template_spec.rb | 112 +- spec/models/klass_spec.rb | 122 +- spec/models/user_spec.rb | 150 +- spec/rails_helper.rb | 158 +- spec/requests/admin/dashboard_spec.rb | 22 +- spec/requests/admin/users_spec.rb | 78 +- spec/requests/home_spec.rb | 22 +- spec/spec_helper.rb | 188 +- .../admin/dashboard/index.html.erb_spec.rb | 10 +- .../admin/users/destroy.html.erb_spec.rb | 10 +- spec/views/admin/users/edit.html.erb_spec.rb | 10 +- spec/views/admin/users/index.html.erb_spec.rb | 10 +- spec/views/admin/users/show.html.erb_spec.rb | 10 +- .../views/admin/users/update.html.erb_spec.rb | 10 +- spec/views/home/index.html.erb_spec.rb | 10 +- test/application_system_test_case.rb | 10 +- test/test_helper.rb | 30 +- 185 files changed, 7790 insertions(+), 7790 deletions(-) diff --git a/.dockerignore b/.dockerignore index 325bfc036d..fbc400407d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,51 +1,51 @@ -# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. - -# Ignore git directory. -/.git/ -/.gitignore - -# Ignore bundler config. -/.bundle - -# Ignore all environment files. -/.env* - -# Ignore all default key files. -/config/master.key -/config/credentials/*.key - -# Ignore all logfiles and tempfiles. -/log/* -/tmp/* -!/log/.keep -!/tmp/.keep - -# Ignore pidfiles, but keep the directory. -/tmp/pids/* -!/tmp/pids/.keep - -# Ignore storage (uploaded files in development and any SQLite databases). -/storage/* -!/storage/.keep -/tmp/storage/* -!/tmp/storage/.keep - -# Ignore assets. -/node_modules/ -/app/assets/builds/* -!/app/assets/builds/.keep -/public/assets - -# Ignore CI service files. -/.github - -# Ignore Kamal files. -/config/deploy*.yml -/.kamal - -# Ignore development files -/.devcontainer - -# Ignore Docker-related files -/.dockerignore -/Dockerfile* +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ +/.gitignore + +# Ignore bundler config. +/.bundle + +# Ignore all environment files. +/.env* + +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/.keep + +# Ignore assets. +/node_modules/ +/app/assets/builds/* +!/app/assets/builds/.keep +/public/assets + +# Ignore CI service files. +/.github + +# Ignore Kamal files. +/config/deploy*.yml +/.kamal + +# Ignore development files +/.devcontainer + +# Ignore Docker-related files +/.dockerignore +/Dockerfile* diff --git a/.gitattributes b/.gitattributes index 8dc4323435..0ac91dd2d8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,9 @@ -# See https://git-scm.com/docs/gitattributes for more about git attribute files. - -# Mark the database schema as having been generated. -db/schema.rb linguist-generated - -# Mark any vendored files as having been vendored. -vendor/* linguist-vendored -config/credentials/*.yml.enc diff=rails_credentials -config/credentials.yml.enc diff=rails_credentials +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored +config/credentials/*.yml.enc diff=rails_credentials +config/credentials.yml.enc diff=rails_credentials diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f0527e6be1..c815c2cd7d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,12 @@ -version: 2 -updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b7c0c59b3..653b137e79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,90 +1,90 @@ -name: CI - -on: - pull_request: - push: - branches: [ main ] - -jobs: - scan_ruby: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Scan for common Rails security vulnerabilities using static analysis - run: bin/brakeman --no-pager - - scan_js: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Scan for security vulnerabilities in JavaScript dependencies - run: bin/importmap audit - - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Lint code for consistent style - run: bin/rubocop -f github - - test: - runs-on: ubuntu-latest - - # services: - # redis: - # image: redis - # ports: - # - 6379:6379 - # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - steps: - - name: Install packages - run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config google-chrome-stable - - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: .ruby-version - bundler-cache: true - - - name: Run tests - env: - RAILS_ENV: test - # REDIS_URL: redis://localhost:6379/0 - run: bin/rails db:test:prepare test test:system - - - name: Keep screenshots from failed system tests - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots - path: ${{ github.workspace }}/tmp/screenshots - if-no-files-found: ignore +name: CI + +on: + pull_request: + push: + branches: [ main ] + +jobs: + scan_ruby: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for common Rails security vulnerabilities using static analysis + run: bin/brakeman --no-pager + + scan_js: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for security vulnerabilities in JavaScript dependencies + run: bin/importmap audit + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop -f github + + test: + runs-on: ubuntu-latest + + # services: + # redis: + # image: redis + # ports: + # - 6379:6379 + # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Install packages + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config google-chrome-stable + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run tests + env: + RAILS_ENV: test + # REDIS_URL: redis://localhost:6379/0 + run: bin/rails db:test:prepare test test:system + + - name: Keep screenshots from failed system tests + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots + path: ${{ github.workspace }}/tmp/screenshots + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index f92525ca5e..189a50bf5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,34 @@ -# See https://help.github.com/articles/ignoring-files for more about ignoring files. -# -# Temporary files generated by your text editor or operating system -# belong in git's global ignore instead: -# `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` - -# Ignore bundler config. -/.bundle - -# Ignore all environment files. -/.env* - -# Ignore all logfiles and tempfiles. -/log/* -/tmp/* -!/log/.keep -!/tmp/.keep - -# Ignore pidfiles, but keep the directory. -/tmp/pids/* -!/tmp/pids/ -!/tmp/pids/.keep - -# Ignore storage (uploaded files in development and any SQLite databases). -/storage/* -!/storage/.keep -/tmp/storage/* -!/tmp/storage/ -!/tmp/storage/.keep - -/public/assets - -# Ignore master key for decrypting credentials and more. -/config/master.key +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# Temporary files generated by your text editor or operating system +# belong in git's global ignore instead: +# `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` + +# Ignore bundler config. +/.bundle + +# Ignore all environment files. +/.env* + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +/public/assets + +# Ignore master key for decrypting credentials and more. +/config/master.key diff --git a/.kamal/hooks/docker-setup.sample b/.kamal/hooks/docker-setup.sample index 2fb07d7d7a..7b8a4ea378 100644 --- a/.kamal/hooks/docker-setup.sample +++ b/.kamal/hooks/docker-setup.sample @@ -1,3 +1,3 @@ -#!/bin/sh - -echo "Docker set up on $KAMAL_HOSTS..." +#!/bin/sh + +echo "Docker set up on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-app-boot.sample b/.kamal/hooks/post-app-boot.sample index 70f9c4bc95..84353aaaaa 100644 --- a/.kamal/hooks/post-app-boot.sample +++ b/.kamal/hooks/post-app-boot.sample @@ -1,3 +1,3 @@ -#!/bin/sh - -echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..." +#!/bin/sh + +echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-deploy.sample b/.kamal/hooks/post-deploy.sample index fd364c2a77..5fe35d16cd 100644 --- a/.kamal/hooks/post-deploy.sample +++ b/.kamal/hooks/post-deploy.sample @@ -1,14 +1,14 @@ -#!/bin/sh - -# A sample post-deploy hook -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) -# KAMAL_RUNTIME - -echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" +#!/bin/sh + +# A sample post-deploy hook +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" diff --git a/.kamal/hooks/post-proxy-reboot.sample b/.kamal/hooks/post-proxy-reboot.sample index 1435a677f2..ba2e14929b 100644 --- a/.kamal/hooks/post-proxy-reboot.sample +++ b/.kamal/hooks/post-proxy-reboot.sample @@ -1,3 +1,3 @@ -#!/bin/sh - -echo "Rebooted kamal-proxy on $KAMAL_HOSTS" +#!/bin/sh + +echo "Rebooted kamal-proxy on $KAMAL_HOSTS" diff --git a/.kamal/hooks/pre-app-boot.sample b/.kamal/hooks/pre-app-boot.sample index 45f7355045..e5201d609a 100644 --- a/.kamal/hooks/pre-app-boot.sample +++ b/.kamal/hooks/pre-app-boot.sample @@ -1,3 +1,3 @@ -#!/bin/sh - -echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..." +#!/bin/sh + +echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/pre-build.sample b/.kamal/hooks/pre-build.sample index c5a55678b2..bb3d9dc687 100644 --- a/.kamal/hooks/pre-build.sample +++ b/.kamal/hooks/pre-build.sample @@ -1,51 +1,51 @@ -#!/bin/sh - -# A sample pre-build hook -# -# Checks: -# 1. We have a clean checkout -# 2. A remote is configured -# 3. The branch has been pushed to the remote -# 4. The version we are deploying matches the remote -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) - -if [ -n "$(git status --porcelain)" ]; then - echo "Git checkout is not clean, aborting..." >&2 - git status --porcelain >&2 - exit 1 -fi - -first_remote=$(git remote) - -if [ -z "$first_remote" ]; then - echo "No git remote set, aborting..." >&2 - exit 1 -fi - -current_branch=$(git branch --show-current) - -if [ -z "$current_branch" ]; then - echo "Not on a git branch, aborting..." >&2 - exit 1 -fi - -remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) - -if [ -z "$remote_head" ]; then - echo "Branch not pushed to remote, aborting..." >&2 - exit 1 -fi - -if [ "$KAMAL_VERSION" != "$remote_head" ]; then - echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 - exit 1 -fi - -exit 0 +#!/bin/sh + +# A sample pre-build hook +# +# Checks: +# 1. We have a clean checkout +# 2. A remote is configured +# 3. The branch has been pushed to the remote +# 4. The version we are deploying matches the remote +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) + +if [ -n "$(git status --porcelain)" ]; then + echo "Git checkout is not clean, aborting..." >&2 + git status --porcelain >&2 + exit 1 +fi + +first_remote=$(git remote) + +if [ -z "$first_remote" ]; then + echo "No git remote set, aborting..." >&2 + exit 1 +fi + +current_branch=$(git branch --show-current) + +if [ -z "$current_branch" ]; then + echo "Not on a git branch, aborting..." >&2 + exit 1 +fi + +remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) + +if [ -z "$remote_head" ]; then + echo "Branch not pushed to remote, aborting..." >&2 + exit 1 +fi + +if [ "$KAMAL_VERSION" != "$remote_head" ]; then + echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 + exit 1 +fi + +exit 0 diff --git a/.kamal/hooks/pre-connect.sample b/.kamal/hooks/pre-connect.sample index 77744bdca8..e1bcab1e8c 100644 --- a/.kamal/hooks/pre-connect.sample +++ b/.kamal/hooks/pre-connect.sample @@ -1,47 +1,47 @@ -#!/usr/bin/env ruby - -# A sample pre-connect check -# -# Warms DNS before connecting to hosts in parallel -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) -# KAMAL_RUNTIME - -hosts = ENV["KAMAL_HOSTS"].split(",") -results = nil -max = 3 - -elapsed = Benchmark.realtime do - results = hosts.map do |host| - Thread.new do - tries = 1 - - begin - Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) - rescue SocketError - if tries < max - puts "Retrying DNS warmup: #{host}" - tries += 1 - sleep rand - retry - else - puts "DNS warmup failed: #{host}" - host - end - end - - tries - end - end.map(&:value) -end - -retries = results.sum - hosts.size -nopes = results.count { |r| r == max } - -puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] +#!/usr/bin/env ruby + +# A sample pre-connect check +# +# Warms DNS before connecting to hosts in parallel +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) +# KAMAL_RUNTIME + +hosts = ENV["KAMAL_HOSTS"].split(",") +results = nil +max = 3 + +elapsed = Benchmark.realtime do + results = hosts.map do |host| + Thread.new do + tries = 1 + + begin + Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) + rescue SocketError + if tries < max + puts "Retrying DNS warmup: #{host}" + tries += 1 + sleep rand + retry + else + puts "DNS warmup failed: #{host}" + host + end + end + + tries + end + end.map(&:value) +end + +retries = results.sum - hosts.size +nopes = results.count { |r| r == max } + +puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] diff --git a/.kamal/hooks/pre-deploy.sample b/.kamal/hooks/pre-deploy.sample index 05b3055b72..62c87a88bb 100644 --- a/.kamal/hooks/pre-deploy.sample +++ b/.kamal/hooks/pre-deploy.sample @@ -1,122 +1,122 @@ -#!/usr/bin/env ruby - -# A sample pre-deploy hook -# -# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. -# -# Fails unless the combined status is "success" -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_COMMAND -# KAMAL_SUBCOMMAND -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) - -# Only check the build status for production deployments -if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" - exit 0 -end - -require "bundler/inline" - -# true = install gems so this is fast on repeat invocations -gemfile(true, quiet: true) do - source "https://rubygems.org" - - gem "octokit" - gem "faraday-retry" -end - -MAX_ATTEMPTS = 72 -ATTEMPTS_GAP = 10 - -def exit_with_error(message) - $stderr.puts message - exit 1 -end - -class GithubStatusChecks - attr_reader :remote_url, :git_sha, :github_client, :combined_status - - def initialize - @remote_url = github_repo_from_remote_url - @git_sha = `git rev-parse HEAD`.strip - @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) - refresh! - end - - def refresh! - @combined_status = github_client.combined_status(remote_url, git_sha) - end - - def state - combined_status[:state] - end - - def first_status_url - first_status = combined_status[:statuses].find { |status| status[:state] == state } - first_status && first_status[:target_url] - end - - def complete_count - combined_status[:statuses].count { |status| status[:state] != "pending"} - end - - def total_count - combined_status[:statuses].count - end - - def current_status - if total_count > 0 - "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." - else - "Build not started..." - end - end - - private - def github_repo_from_remote_url - url = `git config --get remote.origin.url`.strip.delete_suffix(".git") - if url.start_with?("https://github.com/") - url.delete_prefix("https://github.com/") - elsif url.start_with?("git@github.com:") - url.delete_prefix("git@github.com:") - else - url - end - end -end - - -$stdout.sync = true - -begin - puts "Checking build status..." - - attempts = 0 - checks = GithubStatusChecks.new - - loop do - case checks.state - when "success" - puts "Checks passed, see #{checks.first_status_url}" - exit 0 - when "failure" - exit_with_error "Checks failed, see #{checks.first_status_url}" - when "pending" - attempts += 1 - end - - exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS - - puts checks.current_status - sleep(ATTEMPTS_GAP) - checks.refresh! - end -rescue Octokit::NotFound - exit_with_error "Build status could not be found" -end +#!/usr/bin/env ruby + +# A sample pre-deploy hook +# +# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. +# +# Fails unless the combined status is "success" +# +# These environment variables are available: +# KAMAL_RECORDED_AT +# KAMAL_PERFORMER +# KAMAL_VERSION +# KAMAL_HOSTS +# KAMAL_COMMAND +# KAMAL_SUBCOMMAND +# KAMAL_ROLES (if set) +# KAMAL_DESTINATION (if set) + +# Only check the build status for production deployments +if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" + exit 0 +end + +require "bundler/inline" + +# true = install gems so this is fast on repeat invocations +gemfile(true, quiet: true) do + source "https://rubygems.org" + + gem "octokit" + gem "faraday-retry" +end + +MAX_ATTEMPTS = 72 +ATTEMPTS_GAP = 10 + +def exit_with_error(message) + $stderr.puts message + exit 1 +end + +class GithubStatusChecks + attr_reader :remote_url, :git_sha, :github_client, :combined_status + + def initialize + @remote_url = github_repo_from_remote_url + @git_sha = `git rev-parse HEAD`.strip + @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) + refresh! + end + + def refresh! + @combined_status = github_client.combined_status(remote_url, git_sha) + end + + def state + combined_status[:state] + end + + def first_status_url + first_status = combined_status[:statuses].find { |status| status[:state] == state } + first_status && first_status[:target_url] + end + + def complete_count + combined_status[:statuses].count { |status| status[:state] != "pending"} + end + + def total_count + combined_status[:statuses].count + end + + def current_status + if total_count > 0 + "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." + else + "Build not started..." + end + end + + private + def github_repo_from_remote_url + url = `git config --get remote.origin.url`.strip.delete_suffix(".git") + if url.start_with?("https://github.com/") + url.delete_prefix("https://github.com/") + elsif url.start_with?("git@github.com:") + url.delete_prefix("git@github.com:") + else + url + end + end +end + + +$stdout.sync = true + +begin + puts "Checking build status..." + + attempts = 0 + checks = GithubStatusChecks.new + + loop do + case checks.state + when "success" + puts "Checks passed, see #{checks.first_status_url}" + exit 0 + when "failure" + exit_with_error "Checks failed, see #{checks.first_status_url}" + when "pending" + attempts += 1 + end + + exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS + + puts checks.current_status + sleep(ATTEMPTS_GAP) + checks.refresh! + end +rescue Octokit::NotFound + exit_with_error "Build status could not be found" +end diff --git a/.kamal/hooks/pre-proxy-reboot.sample b/.kamal/hooks/pre-proxy-reboot.sample index 061f8059e6..807fb707ef 100644 --- a/.kamal/hooks/pre-proxy-reboot.sample +++ b/.kamal/hooks/pre-proxy-reboot.sample @@ -1,3 +1,3 @@ -#!/bin/sh - -echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." +#!/bin/sh + +echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." diff --git a/.kamal/secrets b/.kamal/secrets index 9a771a3985..54ffde8407 100644 --- a/.kamal/secrets +++ b/.kamal/secrets @@ -1,17 +1,17 @@ -# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, -# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either -# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. - -# Example of extracting secrets from 1password (or another compatible pw manager) -# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) -# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) -# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) - -# Use a GITHUB_TOKEN if private repositories are needed for the image -# GITHUB_TOKEN=$(gh config get -h github.com oauth_token) - -# Grab the registry password from ENV -KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD - -# Improve security by using a password manager. Never check config/master.key into git! -RAILS_MASTER_KEY=$(cat config/master.key) +# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, +# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either +# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. + +# Example of extracting secrets from 1password (or another compatible pw manager) +# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) +# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) +# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) + +# Use a GITHUB_TOKEN if private repositories are needed for the image +# GITHUB_TOKEN=$(gh config get -h github.com oauth_token) + +# Grab the registry password from ENV +KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD + +# Improve security by using a password manager. Never check config/master.key into git! +RAILS_MASTER_KEY=$(cat config/master.key) diff --git a/.rspec b/.rspec index c99d2e7396..2673547e8d 100644 --- a/.rspec +++ b/.rspec @@ -1 +1 @@ ---require spec_helper +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml index f9d86d4a54..eda589f6bc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +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 +# 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/.ruby-version b/.ruby-version index fdeaef88f6..919911cc0c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.4.7 +ruby-3.4.7 diff --git a/Dockerfile b/Dockerfile index 44381516b7..9e47ef92bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,72 +1,72 @@ -# syntax=docker/dockerfile:1 -# check=error=true - -# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: -# docker build -t projeto_camaar . -# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name projeto_camaar projeto_camaar - -# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html - -# Make sure RUBY_VERSION matches the Ruby version in .ruby-version -ARG RUBY_VERSION=3.4.7 -FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base - -# Rails app lives here -WORKDIR /rails - -# Install base packages -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives - -# Set production environment -ENV RAILS_ENV="production" \ - BUNDLE_DEPLOYMENT="1" \ - BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development" - -# Throw-away build stage to reduce size of final image -FROM base AS build - -# Install packages needed to build gems -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives - -# Install application gems -COPY Gemfile Gemfile.lock ./ -RUN bundle install && \ - rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ - bundle exec bootsnap precompile --gemfile - -# Copy application code -COPY . . - -# Precompile bootsnap code for faster boot times -RUN bundle exec bootsnap precompile app/ lib/ - -# Precompiling assets for production without requiring secret RAILS_MASTER_KEY -RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile - - - - -# Final stage for app image -FROM base - -# Copy built artifacts: gems, application -COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" -COPY --from=build /rails /rails - -# Run and own only the runtime files as a non-root user for security -RUN groupadd --system --gid 1000 rails && \ - useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ - chown -R rails:rails db log storage tmp -USER 1000:1000 - -# Entrypoint prepares the database. -ENTRYPOINT ["/rails/bin/docker-entrypoint"] - -# Start server via Thruster by default, this can be overwritten at runtime -EXPOSE 80 -CMD ["./bin/thrust", "./bin/rails", "server"] +# syntax=docker/dockerfile:1 +# check=error=true + +# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: +# docker build -t projeto_camaar . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name projeto_camaar projeto_camaar + +# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version +ARG RUBY_VERSION=3.4.7 +FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base + +# Rails app lives here +WORKDIR /rails + +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Set production environment +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" + +# Throw-away build stage to reduce size of final image +FROM base AS build + +# Install packages needed to build gems +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Install application gems +COPY Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile + +# Copy application code +COPY . . + +# Precompile bootsnap code for faster boot times +RUN bundle exec bootsnap precompile app/ lib/ + +# Precompiling assets for production without requiring secret RAILS_MASTER_KEY +RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile + + + + +# Final stage for app image +FROM base + +# Copy built artifacts: gems, application +COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --from=build /rails /rails + +# Run and own only the runtime files as a non-root user for security +RUN groupadd --system --gid 1000 rails && \ + useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ + chown -R rails:rails db log storage tmp +USER 1000:1000 + +# Entrypoint prepares the database. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Start server via Thruster by default, this can be overwritten at runtime +EXPOSE 80 +CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/Gemfile b/Gemfile index 2975b2f6b6..1644ec664e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,57 +1,57 @@ -source "https://rubygems.org" - -gem "rails", "~> 8.0.4" -gem "propshaft" -gem "sqlite3", ">= 2.1" -gem "puma", ">= 5.0" -gem "importmap-rails" -gem "turbo-rails" -gem "stimulus-rails" -gem "jbuilder" - -# ============================================ -# Authentication -# ============================================ -gem "devise", "~> 4.9" - -gem "bcrypt", "~> 3.1.7" - -gem "tzinfo-data", platforms: %i[ windows jruby ] - -gem "solid_cache" -gem "solid_queue" -gem "solid_cable" - -gem "bootsnap", require: false -gem "kamal", require: false -gem "thruster", require: false - -group :development, :test do - gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" - gem "brakeman", require: false - gem "rubocop-rails-omakase", require: false - - # ============================================ - # RSpec - Test-Driven Development (TDD) - # ============================================ - gem "rspec-rails", "~> 6.0.0" - gem "factory_bot_rails", "~> 6.2" - gem "faker", "~> 3.2" - gem "shoulda-matchers", "~> 5.1" - gem "csv" -end - -group :development do - gem "web-console" -end - -group :test do - gem "capybara" - gem "selenium-webdriver" - - gem 'cucumber-rails', require: false - gem 'database_cleaner' - - gem "simplecov", "~> 0.22.0" - gem "simplecov-console" -end +source "https://rubygems.org" + +gem "rails", "~> 8.0.4" +gem "propshaft" +gem "sqlite3", ">= 2.1" +gem "puma", ">= 5.0" +gem "importmap-rails" +gem "turbo-rails" +gem "stimulus-rails" +gem "jbuilder" + +# ============================================ +# Authentication +# ============================================ +gem "devise", "~> 4.9" + +gem "bcrypt", "~> 3.1.7" + +gem "tzinfo-data", platforms: %i[ windows jruby ] + +gem "solid_cache" +gem "solid_queue" +gem "solid_cable" + +gem "bootsnap", require: false +gem "kamal", require: false +gem "thruster", require: false + +group :development, :test do + gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" + gem "brakeman", require: false + gem "rubocop-rails-omakase", require: false + + # ============================================ + # RSpec - Test-Driven Development (TDD) + # ============================================ + gem "rspec-rails", "~> 6.0.0" + gem "factory_bot_rails", "~> 6.2" + gem "faker", "~> 3.2" + gem "shoulda-matchers", "~> 5.1" + gem "csv" +end + +group :development do + gem "web-console" +end + +group :test do + gem "capybara" + gem "selenium-webdriver" + + gem 'cucumber-rails', require: false + gem 'database_cleaner' + + gem "simplecov", "~> 0.22.0" + gem "simplecov-console" +end diff --git a/Gemfile.lock b/Gemfile.lock index 61c88f2726..f438714971 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,500 +1,500 @@ -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.7) - public_suffix (>= 2.0.2, < 7.0) - ansi (1.5.0) - ast (2.4.3) - base64 (0.3.0) - bcrypt (3.1.20) - bcrypt_pbkdf (1.1.1) - benchmark (0.5.0) - bigdecimal (3.3.1) - bindex (0.8.1) - bootsnap (1.18.6) - 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) - concurrent-ruby (1.3.5) - connection_pool (2.5.4) - crass (1.0.6) - csv (3.3.5) - 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 (34.0.0) - cucumber-messages (> 25, < 29) - cucumber-html-formatter (21.15.1) - cucumber-messages (> 19, < 28) - cucumber-messages (27.2.0) - cucumber-rails (4.0.0) - capybara (>= 3.25, < 4) - cucumber (>= 7, < 11) - railties (>= 6.1, < 9) - cucumber-tag-expressions (8.0.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.0) - debug (1.11.0) - irb (~> 1.10) - reline (>= 0.3.8) - devise (4.9.4) - bcrypt (~> 3.0) - orm_adapter (~> 0.1) - railties (>= 4.1.0) - responders - warden (~> 1.2.3) - diff-lcs (1.6.2) - docile (1.4.1) - dotenv (3.1.8) - drb (2.2.3) - ed25519 (1.4.0) - erb (6.0.0) - 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-aarch64-linux-gnu) - ffi (1.17.2-aarch64-linux-musl) - ffi (1.17.2-arm-linux-gnu) - ffi (1.17.2-arm-linux-musl) - ffi (1.17.2-x86_64-linux-gnu) - ffi (1.17.2-x86_64-linux-musl) - 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) - 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.16.0) - kamal (2.8.2) - activesupport (>= 7.0) - base64 (~> 0.2) - bcrypt_pbkdf (~> 1.0) - concurrent-ruby (~> 1.2) - dotenv (~> 3.1) - ed25519 (~> 1.4) - net-ssh (~> 7.3) - sshkit (>= 1.23.0, < 2.0) - thor (~> 1.3) - zeitwerk (>= 2.6.18, < 3.0) - language_server-protocol (3.17.0.5) - lint_roller (1.1.0) - logger (1.7.0) - loofah (2.24.1) - 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) - mini_mime (1.1.5) - minitest (5.26.1) - msgpack (1.8.0) - multi_test (1.1.0) - net-imap (0.5.12) - date - net-protocol - net-pop (0.1.2) - net-protocol - net-protocol (0.2.2) - timeout - net-scp (4.1.0) - net-ssh (>= 2.6.5, < 8.0.0) - net-sftp (4.0.0) - net-ssh (>= 5.0.0, < 8.0.0) - net-smtp (0.5.1) - net-protocol - net-ssh (7.3.0) - nio4r (2.7.5) - nokogiri (1.18.10-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-musl) - racc (~> 1.4) - orm_adapter (0.5.0) - ostruct (0.6.3) - parallel (1.27.0) - parser (3.3.10.0) - ast (~> 2.4.1) - racc - 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.2.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-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.15.1) - erb - psych (>= 4.0.0) - tsort - regexp_parser (2.11.3) - reline (0.6.3) - io-console (~> 0.5) - responders (3.2.0) - actionpack (>= 7.0) - railties (>= 7.0) - 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 (6.0.4) - actionpack (>= 6.1) - activesupport (>= 6.1) - railties (>= 6.1) - rspec-core (~> 3.12) - rspec-expectations (~> 3.12) - rspec-mocks (~> 3.12) - rspec-support (~> 3.12) - 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.33.4) - 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) - rubyzip (3.2.2) - securerandom (0.4.1) - selenium-webdriver (4.38.0) - base64 (~> 0.2) - logger (~> 1.4) - rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2, < 4.0) - websocket (~> 1.0) - 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-console (0.9.4) - ansi - simplecov - terminal-table - 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.0-aarch64-linux-gnu) - sqlite3 (2.8.0-aarch64-linux-musl) - sqlite3 (2.8.0-arm-linux-gnu) - sqlite3 (2.8.0-arm-linux-musl) - sqlite3 (2.8.0-x86_64-linux-gnu) - sqlite3 (2.8.0-x86_64-linux-musl) - sshkit (1.24.0) - base64 - logger - net-scp (>= 1.1.2) - net-sftp (>= 2.1.2) - net-ssh (>= 2.8.0) - ostruct - stimulus-rails (1.3.4) - railties (>= 6.0.0) - stringio (3.1.8) - sys-uname (1.4.1) - ffi (~> 1.1) - memoist3 (~> 1.0.0) - terminal-table (4.0.0) - unicode-display_width (>= 1.1.1, < 4) - thor (1.4.0) - thruster (0.1.16) - thruster (0.1.16-aarch64-linux) - thruster (0.1.16-x86_64-linux) - timeout (0.4.4) - tsort (0.2.0) - turbo-rails (2.0.20) - actionpack (>= 7.1.0) - railties (>= 7.1.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) - uri (1.1.1) - useragent (0.16.11) - warden (1.2.9) - rack (>= 2.0.9) - web-console (4.2.1) - actionview (>= 6.0.0) - activemodel (>= 6.0.0) - bindex (>= 0.4.0) - railties (>= 6.0.0) - websocket (1.2.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.7.3) - -PLATFORMS - aarch64-linux - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-gnu - arm-linux-musl - x86_64-linux - x86_64-linux-gnu - x86_64-linux-musl - -DEPENDENCIES - bcrypt (~> 3.1.7) - bootsnap - brakeman - capybara - csv - cucumber-rails - database_cleaner - debug - devise (~> 4.9) - factory_bot_rails (~> 6.2) - faker (~> 3.2) - importmap-rails - jbuilder - kamal - propshaft - puma (>= 5.0) - rails (~> 8.0.4) - rspec-rails (~> 6.0.0) - rubocop-rails-omakase - selenium-webdriver - shoulda-matchers (~> 5.1) - simplecov (~> 0.22.0) - simplecov-console - solid_cable - solid_cache - solid_queue - sqlite3 (>= 2.1) - stimulus-rails - thruster - turbo-rails - tzinfo-data - web-console - -BUNDLED WITH - 2.6.9 +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.7) + public_suffix (>= 2.0.2, < 7.0) + ansi (1.5.0) + ast (2.4.3) + base64 (0.3.0) + bcrypt (3.1.20) + bcrypt_pbkdf (1.1.1) + benchmark (0.5.0) + bigdecimal (3.3.1) + bindex (0.8.1) + bootsnap (1.18.6) + 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) + concurrent-ruby (1.3.5) + connection_pool (2.5.4) + crass (1.0.6) + csv (3.3.5) + 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 (34.0.0) + cucumber-messages (> 25, < 29) + cucumber-html-formatter (21.15.1) + cucumber-messages (> 19, < 28) + cucumber-messages (27.2.0) + cucumber-rails (4.0.0) + capybara (>= 3.25, < 4) + cucumber (>= 7, < 11) + railties (>= 6.1, < 9) + cucumber-tag-expressions (8.0.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.0) + debug (1.11.0) + irb (~> 1.10) + reline (>= 0.3.8) + devise (4.9.4) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) + diff-lcs (1.6.2) + docile (1.4.1) + dotenv (3.1.8) + drb (2.2.3) + ed25519 (1.4.0) + erb (6.0.0) + 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-aarch64-linux-gnu) + ffi (1.17.2-aarch64-linux-musl) + ffi (1.17.2-arm-linux-gnu) + ffi (1.17.2-arm-linux-musl) + ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) + 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) + 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.16.0) + kamal (2.8.2) + activesupport (>= 7.0) + base64 (~> 0.2) + bcrypt_pbkdf (~> 1.0) + concurrent-ruby (~> 1.2) + dotenv (~> 3.1) + ed25519 (~> 1.4) + net-ssh (~> 7.3) + sshkit (>= 1.23.0, < 2.0) + thor (~> 1.3) + zeitwerk (>= 2.6.18, < 3.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.24.1) + 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) + mini_mime (1.1.5) + minitest (5.26.1) + msgpack (1.8.0) + multi_test (1.1.0) + net-imap (0.5.12) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-scp (4.1.0) + net-ssh (>= 2.6.5, < 8.0.0) + net-sftp (4.0.0) + net-ssh (>= 5.0.0, < 8.0.0) + net-smtp (0.5.1) + net-protocol + net-ssh (7.3.0) + nio4r (2.7.5) + nokogiri (1.18.10-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + orm_adapter (0.5.0) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.0) + ast (~> 2.4.1) + racc + 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.2.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-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.15.1) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) + 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 (6.0.4) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + 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.33.4) + 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) + rubyzip (3.2.2) + securerandom (0.4.1) + selenium-webdriver (4.38.0) + base64 (~> 0.2) + logger (~> 1.4) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 4.0) + websocket (~> 1.0) + 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-console (0.9.4) + ansi + simplecov + terminal-table + 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.0-aarch64-linux-gnu) + sqlite3 (2.8.0-aarch64-linux-musl) + sqlite3 (2.8.0-arm-linux-gnu) + sqlite3 (2.8.0-arm-linux-musl) + sqlite3 (2.8.0-x86_64-linux-gnu) + sqlite3 (2.8.0-x86_64-linux-musl) + sshkit (1.24.0) + base64 + logger + net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) + net-ssh (>= 2.8.0) + ostruct + stimulus-rails (1.3.4) + railties (>= 6.0.0) + stringio (3.1.8) + sys-uname (1.4.1) + ffi (~> 1.1) + memoist3 (~> 1.0.0) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + thor (1.4.0) + thruster (0.1.16) + thruster (0.1.16-aarch64-linux) + thruster (0.1.16-x86_64-linux) + timeout (0.4.4) + tsort (0.2.0) + turbo-rails (2.0.20) + actionpack (>= 7.1.0) + railties (>= 7.1.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) + uri (1.1.1) + useragent (0.16.11) + warden (1.2.9) + rack (>= 2.0.9) + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) + bindex (>= 0.4.0) + railties (>= 6.0.0) + websocket (1.2.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.7.3) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bcrypt (~> 3.1.7) + bootsnap + brakeman + capybara + csv + cucumber-rails + database_cleaner + debug + devise (~> 4.9) + factory_bot_rails (~> 6.2) + faker (~> 3.2) + importmap-rails + jbuilder + kamal + propshaft + puma (>= 5.0) + rails (~> 8.0.4) + rspec-rails (~> 6.0.0) + rubocop-rails-omakase + selenium-webdriver + shoulda-matchers (~> 5.1) + simplecov (~> 0.22.0) + simplecov-console + solid_cable + solid_cache + solid_queue + sqlite3 (>= 2.1) + stimulus-rails + thruster + turbo-rails + tzinfo-data + web-console + +BUNDLED WITH + 2.6.9 diff --git a/README.md b/README.md index 8a1af75dd1..6567634933 100644 --- a/README.md +++ b/README.md @@ -1,1263 +1,1263 @@ -# Wiki CAMAAR - Plataforma de Avaliação de Cursos - ---- - -## Informações do Projeto - -| Item | Descrição | -|------|-----------| -| **Nome do Projeto** | CAMAAR - Plataforma de Avaliação de Cursos | -| **Disciplina** | Engenharia de Software | -| **Período** | 2025.2 | -| **Integrantes do Grupo** | Bernardo Gomes Rodrigues - 231034190
Isaac Silva - 231025216
Filipe Abadia Marcelino - 190087161
Maria Carolina Burgum Abreu Jorge - 231013547 | - ---- - -## Papéis Scrum - -| Papel | Responsável | Matrícula | -|-------|-------------|-----------| -| **Scrum Master** | Bernardo Gomes Rodrigues | 231034190 | -| **Product Owner** | Maria Carolina Burgum Abreu Jorge | 231013547 | - ---- - -## Escopo do Projeto - -### Descrição Geral - -O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** integrada com o SIGAA (Sistema Integrado de Gestão de Atividades Acadêmicas) por meio de arquivos JSON. O sistema permite que administradores criem formulários de avaliação que são respondidos por alunos e professores, gerando relatórios sobre o desempenho das disciplinas e infraestrutura. - -### Objetivos Principais - -- Automatizar o processo de coleta de feedback sobre disciplinas -- Integrar dados do SIGAA para alimentar a plataforma -- Permitir análise de dados através de exportação em CSV -- Facilitar a gestão de templates reutilizáveis de formulários - -### Stakeholders - -- **Coordenadores de Curso** (Administradores): Criam e gerenciam formulários -- **Professores**: Respondem questionários sobre infraestrutura e ementa -- **Alunos**: Avaliam disciplinas e professores -- **Sistema SIGAA**: Fornecedor de dados de turmas, disciplinas e participantes - ---- - -## Funcionalidades e Regras de Negócio - -### 1. Autenticação de Usuários (2 pontos) - -**Funcionalidades:** -- Login com email ou matrícula + senha -- Definir senha após convite de cadastro - -**Regras de Negócio:** -- Senhas devem ter no mínimo 4 caracteres -- Senhas devem conter pelo menos: 1 letra maiúscula, 1 letra minúscula, 1 número e 1 caractere especial -- Usuário deve alterar senha temporária no primeiro acesso - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 2. Importação de Dados do SIGAA (8 pontos) - -**Funcionalidades:** -- Importar turmas, disciplinas e participantes do SIGAA -- Atualizar dados existentes com informações atualizadas -- Marcar turmas como inativas quando descontinuadas - -**Regras de Negócio:** -- Importação deve ser realizada apenas por administradores -- Registros duplicados são ignorados (validação por ID único do SIGAA) -- Ao importar novos usuários, criar contas com senha temporária -- Dados importados não devem sobrescrever informações customizadas no sistema -- Participantes removidos do SIGAA devem ter acesso desativado -- Atualização de mudanças de turmas de alunos/professores - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 3. Gerenciamento de Templates de Formulários (5 pontos) - -**Funcionalidades:** -- Criar templates com múltiplos tipos de questões -- Visualizar templates criados -- Editar templates -- Deletar templates - -**Regras de Negócio:** -- Template deve ter no mínimo 1 questão -- Nome do template deve ser único -- Apenas o criador do template pode editá-lo -- Tipos de questões suportados: Múltipla escolha, Texto aberto, Verdadeiro/Falso -- Edições em template afetam apenas novos formulários criados, não retroativamente - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 4. Criação de Formulários (8 pontos) - -**Funcionalidades:** -- Criar formulário baseado em template -- Selecionar turmas para aplicar o formulário -- Escolher tipo de avaliador (docentes ou discentes) -- Visualizar formulários criados - -**Regras de Negócio:** -- Formulário herda todas as questões do template no momento da criação -- Deve selecionar no mínimo 1 turma -- Deve escolher tipo de avaliador (obrigatório) -- Mesmo formulário não pode ser respondido 2 vezes pelo mesmo participante - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 5. Resposta de Questionários (5 pontos) - -**Funcionalidades:** -- Visualizar formulários não respondidos -- Responder questionário -- Submeter respostas -- Filtrar respostas de turmas - -**Regras de Negócio:** -- Participante visualiza apenas formulários de turmas onde está matriculado -- Todos os campos obrigatórios devem ser preenchidos antes de enviar -- Participante pode editar respostas enquanto formulário não foi enviado -- Uma vez enviado, não pode ser alterado -- Resposta não pode ser submetida duas vezes pelo mesmo participante -- Sistema valida tipo de resposta conforme tipo de questão - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 6. Exportação de Resultados (5 pontos) - -**Funcionalidades:** -- Exportar respostas em formato CSV -- Filtrar por formulário -- Download do arquivo - -**Regras de Negócio:** -- Apenas administrador pode exportar resultados -- Arquivo CSV deve conter: ID da resposta, Participante, Data de resposta, Respostas individuais -- Formato CSV deve ser compatível com planilhas -- Caracteres especiais devem estar codificados corretamente (UTF-8) -- Arquivo só pode ser gerado se houver respostas -- Nomes de participantes anonimizados por padrão (apenas matrícula mostrada) -- Arquivo gerado com timestamp: `formulario_[id]_[data_hora].csv` - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 7. Diferenciação de Avaliadores (3 pontos) - -**Funcionalidades:** -- Escolher tipo de avaliador (docentes ou discentes) -- Exibir formulário apenas para o tipo correto - -**Regras de Negócio:** -- Ao criar formulário, obrigatório selecionar tipo: "Docentes" ou "Discentes" -- Aluno só vê formulários criados para "Discentes" -- Professor só vê formulários criados para "Docentes" -- Sistema valida tipo de usuário ao tentar acessar formulário -- Formulário para docente é enviado apenas para professores das turmas -- Formulário para discente é enviado apenas para alunos das turmas -- Mesmo formulário com dois tipos diferentes pode coexistir para mesma turma - -**Responsável:** [Nome do desenvolvedor] - ---- - -### 8. Gestão de Usuários e Participantes (3 pontos) - -**Funcionalidades:** -- Cadastro automático de participantes na importação -- Desativação de participantes inativos - -**Regras de Negócio:** -- Usuários são criados automaticamente durante importação do SIGAA -- Usuário inativo não pode fazer login -- Quando participante é removido do SIGAA, status muda para inativo -- Email de boas-vindas é enviado automaticamente -- Senha temporária é gerada e enviada no email de boas vindas - -**Responsável:** [Nome do desenvolvedor] - ---- - -## Atribuição de Histórias de Usuário - -| # | História de Usuário | Funcionalidade | Pontos | Responsável | -|---|---------------------|-----------------|--------|-------------| -| 1 | Importar dados do SIGAA | Importação de Dados | 8 | [Nome] | -| 2 | Responder questionário sobre turma | Resposta de Questionários | 5 | [Nome] | -| 3 | Cadastrar participantes ao importar | Gestão de Usuários | 3 | [Nome] | -| 4 | Baixar resultados em CSV | Exportação de Resultados | 5 | [Nome] | -| 5 | Criar template de formulário | Gerenciamento de Templates | 5 | [Nome] | -| 6 | Criar formulário baseado em template | Criação de Formulários | 8 | [Nome] | -| 7 | Acessar sistema com credenciais | Autenticação | 3 | [Nome] | -| 8 | Definir senha após cadastro | Autenticação | 2 | [Nome] | -| 9 | Atualizar base de dados existente | Importação de Dados | 5 | [Nome] | -| 10 | Visualizar formulários não respondidos | Resposta de Questionários | 3 | [Nome] | -| 11 | Visualizar formulários criados | Criação de Formulários | 3 | [Nome] | -| 12 | Visualizar templates criados | Gerenciamento de Templates | 2 | [Nome] | -| 13 | Editar e deletar template | Gerenciamento de Templates | 3 | [Nome] | -| 14 | Escolher tipo de avaliador | Diferenciação de Avaliadores | 3 | [Nome] | - -**Total de Pontos: 56 pontos** - ---- - -## Histórias de Usuário Detalhadas com Pontuação - -### HU-01: Importar dados do SIGAA (8 pontos) - -``` -Como Administrador -Quero importar dados de turmas, matérias e participantes do SIGAA -A fim de alimentar a base de dados do sistema - -Critérios de Aceitação: -- Sistema importa com sucesso turmas, disciplinas e participantes do SIGAA -- Dados duplicados são ignorados -- Novos usuários recebem email de boas-vindas -- Erro de conexão é tratado graciosamente -- Máximo 1 importação simultânea -``` - -**Pontos:** 8 | **Responsável:** [Nome] - ---- - -### HU-02: Responder questionário sobre turma (5 pontos) - -``` -Como Participante de uma turma -Quero responder o questionário sobre a turma em que estou matriculado -A fim de submeter minha avaliação da turma - -Critérios de Aceitação: -- Visualiza questionário da turma -- Preenche questões obrigatórias -- Submete respostas finais -- Não pode responder 2 vezes o mesmo formulário -``` - -**Pontos:** 5 | **Responsável:** [Nome] - ---- - -### HU-03: Cadastrar participantes ao importar (3 pontos) - -``` -Como Administrador -Quero cadastrar participantes de turmas do SIGAA ao importar dados de usuários novos -A fim de que eles acessem o sistema CAMAAR - -Critérios de Aceitação: -- Novos usuários são criados automaticamente -- Senha temporária é gerada -- Usuário deve alterar senha no primeiro acesso -- Usuários já existentes são associados às novas turmas -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -### HU-04: Baixar resultados em CSV (5 pontos) - -``` -Como Administrador -Quero baixar um arquivo CSV contendo os resultados de um formulário -A fim de avaliar o desempenho das turmas - -Critérios de Aceitação: -- Arquivo CSV é gerado com estrutura correta -- Colunas: ID, Participante, Data, Respostas -- Caracteres especiais codificados (UTF-8) -- Anonimização de nomes (apenas matrícula) -``` - -**Pontos:** 5 | **Responsável:** [Nome] - ---- - -### HU-05: Criar template de formulário (5 pontos) - -``` -Como Administrador -Quero criar um template de formulário contendo as questões -A fim de gerar formulários de avaliações reutilizáveis - -Critérios de Aceitação: -- Template com nome único -- Mínimo 1 questão obrigatória -- Suporta múltiplos tipos: múltipla escolha, texto, V/F -- Apenas criador pode editar -- Edições não afetam formulários já criados -``` - -**Pontos:** 5 | **Responsável:** [Nome] - ---- - -### HU-06: Criar formulário baseado em template (8 pontos) - -``` -Como Administrador -Quero criar um formulário baseado em um template para turmas escolhidas -A fim de avaliar o desempenho das turmas no semestre atual - -Critérios de Aceitação: -- Seleciona template -- Escolhe múltiplas turmas (mínimo 1) -- Escolhe tipo de avaliador (docentes/discentes) -- Formulário herda questões do template -- Notificação enviada aos participantes -``` - -**Pontos:** 8 | **Responsável:** [Nome] - ---- - -### HU-07: Acessar sistema com credenciais (3 pontos) - -``` -Como Usuário do sistema -Quero acessar o sistema utilizando email e senha -A fim de responder formulários ou gerenciar o sistema - -Critérios de Aceitação: -- Login com email funciona -- Validação de credenciais -- Redirecionamento para painel -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -### HU-08: Definir senha após cadastro (2 pontos) - -``` -Como Usuário -Quero definir uma senha para o meu usuário a partir do email de cadastro -A fim de acessar o sistema - -Critérios de Aceitação: -- Senha deve cumprir critérios de segurança -- Senhas devem conferir -- Redirecionamento para login após sucesso -``` - -**Pontos:** 2 | **Responsável:** [Nome] - ---- - -### HU-09: Atualizar base de dados existente (5 pontos) - -``` -Como Administrador -Quero atualizar a base de dados com dados atuais do SIGAA -A fim de corrigir a base de dados do sistema - -Critérios de Aceitação: -- Sincroniza dados existentes -- Adiciona novos dados -- Atualiza mudanças de turmas -``` - -**Pontos:** 5 | **Responsável:** [Nome] - ---- - -### HU-10: Visualizar formulários não respondidos (3 pontos) - -``` -Como Participante de uma turma -Quero visualizar os formulários não respondidos das turmas em que estou matriculado -A fim de poder escolher qual irei responder - -Critérios de Aceitação: -- Lista apenas formulários não respondidos -- Mostra turma, disciplina, datas -- Formulários respondidos não aparecem -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -### HU-11: Visualizar formulários criados (3 pontos) - -``` -Como Administrador -Quero visualizar os formulários criados -A fim de poder gerar um relatório a partir das respostas - -Critérios de Aceitação: -- Lista todos os formulários -- Mostra nome, template, turmas, status, data -- Quantidade de respostas exibida -- Opção de visualizar detalhes -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -### HU-12: Visualizar templates criados (2 pontos) - -``` -Como Administrador -Quero visualizar os templates criados -A fim de poder editar e/ou deletar um template que eu criei - -Critérios de Aceitação: -- Lista todos os templates -- Mostra nome, quantidade de questões, data -- Acesso aos detalhes -- Opções de editar/deletar -``` - -**Pontos:** 2 | **Responsável:** [Nome] - ---- - -### HU-13: Editar e deletar template (3 pontos) - -``` -Como Administrador -Quero editar e/ou deletar um template que eu criei sem afetar formulários já criados -A fim de organizar os templates existentes - -Critérios de Aceitação: -- Confirmação de exclusão -- Apenas criador pode editar e excluir -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -### HU-14: Escolher tipo de avaliador (3 pontos) - -``` -Como Administrador -Quero escolher criar um formulário para os docentes ou os discentes de uma turma -A fim de avaliar o desempenho de uma matéria - -Critérios de Aceitação: -- Opção obrigatória: Docentes ou Discentes -- Professores veem apenas formulários para Docentes -- Alunos veem apenas formulários para Discentes -- Múltiplos formulários podem coexistir para mesma turma -``` - -**Pontos:** 3 | **Responsável:** [Nome] - ---- - -## Política de Branching - -### Estratégia Git Flow - -``` -- main: Produção (releases e hotfixes) -- develop: Desenvolvimento principal -- feature/HU-XX-descricao: Novas funcionalidades -- bugfix/HU-XX-descricao: Correções de bugs -- hotfix/issue-descricao: Correções urgentes em produção -``` - -### Fluxo de Trabalho - -1. **Iniciar Nova Funcionalidade:** - - Criação de branch para a funcionalidade - - Garantir que a nova branch está atualizada com as alterações da main - -2. **Trabalhar na Funcionalidade:** - - Commits descritivos em português - - Formato: `HU-01: Descrição da alteração` - - Exemplo: `HU-01: Implementar validação de duplicatas` - -3. **Pull Request para Develop:** - - Descrever as alterações - - Referenciar a HU - - Mínimo 1 aprovação antes de merge - - Rodar testes antes de merge - -### Convenção de Commits - -``` -(): - - -``` - -**Tipos:** -- `feat`: Nova funcionalidade (HU) -- `fix`: Correção de bug -- `docs`: Documentação -- `style`: Formatação -- `refactor`: Refatoração -- `test`: Testes -- `chore`: Tarefas de build/dependências - -**Exemplo:** -``` -feat(autenticacao): Implementar login com email e matrícula -``` - ---- - -## Métricas e Velocidade - -### Cálculo de Velocity - -**Sprint Atual:** -- Total de Pontos: **56 pontos** -- Histórias por Sprint: [A definir conforme organização] - -**Fórmula:** -``` -Velocity = Pontos Concluídos / Número de Sprints - -Exemplo (após 3 sprints): -Sprint 1: 21 pontos -Sprint 2: 18 pontos -Sprint 3: 20 pontos -Velocity Média = (21+18+20)/3 = 19.67 pontos/sprint -``` - -### Capacidade de Planning - -``` -Baseado na velocity média, para próxima sprint: -- Pontos a planejar ≈ Velocity média -- Buffer (20% do total) para imprevistos -- Ajustar conforme necessário -``` - ---- - -### Estrutura do Banco de Dados: - -``` - -// CAMAAR - Database Schema (DbDiagram.io format) -// Plataforma de Avaliação de Cursos -// Versão: 1.0 (Pós-Remoções) -// Data: 07/12/2025 - -// ============================================ -// ENUMS -// ============================================ - -Enum user_role { - aluno - professor - admin -} - -Enum form_evaluator_type { - aluno - professor -} - -Enum form_status { - rascunho - ativo - finalizado -} - -Enum response_status { - rascunho - submetido -} - -// ============================================ -// TABELAS PRINCIPAIS -// ============================================ - -Table users { - id integer [pk, increment] - name varchar(255) [not null, note: 'Nome completo do usuário'] - email varchar(255) [not null, unique, note: 'Email único do usuário'] - matricula varchar(50) [not null, unique, note: 'Matrícula do SIGAA'] - password_digest varchar [not null, note: 'Senha criptografada (bcrypt)'] - role user_role [not null, note: 'Tipo de usuário: aluno, professor ou admin'] - department_id integer [not null, ref: > departments.id, note: 'Departamento do usuário'] - active boolean [not null, default: true, note: 'Usuário ativo no sistema'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (email) [unique, name: 'idx_users_email'] - (matricula) [unique, name: 'idx_users_matricula'] - (department_id) [name: 'idx_users_department_id'] - (role) [name: 'idx_users_role'] - } - - Note: 'Usuários do sistema (alunos, professores, administradores)' -} - - -Table departments { - id integer [pk, increment] - name varchar(255) [not null, note: 'Nome do departamento'] - code varchar(20) [not null, unique, note: 'Sigla/código do departamento (ex: CIC, ENE)'] - sigaa_id varchar(50) [note: 'ID do departamento no SIGAA (opcional)'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (code) [unique, name: 'idx_departments_code'] - (sigaa_id) [name: 'idx_departments_sigaa_id'] - } - - Note: 'Departamentos acadêmicos' -} - - -Table courses { - id integer [pk, increment] - name varchar(255) [not null, note: 'Nome da disciplina'] - code varchar(20) [not null, note: 'Código da disciplina (ex: FGA0208)'] - department_id integer [not null, ref: > departments.id, note: 'Departamento responsável'] - sigaa_id varchar(50) [note: 'ID da disciplina no SIGAA (opcional)'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (code, department_id) [unique, name: 'idx_courses_code_dept'] - (department_id) [name: 'idx_courses_department_id'] - (sigaa_id) [name: 'idx_courses_sigaa_id'] - } - - Note: 'Disciplinas/Cursos oferecidos' -} - - -Table classes { - id integer [pk, increment] - course_id integer [not null, ref: > courses.id, note: 'Disciplina da turma'] - professor_id integer [not null, ref: > users.id, note: 'Professor responsável (role=professor)'] - semester varchar(10) [not null, note: 'Semestre/período (ex: 2025.2)'] - turma_code varchar(10) [not null, note: 'Código da turma (ex: A, B, 01)'] - sigaa_id varchar(50) [note: 'ID da turma no SIGAA (opcional)'] - active boolean [not null, default: true, note: 'Turma ativa'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (course_id, semester, turma_code) [unique, name: 'idx_classes_unique'] - (course_id) [name: 'idx_classes_course_id'] - (professor_id) [name: 'idx_classes_professor_id'] - (semester) [name: 'idx_classes_semester'] - (sigaa_id) [name: 'idx_classes_sigaa_id'] - (active) [name: 'idx_classes_active'] - } - - Note: 'Turmas específicas de um curso em um semestre' -} - - -Table templates { - id integer [pk, increment] - title varchar(255) [not null, note: 'Título do template'] - description text [note: 'Descrição/instruções do template'] - questions jsonb [not null, note: 'Array JSON com questões estruturadas'] - creator_id integer [not null, ref: > users.id, note: 'Admin que criou o template (role=admin)'] - department_id integer [not null, ref: > departments.id, note: 'Departamento proprietário'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (creator_id) [name: 'idx_templates_creator_id'] - (department_id) [name: 'idx_templates_department_id'] - (title, department_id) [unique, name: 'idx_templates_title_dept'] - } - - Note: '''Template reutilizável de formulário de avaliação - - Estrutura esperada do campo questions: - [ - { - "id": "q1", - "text": "Pergunta?", - "type": "multiple_choice | text | boolean", - "required": true, - "options": ["Op1", "Op2"] // se multiple_choice - } - ]''' -} - - -Table forms { - id integer [pk, increment] - title varchar(255) [not null, note: 'Título do formulário'] - description text [note: 'Descrição/instruções do formulário'] - questions jsonb [not null, note: 'Array JSON com questões estruturadas (cópia do template)'] - evaluator_type form_evaluator_type [not null, note: 'Quem deve responder: aluno ou professor'] - creator_id integer [not null, ref: > users.id, note: 'Admin que criou o formulário (role=admin)'] - template_id integer [ref: > templates.id, note: 'Template usado como base (rastreamento)'] - status form_status [not null, default: 'rascunho', note: 'Estado do formulário'] - start_date date [note: 'Data de início da avaliação'] - end_date date [note: 'Data limite da avaliação'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (status) [name: 'idx_forms_status'] - (start_date, end_date) [name: 'idx_forms_date_range'] - (creator_id) [name: 'idx_forms_creator_id'] - (template_id) [name: 'idx_forms_template_id'] - } - - Note: '''Formulários de avaliação - - Estrutura esperada do campo questions: - [ - { - "id": "q1", - "text": "Pergunta?", - "type": "multiple_choice | text | boolean", - "required": true, - "options": ["Op1", "Op2"] // se multiple_choice - } - ]''' -} - - -Table responses { - id integer [pk, increment] - form_id integer [not null, ref: > forms.id, note: 'Formulário respondido'] - user_id integer [not null, ref: > users.id, note: 'Usuário que respondeu'] - class_id integer [ref: > classes.id, note: 'Turma avaliada (opcional)'] - answers jsonb [not null, note: 'Array JSON com respostas'] - status response_status [not null, default: 'rascunho', note: 'Estado da resposta'] - submitted_at timestamp [note: 'Data/hora de submissão'] - created_at timestamp [not null, default: 'now()'] - updated_at timestamp [not null, default: 'now()'] - - indexes { - (form_id, user_id) [unique, name: 'idx_responses_unique'] - (user_id) [name: 'idx_responses_user_id'] - (status) [name: 'idx_responses_status'] - (submitted_at) [name: 'idx_responses_submitted_at'] - } - - Note: '''Respostas dos participantes a formulários - - Estrutura esperada do campo answers: - [ - { - "question_id": "q1", - "value": "Resposta" - } - ] - - Constraint: um usuário responde apenas uma vez por formulário (unique em form_id, user_id)''' -} - - -// ============================================ -// TABELAS DE JUNÇÃO (Many-to-Many) -// ============================================ - - -Table classes_users { - class_id integer [not null, ref: > classes.id, note: 'Turma'] - user_id integer [not null, ref: > users.id, note: 'Aluno (role=aluno) matriculado'] - created_at timestamp [not null, default: 'now()'] - - indexes { - (class_id, user_id) [pk, unique, name: 'idx_classes_users_pk'] - (user_id) [name: 'idx_classes_users_user_id'] - } - - Note: 'Relacionamento muitos-para-muitos: alunos matriculados em turmas' -} - - -Table forms_classes { - form_id integer [not null, ref: > forms.id, note: 'Formulário de avaliação'] - class_id integer [not null, ref: > classes.id, note: 'Turma que deve responder'] - created_at timestamp [not null, default: 'now()'] - - indexes { - (form_id, class_id) [pk, unique, name: 'idx_forms_classes_pk'] - (class_id) [name: 'idx_forms_classes_class_id'] - } - - Note: 'Relacionamento muitos-para-muitos: turmas associadas a formulários' -} - -``` ---- - -### Arquivo classes.json (exemplo) - -``` - -[ - { - "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" - } - } -] - -``` - -### Arquivo class_members.json (exemplo): - -``` - -[ - { - "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" - } - } -] - -``` +# Wiki CAMAAR - Plataforma de Avaliação de Cursos + +--- + +## Informações do Projeto + +| Item | Descrição | +|------|-----------| +| **Nome do Projeto** | CAMAAR - Plataforma de Avaliação de Cursos | +| **Disciplina** | Engenharia de Software | +| **Período** | 2025.2 | +| **Integrantes do Grupo** | Bernardo Gomes Rodrigues - 231034190
Isaac Silva - 231025216
Filipe Abadia Marcelino - 190087161
Maria Carolina Burgum Abreu Jorge - 231013547 | + +--- + +## Papéis Scrum + +| Papel | Responsável | Matrícula | +|-------|-------------|-----------| +| **Scrum Master** | Bernardo Gomes Rodrigues | 231034190 | +| **Product Owner** | Maria Carolina Burgum Abreu Jorge | 231013547 | + +--- + +## Escopo do Projeto + +### Descrição Geral + +O CAMAAR é uma **plataforma web para avaliação de cursos e disciplinas** integrada com o SIGAA (Sistema Integrado de Gestão de Atividades Acadêmicas) por meio de arquivos JSON. O sistema permite que administradores criem formulários de avaliação que são respondidos por alunos e professores, gerando relatórios sobre o desempenho das disciplinas e infraestrutura. + +### Objetivos Principais + +- Automatizar o processo de coleta de feedback sobre disciplinas +- Integrar dados do SIGAA para alimentar a plataforma +- Permitir análise de dados através de exportação em CSV +- Facilitar a gestão de templates reutilizáveis de formulários + +### Stakeholders + +- **Coordenadores de Curso** (Administradores): Criam e gerenciam formulários +- **Professores**: Respondem questionários sobre infraestrutura e ementa +- **Alunos**: Avaliam disciplinas e professores +- **Sistema SIGAA**: Fornecedor de dados de turmas, disciplinas e participantes + +--- + +## Funcionalidades e Regras de Negócio + +### 1. Autenticação de Usuários (2 pontos) + +**Funcionalidades:** +- Login com email ou matrícula + senha +- Definir senha após convite de cadastro + +**Regras de Negócio:** +- Senhas devem ter no mínimo 4 caracteres +- Senhas devem conter pelo menos: 1 letra maiúscula, 1 letra minúscula, 1 número e 1 caractere especial +- Usuário deve alterar senha temporária no primeiro acesso + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 2. Importação de Dados do SIGAA (8 pontos) + +**Funcionalidades:** +- Importar turmas, disciplinas e participantes do SIGAA +- Atualizar dados existentes com informações atualizadas +- Marcar turmas como inativas quando descontinuadas + +**Regras de Negócio:** +- Importação deve ser realizada apenas por administradores +- Registros duplicados são ignorados (validação por ID único do SIGAA) +- Ao importar novos usuários, criar contas com senha temporária +- Dados importados não devem sobrescrever informações customizadas no sistema +- Participantes removidos do SIGAA devem ter acesso desativado +- Atualização de mudanças de turmas de alunos/professores + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 3. Gerenciamento de Templates de Formulários (5 pontos) + +**Funcionalidades:** +- Criar templates com múltiplos tipos de questões +- Visualizar templates criados +- Editar templates +- Deletar templates + +**Regras de Negócio:** +- Template deve ter no mínimo 1 questão +- Nome do template deve ser único +- Apenas o criador do template pode editá-lo +- Tipos de questões suportados: Múltipla escolha, Texto aberto, Verdadeiro/Falso +- Edições em template afetam apenas novos formulários criados, não retroativamente + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 4. Criação de Formulários (8 pontos) + +**Funcionalidades:** +- Criar formulário baseado em template +- Selecionar turmas para aplicar o formulário +- Escolher tipo de avaliador (docentes ou discentes) +- Visualizar formulários criados + +**Regras de Negócio:** +- Formulário herda todas as questões do template no momento da criação +- Deve selecionar no mínimo 1 turma +- Deve escolher tipo de avaliador (obrigatório) +- Mesmo formulário não pode ser respondido 2 vezes pelo mesmo participante + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 5. Resposta de Questionários (5 pontos) + +**Funcionalidades:** +- Visualizar formulários não respondidos +- Responder questionário +- Submeter respostas +- Filtrar respostas de turmas + +**Regras de Negócio:** +- Participante visualiza apenas formulários de turmas onde está matriculado +- Todos os campos obrigatórios devem ser preenchidos antes de enviar +- Participante pode editar respostas enquanto formulário não foi enviado +- Uma vez enviado, não pode ser alterado +- Resposta não pode ser submetida duas vezes pelo mesmo participante +- Sistema valida tipo de resposta conforme tipo de questão + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 6. Exportação de Resultados (5 pontos) + +**Funcionalidades:** +- Exportar respostas em formato CSV +- Filtrar por formulário +- Download do arquivo + +**Regras de Negócio:** +- Apenas administrador pode exportar resultados +- Arquivo CSV deve conter: ID da resposta, Participante, Data de resposta, Respostas individuais +- Formato CSV deve ser compatível com planilhas +- Caracteres especiais devem estar codificados corretamente (UTF-8) +- Arquivo só pode ser gerado se houver respostas +- Nomes de participantes anonimizados por padrão (apenas matrícula mostrada) +- Arquivo gerado com timestamp: `formulario_[id]_[data_hora].csv` + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 7. Diferenciação de Avaliadores (3 pontos) + +**Funcionalidades:** +- Escolher tipo de avaliador (docentes ou discentes) +- Exibir formulário apenas para o tipo correto + +**Regras de Negócio:** +- Ao criar formulário, obrigatório selecionar tipo: "Docentes" ou "Discentes" +- Aluno só vê formulários criados para "Discentes" +- Professor só vê formulários criados para "Docentes" +- Sistema valida tipo de usuário ao tentar acessar formulário +- Formulário para docente é enviado apenas para professores das turmas +- Formulário para discente é enviado apenas para alunos das turmas +- Mesmo formulário com dois tipos diferentes pode coexistir para mesma turma + +**Responsável:** [Nome do desenvolvedor] + +--- + +### 8. Gestão de Usuários e Participantes (3 pontos) + +**Funcionalidades:** +- Cadastro automático de participantes na importação +- Desativação de participantes inativos + +**Regras de Negócio:** +- Usuários são criados automaticamente durante importação do SIGAA +- Usuário inativo não pode fazer login +- Quando participante é removido do SIGAA, status muda para inativo +- Email de boas-vindas é enviado automaticamente +- Senha temporária é gerada e enviada no email de boas vindas + +**Responsável:** [Nome do desenvolvedor] + +--- + +## Atribuição de Histórias de Usuário + +| # | História de Usuário | Funcionalidade | Pontos | Responsável | +|---|---------------------|-----------------|--------|-------------| +| 1 | Importar dados do SIGAA | Importação de Dados | 8 | [Nome] | +| 2 | Responder questionário sobre turma | Resposta de Questionários | 5 | [Nome] | +| 3 | Cadastrar participantes ao importar | Gestão de Usuários | 3 | [Nome] | +| 4 | Baixar resultados em CSV | Exportação de Resultados | 5 | [Nome] | +| 5 | Criar template de formulário | Gerenciamento de Templates | 5 | [Nome] | +| 6 | Criar formulário baseado em template | Criação de Formulários | 8 | [Nome] | +| 7 | Acessar sistema com credenciais | Autenticação | 3 | [Nome] | +| 8 | Definir senha após cadastro | Autenticação | 2 | [Nome] | +| 9 | Atualizar base de dados existente | Importação de Dados | 5 | [Nome] | +| 10 | Visualizar formulários não respondidos | Resposta de Questionários | 3 | [Nome] | +| 11 | Visualizar formulários criados | Criação de Formulários | 3 | [Nome] | +| 12 | Visualizar templates criados | Gerenciamento de Templates | 2 | [Nome] | +| 13 | Editar e deletar template | Gerenciamento de Templates | 3 | [Nome] | +| 14 | Escolher tipo de avaliador | Diferenciação de Avaliadores | 3 | [Nome] | + +**Total de Pontos: 56 pontos** + +--- + +## Histórias de Usuário Detalhadas com Pontuação + +### HU-01: Importar dados do SIGAA (8 pontos) + +``` +Como Administrador +Quero importar dados de turmas, matérias e participantes do SIGAA +A fim de alimentar a base de dados do sistema + +Critérios de Aceitação: +- Sistema importa com sucesso turmas, disciplinas e participantes do SIGAA +- Dados duplicados são ignorados +- Novos usuários recebem email de boas-vindas +- Erro de conexão é tratado graciosamente +- Máximo 1 importação simultânea +``` + +**Pontos:** 8 | **Responsável:** [Nome] + +--- + +### HU-02: Responder questionário sobre turma (5 pontos) + +``` +Como Participante de uma turma +Quero responder o questionário sobre a turma em que estou matriculado +A fim de submeter minha avaliação da turma + +Critérios de Aceitação: +- Visualiza questionário da turma +- Preenche questões obrigatórias +- Submete respostas finais +- Não pode responder 2 vezes o mesmo formulário +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-03: Cadastrar participantes ao importar (3 pontos) + +``` +Como Administrador +Quero cadastrar participantes de turmas do SIGAA ao importar dados de usuários novos +A fim de que eles acessem o sistema CAMAAR + +Critérios de Aceitação: +- Novos usuários são criados automaticamente +- Senha temporária é gerada +- Usuário deve alterar senha no primeiro acesso +- Usuários já existentes são associados às novas turmas +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-04: Baixar resultados em CSV (5 pontos) + +``` +Como Administrador +Quero baixar um arquivo CSV contendo os resultados de um formulário +A fim de avaliar o desempenho das turmas + +Critérios de Aceitação: +- Arquivo CSV é gerado com estrutura correta +- Colunas: ID, Participante, Data, Respostas +- Caracteres especiais codificados (UTF-8) +- Anonimização de nomes (apenas matrícula) +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-05: Criar template de formulário (5 pontos) + +``` +Como Administrador +Quero criar um template de formulário contendo as questões +A fim de gerar formulários de avaliações reutilizáveis + +Critérios de Aceitação: +- Template com nome único +- Mínimo 1 questão obrigatória +- Suporta múltiplos tipos: múltipla escolha, texto, V/F +- Apenas criador pode editar +- Edições não afetam formulários já criados +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-06: Criar formulário baseado em template (8 pontos) + +``` +Como Administrador +Quero criar um formulário baseado em um template para turmas escolhidas +A fim de avaliar o desempenho das turmas no semestre atual + +Critérios de Aceitação: +- Seleciona template +- Escolhe múltiplas turmas (mínimo 1) +- Escolhe tipo de avaliador (docentes/discentes) +- Formulário herda questões do template +- Notificação enviada aos participantes +``` + +**Pontos:** 8 | **Responsável:** [Nome] + +--- + +### HU-07: Acessar sistema com credenciais (3 pontos) + +``` +Como Usuário do sistema +Quero acessar o sistema utilizando email e senha +A fim de responder formulários ou gerenciar o sistema + +Critérios de Aceitação: +- Login com email funciona +- Validação de credenciais +- Redirecionamento para painel +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-08: Definir senha após cadastro (2 pontos) + +``` +Como Usuário +Quero definir uma senha para o meu usuário a partir do email de cadastro +A fim de acessar o sistema + +Critérios de Aceitação: +- Senha deve cumprir critérios de segurança +- Senhas devem conferir +- Redirecionamento para login após sucesso +``` + +**Pontos:** 2 | **Responsável:** [Nome] + +--- + +### HU-09: Atualizar base de dados existente (5 pontos) + +``` +Como Administrador +Quero atualizar a base de dados com dados atuais do SIGAA +A fim de corrigir a base de dados do sistema + +Critérios de Aceitação: +- Sincroniza dados existentes +- Adiciona novos dados +- Atualiza mudanças de turmas +``` + +**Pontos:** 5 | **Responsável:** [Nome] + +--- + +### HU-10: Visualizar formulários não respondidos (3 pontos) + +``` +Como Participante de uma turma +Quero visualizar os formulários não respondidos das turmas em que estou matriculado +A fim de poder escolher qual irei responder + +Critérios de Aceitação: +- Lista apenas formulários não respondidos +- Mostra turma, disciplina, datas +- Formulários respondidos não aparecem +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-11: Visualizar formulários criados (3 pontos) + +``` +Como Administrador +Quero visualizar os formulários criados +A fim de poder gerar um relatório a partir das respostas + +Critérios de Aceitação: +- Lista todos os formulários +- Mostra nome, template, turmas, status, data +- Quantidade de respostas exibida +- Opção de visualizar detalhes +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-12: Visualizar templates criados (2 pontos) + +``` +Como Administrador +Quero visualizar os templates criados +A fim de poder editar e/ou deletar um template que eu criei + +Critérios de Aceitação: +- Lista todos os templates +- Mostra nome, quantidade de questões, data +- Acesso aos detalhes +- Opções de editar/deletar +``` + +**Pontos:** 2 | **Responsável:** [Nome] + +--- + +### HU-13: Editar e deletar template (3 pontos) + +``` +Como Administrador +Quero editar e/ou deletar um template que eu criei sem afetar formulários já criados +A fim de organizar os templates existentes + +Critérios de Aceitação: +- Confirmação de exclusão +- Apenas criador pode editar e excluir +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +### HU-14: Escolher tipo de avaliador (3 pontos) + +``` +Como Administrador +Quero escolher criar um formulário para os docentes ou os discentes de uma turma +A fim de avaliar o desempenho de uma matéria + +Critérios de Aceitação: +- Opção obrigatória: Docentes ou Discentes +- Professores veem apenas formulários para Docentes +- Alunos veem apenas formulários para Discentes +- Múltiplos formulários podem coexistir para mesma turma +``` + +**Pontos:** 3 | **Responsável:** [Nome] + +--- + +## Política de Branching + +### Estratégia Git Flow + +``` +- main: Produção (releases e hotfixes) +- develop: Desenvolvimento principal +- feature/HU-XX-descricao: Novas funcionalidades +- bugfix/HU-XX-descricao: Correções de bugs +- hotfix/issue-descricao: Correções urgentes em produção +``` + +### Fluxo de Trabalho + +1. **Iniciar Nova Funcionalidade:** + - Criação de branch para a funcionalidade + - Garantir que a nova branch está atualizada com as alterações da main + +2. **Trabalhar na Funcionalidade:** + - Commits descritivos em português + - Formato: `HU-01: Descrição da alteração` + - Exemplo: `HU-01: Implementar validação de duplicatas` + +3. **Pull Request para Develop:** + - Descrever as alterações + - Referenciar a HU + - Mínimo 1 aprovação antes de merge + - Rodar testes antes de merge + +### Convenção de Commits + +``` +(): + + +``` + +**Tipos:** +- `feat`: Nova funcionalidade (HU) +- `fix`: Correção de bug +- `docs`: Documentação +- `style`: Formatação +- `refactor`: Refatoração +- `test`: Testes +- `chore`: Tarefas de build/dependências + +**Exemplo:** +``` +feat(autenticacao): Implementar login com email e matrícula +``` + +--- + +## Métricas e Velocidade + +### Cálculo de Velocity + +**Sprint Atual:** +- Total de Pontos: **56 pontos** +- Histórias por Sprint: [A definir conforme organização] + +**Fórmula:** +``` +Velocity = Pontos Concluídos / Número de Sprints + +Exemplo (após 3 sprints): +Sprint 1: 21 pontos +Sprint 2: 18 pontos +Sprint 3: 20 pontos +Velocity Média = (21+18+20)/3 = 19.67 pontos/sprint +``` + +### Capacidade de Planning + +``` +Baseado na velocity média, para próxima sprint: +- Pontos a planejar ≈ Velocity média +- Buffer (20% do total) para imprevistos +- Ajustar conforme necessário +``` + +--- + +### Estrutura do Banco de Dados: + +``` + +// CAMAAR - Database Schema (DbDiagram.io format) +// Plataforma de Avaliação de Cursos +// Versão: 1.0 (Pós-Remoções) +// Data: 07/12/2025 + +// ============================================ +// ENUMS +// ============================================ + +Enum user_role { + aluno + professor + admin +} + +Enum form_evaluator_type { + aluno + professor +} + +Enum form_status { + rascunho + ativo + finalizado +} + +Enum response_status { + rascunho + submetido +} + +// ============================================ +// TABELAS PRINCIPAIS +// ============================================ + +Table users { + id integer [pk, increment] + name varchar(255) [not null, note: 'Nome completo do usuário'] + email varchar(255) [not null, unique, note: 'Email único do usuário'] + matricula varchar(50) [not null, unique, note: 'Matrícula do SIGAA'] + password_digest varchar [not null, note: 'Senha criptografada (bcrypt)'] + role user_role [not null, note: 'Tipo de usuário: aluno, professor ou admin'] + department_id integer [not null, ref: > departments.id, note: 'Departamento do usuário'] + active boolean [not null, default: true, note: 'Usuário ativo no sistema'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (email) [unique, name: 'idx_users_email'] + (matricula) [unique, name: 'idx_users_matricula'] + (department_id) [name: 'idx_users_department_id'] + (role) [name: 'idx_users_role'] + } + + Note: 'Usuários do sistema (alunos, professores, administradores)' +} + + +Table departments { + id integer [pk, increment] + name varchar(255) [not null, note: 'Nome do departamento'] + code varchar(20) [not null, unique, note: 'Sigla/código do departamento (ex: CIC, ENE)'] + sigaa_id varchar(50) [note: 'ID do departamento no SIGAA (opcional)'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (code) [unique, name: 'idx_departments_code'] + (sigaa_id) [name: 'idx_departments_sigaa_id'] + } + + Note: 'Departamentos acadêmicos' +} + + +Table courses { + id integer [pk, increment] + name varchar(255) [not null, note: 'Nome da disciplina'] + code varchar(20) [not null, note: 'Código da disciplina (ex: FGA0208)'] + department_id integer [not null, ref: > departments.id, note: 'Departamento responsável'] + sigaa_id varchar(50) [note: 'ID da disciplina no SIGAA (opcional)'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (code, department_id) [unique, name: 'idx_courses_code_dept'] + (department_id) [name: 'idx_courses_department_id'] + (sigaa_id) [name: 'idx_courses_sigaa_id'] + } + + Note: 'Disciplinas/Cursos oferecidos' +} + + +Table classes { + id integer [pk, increment] + course_id integer [not null, ref: > courses.id, note: 'Disciplina da turma'] + professor_id integer [not null, ref: > users.id, note: 'Professor responsável (role=professor)'] + semester varchar(10) [not null, note: 'Semestre/período (ex: 2025.2)'] + turma_code varchar(10) [not null, note: 'Código da turma (ex: A, B, 01)'] + sigaa_id varchar(50) [note: 'ID da turma no SIGAA (opcional)'] + active boolean [not null, default: true, note: 'Turma ativa'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (course_id, semester, turma_code) [unique, name: 'idx_classes_unique'] + (course_id) [name: 'idx_classes_course_id'] + (professor_id) [name: 'idx_classes_professor_id'] + (semester) [name: 'idx_classes_semester'] + (sigaa_id) [name: 'idx_classes_sigaa_id'] + (active) [name: 'idx_classes_active'] + } + + Note: 'Turmas específicas de um curso em um semestre' +} + + +Table templates { + id integer [pk, increment] + title varchar(255) [not null, note: 'Título do template'] + description text [note: 'Descrição/instruções do template'] + questions jsonb [not null, note: 'Array JSON com questões estruturadas'] + creator_id integer [not null, ref: > users.id, note: 'Admin que criou o template (role=admin)'] + department_id integer [not null, ref: > departments.id, note: 'Departamento proprietário'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (creator_id) [name: 'idx_templates_creator_id'] + (department_id) [name: 'idx_templates_department_id'] + (title, department_id) [unique, name: 'idx_templates_title_dept'] + } + + Note: '''Template reutilizável de formulário de avaliação + + Estrutura esperada do campo questions: + [ + { + "id": "q1", + "text": "Pergunta?", + "type": "multiple_choice | text | boolean", + "required": true, + "options": ["Op1", "Op2"] // se multiple_choice + } + ]''' +} + + +Table forms { + id integer [pk, increment] + title varchar(255) [not null, note: 'Título do formulário'] + description text [note: 'Descrição/instruções do formulário'] + questions jsonb [not null, note: 'Array JSON com questões estruturadas (cópia do template)'] + evaluator_type form_evaluator_type [not null, note: 'Quem deve responder: aluno ou professor'] + creator_id integer [not null, ref: > users.id, note: 'Admin que criou o formulário (role=admin)'] + template_id integer [ref: > templates.id, note: 'Template usado como base (rastreamento)'] + status form_status [not null, default: 'rascunho', note: 'Estado do formulário'] + start_date date [note: 'Data de início da avaliação'] + end_date date [note: 'Data limite da avaliação'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (status) [name: 'idx_forms_status'] + (start_date, end_date) [name: 'idx_forms_date_range'] + (creator_id) [name: 'idx_forms_creator_id'] + (template_id) [name: 'idx_forms_template_id'] + } + + Note: '''Formulários de avaliação + + Estrutura esperada do campo questions: + [ + { + "id": "q1", + "text": "Pergunta?", + "type": "multiple_choice | text | boolean", + "required": true, + "options": ["Op1", "Op2"] // se multiple_choice + } + ]''' +} + + +Table responses { + id integer [pk, increment] + form_id integer [not null, ref: > forms.id, note: 'Formulário respondido'] + user_id integer [not null, ref: > users.id, note: 'Usuário que respondeu'] + class_id integer [ref: > classes.id, note: 'Turma avaliada (opcional)'] + answers jsonb [not null, note: 'Array JSON com respostas'] + status response_status [not null, default: 'rascunho', note: 'Estado da resposta'] + submitted_at timestamp [note: 'Data/hora de submissão'] + created_at timestamp [not null, default: 'now()'] + updated_at timestamp [not null, default: 'now()'] + + indexes { + (form_id, user_id) [unique, name: 'idx_responses_unique'] + (user_id) [name: 'idx_responses_user_id'] + (status) [name: 'idx_responses_status'] + (submitted_at) [name: 'idx_responses_submitted_at'] + } + + Note: '''Respostas dos participantes a formulários + + Estrutura esperada do campo answers: + [ + { + "question_id": "q1", + "value": "Resposta" + } + ] + + Constraint: um usuário responde apenas uma vez por formulário (unique em form_id, user_id)''' +} + + +// ============================================ +// TABELAS DE JUNÇÃO (Many-to-Many) +// ============================================ + + +Table classes_users { + class_id integer [not null, ref: > classes.id, note: 'Turma'] + user_id integer [not null, ref: > users.id, note: 'Aluno (role=aluno) matriculado'] + created_at timestamp [not null, default: 'now()'] + + indexes { + (class_id, user_id) [pk, unique, name: 'idx_classes_users_pk'] + (user_id) [name: 'idx_classes_users_user_id'] + } + + Note: 'Relacionamento muitos-para-muitos: alunos matriculados em turmas' +} + + +Table forms_classes { + form_id integer [not null, ref: > forms.id, note: 'Formulário de avaliação'] + class_id integer [not null, ref: > classes.id, note: 'Turma que deve responder'] + created_at timestamp [not null, default: 'now()'] + + indexes { + (form_id, class_id) [pk, unique, name: 'idx_forms_classes_pk'] + (class_id) [name: 'idx_forms_classes_class_id'] + } + + Note: 'Relacionamento muitos-para-muitos: turmas associadas a formulários' +} + +``` +--- + +### Arquivo classes.json (exemplo) + +``` + +[ + { + "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" + } + } +] + +``` + +### Arquivo class_members.json (exemplo): + +``` + +[ + { + "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" + } + } +] + +``` diff --git a/Rakefile b/Rakefile index 9a5ea7383a..cc4cf93277 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ -# Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. - -require_relative "config/application" - -Rails.application.load_tasks +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/app/assets/application.js b/app/assets/application.js index 8efd6faf08..e4eb668013 100644 --- a/app/assets/application.js +++ b/app/assets/application.js @@ -1,5 +1,5 @@ -// app/assets/application.js - -//= require rails-ujs -//= require turbo -//= require_tree . +// app/assets/application.js + +//= require rails-ujs +//= require turbo +//= require_tree . diff --git a/app/assets/controllers/form_fields_controller.js b/app/assets/controllers/form_fields_controller.js index e93b7882cb..b1e328d11f 100644 --- a/app/assets/controllers/form_fields_controller.js +++ b/app/assets/controllers/form_fields_controller.js @@ -1,23 +1,23 @@ -// app/javascript/controllers/form_fields_controller.js - -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - static targets = ["container"] - - add_field(event) { - event.preventDefault() - - const link = event.currentTarget - const association = link.dataset.association - const content = link.dataset.fields - - const new_id = new Date().getTime() - const regexp = new RegExp("new_" + association, "g") - const new_fields = content.replace(regexp, new_id) - - const temp = document.createElement('div') - temp.innerHTML = new_fields - this.containerTarget.appendChild(temp.firstElementChild) - } -} +// app/javascript/controllers/form_fields_controller.js + +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["container"] + + add_field(event) { + event.preventDefault() + + const link = event.currentTarget + const association = link.dataset.association + const content = link.dataset.fields + + const new_id = new Date().getTime() + const regexp = new RegExp("new_" + association, "g") + const new_fields = content.replace(regexp, new_id) + + const temp = document.createElement('div') + temp.innerHTML = new_fields + this.containerTarget.appendChild(temp.firstElementChild) + } +} diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index fe93333c0f..903799e18e 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,10 +1,10 @@ -/* - * This is a manifest file that'll be compiled into application.css. - * - * With Propshaft, assets are served efficiently without preprocessing steps. You can still include - * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard - * cascading order, meaning styles declared later in the document or manifest will override earlier ones, - * depending on specificity. - * - * Consider organizing styles into separate files for maintainability. - */ +/* + * This is a manifest file that'll be compiled into application.css. + * + * With Propshaft, assets are served efficiently without preprocessing steps. You can still include + * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard + * cascading order, meaning styles declared later in the document or manifest will override earlier ones, + * depending on specificity. + * + * Consider organizing styles into separate files for maintainability. + */ diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index b2fd25d3de..926903a0ce 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,20 +1,20 @@ -# app/controllers/admin/dashboard_controller.rb - -module Admin - class DashboardController < ApplicationController - before_action :authenticate_user! - before_action :check_admin - - def index - @users = User.all - @total_users = User.count - @admin_count = User.where(role: :admin).count - end - - private - - def check_admin - redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? - end - end -end +# app/controllers/admin/dashboard_controller.rb + +module Admin + class DashboardController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + + def index + @users = User.all + @total_users = User.count + @admin_count = User.where(role: :admin).count + end + + private + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 12147dafa1..8d70bc4027 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,46 +1,46 @@ -# app/controllers/admin/users_controller.rb - -module Admin - class UsersController < ApplicationController - before_action :authenticate_user! - before_action :check_admin - before_action :set_user, only: [:show, :edit, :update, :destroy] - - def index - @users = User.all - end - - def show - end - - def edit - end - - def update - if @user.update(user_params) - redirect_to admin_users_path, notice: 'Usuário atualizado com sucesso' - else - render :edit - end - end - - def destroy - @user.destroy - redirect_to admin_users_path, notice: 'Usuário deletado com sucesso' - end - - private - - def set_user - @user = User.find(params[:id]) - end - - def user_params - params.require(:user).permit(:name, :email, :role) - end - - def check_admin - redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? - end - end -end +# app/controllers/admin/users_controller.rb + +module Admin + class UsersController < ApplicationController + before_action :authenticate_user! + before_action :check_admin + before_action :set_user, only: [:show, :edit, :update, :destroy] + + def index + @users = User.all + end + + def show + end + + def edit + end + + def update + if @user.update(user_params) + redirect_to admin_users_path, notice: 'Usuário atualizado com sucesso' + else + render :edit + end + end + + def destroy + @user.destroy + redirect_to admin_users_path, notice: 'Usuário deletado com sucesso' + end + + private + + def set_user + @user = User.find(params[:id]) + end + + def user_params + params.require(:user).permit(:name, :email, :role) + end + + def check_admin + redirect_to root_path, alert: 'Acesso negado!' unless current_user.admin? + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0d95db22b4..e304aa86b8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,4 @@ -class ApplicationController < ActionController::Base - # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. - allow_browser versions: :modern -end +class ApplicationController < ActionController::Base + # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. + allow_browser versions: :modern +end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 192b3c908a..3b92c369d0 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,14 +1,14 @@ -# app/controllers/home_controller.rb - -class HomeController < ApplicationController - before_action :authenticate_user! - - def index - if current_user.admin? - redirect_to admin_root_path - else - @pending_forms = current_user.pending_forms.order(due_date: :asc) - @completed_forms = current_user.completed_forms.order(created_at: :desc) - end - end -end +# app/controllers/home_controller.rb + +class HomeController < ApplicationController + before_action :authenticate_user! + + def index + if current_user.admin? + redirect_to admin_root_path + else + @pending_forms = current_user.pending_forms.order(due_date: :asc) + @completed_forms = current_user.completed_forms.order(created_at: :desc) + end + end +end diff --git a/app/controllers/student/student_controller.rb b/app/controllers/student/student_controller.rb index 5cc7c4ae41..1394307a95 100644 --- a/app/controllers/student/student_controller.rb +++ b/app/controllers/student/student_controller.rb @@ -1,68 +1,68 @@ -# app/controllers/student/forms_controller.rb - -module Student - class FormsController < ApplicationController - before_action :authenticate_user! - before_action :check_student - before_action :set_form, only: [:show, :answer, :submit_answer] - before_action :check_form_accessible, only: [:show, :answer, :submit_answer] - - def index - @pending_forms = current_user.pending_forms.order(due_date: :asc) - @completed_forms = current_user.completed_forms.order(created_at: :desc) - end - - def show - @form_response = @form.form_responses.find_by(user: current_user) - @form_response ||= FormResponse.new(form: @form, user: current_user) - end - - def answer - @form_response = @form.form_responses.find_by(user: current_user) - - if @form_response.nil? - @form_response = FormResponse.create(form: @form, user: current_user) - @form_response.build_answers_for_fields - @form_response.save - end - - @form_response.reload - end - - def submit_answer - @form_response = @form.form_responses.find_by(user: current_user) - - if @form_response.nil? - redirect_to student_form_path(@form), alert: 'Resposta não encontrada' - return - end - - if update_answers && @form_response.submit! - redirect_to student_forms_path, notice: 'Formulário respondido com sucesso!' - else - redirect_to answer_student_form_path(@form), alert: 'Erro ao salvar respostas' - end - end - - private - - def set_form - @form = Form.find(params[:id]) - end - - def check_student - redirect_to admin_root_path if current_user.admin? - end - - def check_form_accessible - unless current_user.klasses.include?(@form.klass) && @form.published? - redirect_to student_forms_path, alert: 'Acesso negado a este formulário' - end - end - - def update_answers - form_answers_params = params.require(:form_response).permit(form_answers_attributes: [:id, :answer]) - @form_response.update(form_answers_params) - end - end -end +# app/controllers/student/forms_controller.rb + +module Student + class FormsController < ApplicationController + before_action :authenticate_user! + before_action :check_student + before_action :set_form, only: [:show, :answer, :submit_answer] + before_action :check_form_accessible, only: [:show, :answer, :submit_answer] + + def index + @pending_forms = current_user.pending_forms.order(due_date: :asc) + @completed_forms = current_user.completed_forms.order(created_at: :desc) + end + + def show + @form_response = @form.form_responses.find_by(user: current_user) + @form_response ||= FormResponse.new(form: @form, user: current_user) + end + + def answer + @form_response = @form.form_responses.find_by(user: current_user) + + if @form_response.nil? + @form_response = FormResponse.create(form: @form, user: current_user) + @form_response.build_answers_for_fields + @form_response.save + end + + @form_response.reload + end + + def submit_answer + @form_response = @form.form_responses.find_by(user: current_user) + + if @form_response.nil? + redirect_to student_form_path(@form), alert: 'Resposta não encontrada' + return + end + + if update_answers && @form_response.submit! + redirect_to student_forms_path, notice: 'Formulário respondido com sucesso!' + else + redirect_to answer_student_form_path(@form), alert: 'Erro ao salvar respostas' + end + end + + private + + def set_form + @form = Form.find(params[:id]) + end + + def check_student + redirect_to admin_root_path if current_user.admin? + end + + def check_form_accessible + unless current_user.klasses.include?(@form.klass) && @form.published? + redirect_to student_forms_path, alert: 'Acesso negado a este formulário' + end + end + + def update_answers + form_answers_params = params.require(:form_response).permit(form_answers_attributes: [:id, :answer]) + @form_response.update(form_answers_params) + end + end +end diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb index 4052b7c4b7..435969172b 100644 --- a/app/helpers/admin/dashboard_helper.rb +++ b/app/helpers/admin/dashboard_helper.rb @@ -1,2 +1,2 @@ -module Admin::DashboardHelper -end +module Admin::DashboardHelper +end diff --git a/app/helpers/admin/users_helper.rb b/app/helpers/admin/users_helper.rb index 5995c2aa82..047899e045 100644 --- a/app/helpers/admin/users_helper.rb +++ b/app/helpers/admin/users_helper.rb @@ -1,2 +1,2 @@ -module Admin::UsersHelper -end +module Admin::UsersHelper +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be7945c..bf7774a466 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,2 @@ -module ApplicationHelper -end +module ApplicationHelper +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 23de56ac60..cfc942e675 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -1,2 +1,2 @@ -module HomeHelper -end +module HomeHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 0d7b49404c..aff7495165 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,3 @@ -// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails -import "@hotwired/turbo-rails" -import "controllers" +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" +import "controllers" diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js index 1213e85c7a..bffc2b182d 100644 --- a/app/javascript/controllers/application.js +++ b/app/javascript/controllers/application.js @@ -1,9 +1,9 @@ -import { Application } from "@hotwired/stimulus" - -const application = Application.start() - -// Configure Stimulus development experience -application.debug = false -window.Stimulus = application - -export { application } +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js index 5975c0789d..345818ba5e 100644 --- a/app/javascript/controllers/hello_controller.js +++ b/app/javascript/controllers/hello_controller.js @@ -1,7 +1,7 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - connect() { - this.element.textContent = "Hello World!" - } -} +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + connect() { + this.element.textContent = "Hello World!" + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index 1156bf8362..db9997a592 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -1,4 +1,4 @@ -// Import and register all your controllers from the importmap via controllers/**/*_controller -import { application } from "controllers/application" -import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" -eagerLoadControllersFrom("controllers", application) +// Import and register all your controllers from the importmap via controllers/**/*_controller +import { application } from "controllers/application" +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb index d394c3d106..8eb2b33e79 100644 --- a/app/jobs/application_job.rb +++ b/app/jobs/application_job.rb @@ -1,7 +1,7 @@ -class ApplicationJob < ActiveJob::Base - # Automatically retry jobs that encountered a deadlock - # retry_on ActiveRecord::Deadlocked - - # Most jobs are safe to ignore if the underlying records are no longer available - # discard_on ActiveJob::DeserializationError -end +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c8148f..6a922dff26 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ -class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" - layout "mailer" -end +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index b63caeb8a5..1eb6ae6015 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,3 @@ -class ApplicationRecord < ActiveRecord::Base - primary_abstract_class -end +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/app/models/class_member.rb b/app/models/class_member.rb index dac66ffeb1..9011631217 100644 --- a/app/models/class_member.rb +++ b/app/models/class_member.rb @@ -1,11 +1,11 @@ -# app/models/class_member.rb - -class ClassMember < ApplicationRecord - # Associações - belongs_to :user - belongs_to :klass - - # Validações - validates :user_id, uniqueness: { scope: :klass_id, message: 'já está inscrito nesta turma' } - validates :role, presence: true, inclusion: { in: %w(dicente docente), message: 'deve ser dicente ou docente' } +# app/models/class_member.rb + +class ClassMember < ApplicationRecord + # Associações + belongs_to :user + belongs_to :klass + + # Validações + validates :user_id, uniqueness: { scope: :klass_id, message: 'já está inscrito nesta turma' } + validates :role, presence: true, inclusion: { in: %w(dicente docente), message: 'deve ser dicente ou docente' } end \ No newline at end of file diff --git a/app/models/form.rb b/app/models/form.rb index f7fe723dce..ad3c708e45 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -1,32 +1,32 @@ -# app/models/form.rb - -class Form < ApplicationRecord - belongs_to :form_template - belongs_to :klass - has_many :form_responses, dependent: :destroy - - validates :form_template_id, presence: true - validates :klass_id, presence: true - validates :title, presence: true - - enum :status, { draft: 0, published: 1, closed: 2 } - - # Definir status padrão - before_create :set_default_status - - # Retorna todos os alunos da turma que ainda não responderam o formulário - def pending_responses - klass.students - form_responses.map(&:user) - end - - # Retorna alunos que já responderam - def completed_responses - form_responses.map(&:user) - end - - private - - def set_default_status - self.status ||= :draft - end -end +# app/models/form.rb + +class Form < ApplicationRecord + belongs_to :form_template + belongs_to :klass + has_many :form_responses, dependent: :destroy + + validates :form_template_id, presence: true + validates :klass_id, presence: true + validates :title, presence: true + + enum :status, { draft: 0, published: 1, closed: 2 } + + # Definir status padrão + before_create :set_default_status + + # Retorna todos os alunos da turma que ainda não responderam o formulário + def pending_responses + klass.students - form_responses.map(&:user) + end + + # Retorna alunos que já responderam + def completed_responses + form_responses.map(&:user) + end + + private + + def set_default_status + self.status ||= :draft + end +end diff --git a/app/models/form_answer.rb b/app/models/form_answer.rb index 530c75e1f8..7ea208e990 100644 --- a/app/models/form_answer.rb +++ b/app/models/form_answer.rb @@ -1,10 +1,10 @@ -# app/models/form_answer.rb - -class FormAnswer < ApplicationRecord - belongs_to :form_response - belongs_to :form_template_field - - validates :form_response_id, presence: true - validates :form_template_field_id, presence: true - validates :answer, presence: true +# app/models/form_answer.rb + +class FormAnswer < ApplicationRecord + belongs_to :form_response + belongs_to :form_template_field + + validates :form_response_id, presence: true + validates :form_template_field_id, presence: true + validates :answer, presence: true end \ No newline at end of file diff --git a/app/models/form_response.rb b/app/models/form_response.rb index bf4a87f0ae..724864c2f7 100644 --- a/app/models/form_response.rb +++ b/app/models/form_response.rb @@ -1,37 +1,37 @@ -# app/models/form_response.rb - -class FormResponse < ApplicationRecord - belongs_to :form - belongs_to :user - has_many :form_answers, dependent: :destroy - - validates :form_id, presence: true - validates :user_id, presence: true - validates :user_id, uniqueness: { scope: :form_id, message: 'já respondeu este formulário' } - - accepts_nested_attributes_for :form_answers - - def completed? - submitted_at.present? - end - - def pending? - !completed? - end - - # Inicializar answers para todos os fields (sem validação) - def build_answers_for_fields - form.form_template.form_template_fields.order(:position).each do |field| - # Só adicionar se ainda não existe - unless form_answers.exists?(form_template_field_id: field.id) - form_answers.build(form_template_field: field, answer: '') - end - end - save(validate: false) if persisted? - end - - # Marcar como submetido - def submit! - update(submitted_at: Time.current) - end -end +# app/models/form_response.rb + +class FormResponse < ApplicationRecord + belongs_to :form + belongs_to :user + has_many :form_answers, dependent: :destroy + + validates :form_id, presence: true + validates :user_id, presence: true + validates :user_id, uniqueness: { scope: :form_id, message: 'já respondeu este formulário' } + + accepts_nested_attributes_for :form_answers + + def completed? + submitted_at.present? + end + + def pending? + !completed? + end + + # Inicializar answers para todos os fields (sem validação) + def build_answers_for_fields + form.form_template.form_template_fields.order(:position).each do |field| + # Só adicionar se ainda não existe + unless form_answers.exists?(form_template_field_id: field.id) + form_answers.build(form_template_field: field, answer: '') + end + end + save(validate: false) if persisted? + end + + # Marcar como submetido + def submit! + update(submitted_at: Time.current) + end +end diff --git a/app/models/form_template.rb b/app/models/form_template.rb index 53b7b867aa..c6140d7b7f 100644 --- a/app/models/form_template.rb +++ b/app/models/form_template.rb @@ -1,13 +1,13 @@ -# app/models/form_template.rb - -class FormTemplate < ApplicationRecord - belongs_to :user - has_many :form_template_fields, dependent: :destroy - has_many :forms, dependent: :destroy - - validates :name, presence: true - validates :description, presence: true - validates :user_id, presence: true - - accepts_nested_attributes_for :form_template_fields, allow_destroy: true +# app/models/form_template.rb + +class FormTemplate < ApplicationRecord + belongs_to :user + has_many :form_template_fields, dependent: :destroy + has_many :forms, dependent: :destroy + + validates :name, presence: true + validates :description, presence: true + validates :user_id, presence: true + + accepts_nested_attributes_for :form_template_fields, allow_destroy: true end \ No newline at end of file diff --git a/app/models/form_template_field.rb b/app/models/form_template_field.rb index d52f0904f8..c9a767e47a 100644 --- a/app/models/form_template_field.rb +++ b/app/models/form_template_field.rb @@ -1,18 +1,18 @@ -# app/models/form_template_field.rb - -class FormTemplateField < ApplicationRecord - belongs_to :form_template - - validates :label, presence: true - validates :field_type, presence: true - validates :position, presence: true, numericality: { only_integer: true } - - # Validar options apenas para tipos que precisam - validates :options, presence: true, if: :requires_options? - - private - - def requires_options? - ['select', 'radio', 'checkbox'].include?(field_type) - end -end +# app/models/form_template_field.rb + +class FormTemplateField < ApplicationRecord + belongs_to :form_template + + validates :label, presence: true + validates :field_type, presence: true + validates :position, presence: true, numericality: { only_integer: true } + + # Validar options apenas para tipos que precisam + validates :options, presence: true, if: :requires_options? + + private + + def requires_options? + ['select', 'radio', 'checkbox'].include?(field_type) + end +end diff --git a/app/models/klass.rb b/app/models/klass.rb index 3de95b22c5..0b2aa1799e 100644 --- a/app/models/klass.rb +++ b/app/models/klass.rb @@ -1,25 +1,25 @@ -# app/models/klass.rb - -class Klass < ApplicationRecord - # Associações - has_many :class_members, dependent: :destroy - has_many :users, through: :class_members - has_many :forms, dependent: :destroy - - # Validações - validates :code, presence: true, uniqueness: true - validates :name, presence: true - validates :semester, presence: true - - # Scopes - scope :active, -> { order(semester: :desc) } - - # Métodos úteis - def students - class_members.where(role: 'dicente').map(&:user) - end - - def teachers - class_members.where(role: 'docente').map(&:user) - end +# app/models/klass.rb + +class Klass < ApplicationRecord + # Associações + has_many :class_members, dependent: :destroy + has_many :users, through: :class_members + has_many :forms, dependent: :destroy + + # Validações + validates :code, presence: true, uniqueness: true + validates :name, presence: true + validates :semester, presence: true + + # Scopes + scope :active, -> { order(semester: :desc) } + + # Métodos úteis + def students + class_members.where(role: 'dicente').map(&:user) + end + + def teachers + class_members.where(role: 'docente').map(&:user) + end end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 64f2978d44..51b55e866e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,46 +1,46 @@ -# app/models/user.rb - -class User < ApplicationRecord - devise :database_authenticatable, - :recoverable, :rememberable, :validatable - - enum :role, { user: 0, admin: 1 } - - # Associações - has_many :class_members, dependent: :destroy - has_many :klasses, through: :class_members - has_many :form_templates, dependent: :destroy - has_many :form_responses, dependent: :destroy - - # Validações - validates :name, presence: true - validates :email, presence: true, uniqueness: true - - # Métodos auxiliares - def admin? - role == 'admin' - end - - def user? - role == 'user' - end - - # Formulários pendentes (não respondidos e no prazo) - def pending_forms - # Pega todos os forms publicados das turmas do usuário - all_forms = Form - .where(klass_id: klasses.pluck(:id), status: :published) - .where('due_date > ? OR due_date IS NULL', Time.current) - - # Remove apenas os que foram RESPONDIDOS (submitted_at NOT NULL) - all_forms.where.not( - id: form_responses.where.not(submitted_at: nil).pluck(:form_id) - ) - end - - # Formulários já respondidos - def completed_forms - Form - .where(id: form_responses.where.not(submitted_at: nil).pluck(:form_id)) - end -end +# app/models/user.rb + +class User < ApplicationRecord + devise :database_authenticatable, + :recoverable, :validatable + + enum :role, { user: 0, admin: 1 } + + # Associações + has_many :class_members, dependent: :destroy + has_many :klasses, through: :class_members + has_many :form_templates, dependent: :destroy + has_many :form_responses, dependent: :destroy + + # Validações + validates :name, presence: true + validates :email, presence: true, uniqueness: true + + # Métodos auxiliares + def admin? + role == 'admin' + end + + def user? + role == 'user' + end + + # Formulários pendentes (não respondidos e no prazo) + def pending_forms + # Pega todos os forms publicados das turmas do usuário + all_forms = Form + .where(klass_id: klasses.pluck(:id), status: :published) + .where('due_date > ? OR due_date IS NULL', Time.current) + + # Remove apenas os que foram RESPONDIDOS (submitted_at NOT NULL) + all_forms.where.not( + id: form_responses.where.not(submitted_at: nil).pluck(:form_id) + ) + end + + # Formulários já respondidos + def completed_forms + Form + .where(id: form_responses.where.not(submitted_at: nil).pluck(:form_id)) + end +end diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index 58012ca86f..edda358043 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -1,83 +1,83 @@ - - -
-
-

Painel Admin

- <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> -
- -
-
-
-
-
Total de Usuários
-

<%= @total_users %>

-
-
-
- -
-
-
-
Total de Admins
-

<%= @admin_count %>

-
-
-
-
- - - - - - - - - - -

Usuários Recentes

- - - - - - - - - - - <% @users.each do |user| %> - - - - - - - <% end %> - -
NomeEmailRoleAções
<%= user.name %><%= user.email %><%= user.role %> - <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> - <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> - <%= link_to 'Deletar', admin_user_path(user), method: :delete, class: 'btn btn-sm btn-danger' %> -
- - <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> -
+ + +
+
+

Painel Admin

+ <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> +
+ +
+
+
+
+
Total de Usuários
+

<%= @total_users %>

+
+
+
+ +
+
+
+
Total de Admins
+

<%= @admin_count %>

+
+
+
+
+ + + + + + + + + + +

Usuários Recentes

+ + + + + + + + + + + <% @users.each do |user| %> + + + + + + + <% end %> + +
NomeEmailRoleAções
<%= user.name %><%= user.email %><%= user.role %> + <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> + <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Deletar', admin_user_path(user), method: :delete, class: 'btn btn-sm btn-danger' %> +
+ + <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> +
diff --git a/app/views/admin/forms/edit.html.erb b/app/views/admin/forms/edit.html.erb index b8b0c4919e..4b82c67f4a 100644 --- a/app/views/admin/forms/edit.html.erb +++ b/app/views/admin/forms/edit.html.erb @@ -1,55 +1,55 @@ - - -
-
-
-

Editar Formulário

- - <%= form_with(model: [:admin, @form], local: true) do |form| %> - - <% if @form.errors.any? %> -
-

<%= pluralize(@form.errors.count, "erro") %> encontrado:

-
    - <% @form.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - -
- <%= form.label :form_template_id, "Template" %> - <%= form.collection_select :form_template_id, @form_templates, :id, :name, - {}, class: 'form-select' %> -
- -
- <%= form.label :klass_id, "Turma" %> - <%= form.collection_select :klass_id, @klasses, :id, :name, - {}, class: 'form-select' %> -
- -
- <%= form.label :title, "Título do Formulário" %> - <%= form.text_field :title, class: 'form-control' %> -
- -
- <%= form.label :description, "Descrição" %> - <%= form.text_area :description, class: 'form-control', rows: 4 %> -
- -
- <%= form.label :due_date, "Prazo (opcional)" %> - <%= form.datetime_select :due_date, class: 'form-control' %> -
- -
- <%= form.submit "Atualizar Formulário", class: 'btn btn-success' %> - <%= link_to 'Cancelar', admin_forms_path, class: 'btn btn-secondary' %> -
- <% end %> -
-
-
+ + +
+
+
+

Editar Formulário

+ + <%= form_with(model: [:admin, @form], local: true) do |form| %> + + <% if @form.errors.any? %> +
+

<%= pluralize(@form.errors.count, "erro") %> encontrado:

+
    + <% @form.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :form_template_id, "Template" %> + <%= form.collection_select :form_template_id, @form_templates, :id, :name, + {}, class: 'form-select' %> +
+ +
+ <%= form.label :klass_id, "Turma" %> + <%= form.collection_select :klass_id, @klasses, :id, :name, + {}, class: 'form-select' %> +
+ +
+ <%= form.label :title, "Título do Formulário" %> + <%= form.text_field :title, class: 'form-control' %> +
+ +
+ <%= form.label :description, "Descrição" %> + <%= form.text_area :description, class: 'form-control', rows: 4 %> +
+ +
+ <%= form.label :due_date, "Prazo (opcional)" %> + <%= form.datetime_select :due_date, class: 'form-control' %> +
+ +
+ <%= form.submit "Atualizar Formulário", class: 'btn btn-success' %> + <%= link_to 'Cancelar', admin_forms_path, class: 'btn btn-secondary' %> +
+ <% end %> +
+
+
diff --git a/app/views/admin/forms/index.html.erb b/app/views/admin/forms/index.html.erb index 8ec3aba439..e57fe1b1b0 100644 --- a/app/views/admin/forms/index.html.erb +++ b/app/views/admin/forms/index.html.erb @@ -1,74 +1,74 @@ - - -
-
-

Formulários

-
- <%= link_to 'Novo Formulário', new_admin_form_path, class: 'btn btn-primary' %> - <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> -
-
- - <% if @forms.any? %> -
- - - - - - - - - - - - - - <% @forms.each do |form| %> - - - - - - - - - - <% end %> - -
TítuloTemplateTurmaStatusRespostasPrazoAções
<%= form.title %><%= form.form_template.name %><%= form.klass.name %> - <% case form.status %> - <% when 'draft' %> - Rascunho - <% when 'published' %> - Publicado - <% when 'closed' %> - Fechado - <% end %> - - <%= form.completed_responses.count %> - / - <%= form.klass.students.count %> - <%= form.due_date.present? ? l(form.due_date, format: :short) : '-' %> - <%= link_to 'Ver', admin_form_path(form), class: 'btn btn-sm btn-info' %> - <% if form.draft? %> - <%= link_to 'Editar', edit_admin_form_path(form), class: 'btn btn-sm btn-warning' %> - <%= button_to 'Publicar', publish_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-success', form_class: 'd-inline' %> - <% end %> - <% if form.published? %> - <%= button_to 'Fechar', close_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-danger', form_class: 'd-inline' %> - <% end %> - <%= link_to 'Deletar', admin_form_path(form), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-outline-danger' %> -
-
- <% else %> -
- Nenhum formulário criado ainda. - <%= link_to 'Criar seu primeiro formulário', new_admin_form_path, class: 'alert-link' %> -
- <% end %> - -
- <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> -
-
+ + +
+
+

Formulários

+
+ <%= link_to 'Novo Formulário', new_admin_form_path, class: 'btn btn-primary' %> + <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
+
+ + <% if @forms.any? %> +
+ + + + + + + + + + + + + + <% @forms.each do |form| %> + + + + + + + + + + <% end %> + +
TítuloTemplateTurmaStatusRespostasPrazoAções
<%= form.title %><%= form.form_template.name %><%= form.klass.name %> + <% case form.status %> + <% when 'draft' %> + Rascunho + <% when 'published' %> + Publicado + <% when 'closed' %> + Fechado + <% end %> + + <%= form.completed_responses.count %> + / + <%= form.klass.students.count %> + <%= form.due_date.present? ? l(form.due_date, format: :short) : '-' %> + <%= link_to 'Ver', admin_form_path(form), class: 'btn btn-sm btn-info' %> + <% if form.draft? %> + <%= link_to 'Editar', edit_admin_form_path(form), class: 'btn btn-sm btn-warning' %> + <%= button_to 'Publicar', publish_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-success', form_class: 'd-inline' %> + <% end %> + <% if form.published? %> + <%= button_to 'Fechar', close_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-danger', form_class: 'd-inline' %> + <% end %> + <%= link_to 'Deletar', admin_form_path(form), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-outline-danger' %> +
+
+ <% else %> +
+ Nenhum formulário criado ainda. + <%= link_to 'Criar seu primeiro formulário', new_admin_form_path, class: 'alert-link' %> +
+ <% end %> + +
+ <%= link_to 'Voltar ao Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
+
diff --git a/app/views/admin/forms/new.html.erb b/app/views/admin/forms/new.html.erb index a713ba4788..596a95e024 100644 --- a/app/views/admin/forms/new.html.erb +++ b/app/views/admin/forms/new.html.erb @@ -1,58 +1,58 @@ - - -
-
-
-

Novo Formulário

- - <%= form_with(model: [:admin, @form], local: true, method: :post) do |form| %> - - <% if @form.errors.any? %> -
-

<%= pluralize(@form.errors.count, "erro") %> encontrado:

-
    - <% @form.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - -
- <%= form.label :form_template_id, "Template" %> - <%= form.collection_select :form_template_id, @form_templates, :id, :name, - { prompt: 'Selecione um template' }, class: 'form-select' %> - Escolha um template para criar o formulário -
- -
- <%= form.label :klass_id, "Turma" %> - <%= form.collection_select :klass_id, @klasses, :id, :name, - { prompt: 'Selecione uma turma' }, class: 'form-select' %> - Selecione a turma que responderá este formulário -
- -
- <%= form.label :title, "Título do Formulário" %> - <%= form.text_field :title, class: 'form-control', placeholder: 'Ex: Avaliação de Desempenho' %> -
- -
- <%= form.label :description, "Descrição" %> - <%= form.text_area :description, class: 'form-control', rows: 4, - placeholder: 'Descreva o propósito deste formulário...' %> -
- -
- <%= form.label :due_date, "Prazo (opcional)" %> - <%= form.datetime_select :due_date, class: 'form-control' %> -
- -
- <%= form.submit "Criar Formulário", class: 'btn btn-success' %> - <%= link_to 'Cancelar', admin_forms_path, class: 'btn btn-secondary' %> -
- <% end %> -
-
-
+ + +
+
+
+

Novo Formulário

+ + <%= form_with(model: [:admin, @form], local: true, method: :post) do |form| %> + + <% if @form.errors.any? %> +
+

<%= pluralize(@form.errors.count, "erro") %> encontrado:

+
    + <% @form.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :form_template_id, "Template" %> + <%= form.collection_select :form_template_id, @form_templates, :id, :name, + { prompt: 'Selecione um template' }, class: 'form-select' %> + Escolha um template para criar o formulário +
+ +
+ <%= form.label :klass_id, "Turma" %> + <%= form.collection_select :klass_id, @klasses, :id, :name, + { prompt: 'Selecione uma turma' }, class: 'form-select' %> + Selecione a turma que responderá este formulário +
+ +
+ <%= form.label :title, "Título do Formulário" %> + <%= form.text_field :title, class: 'form-control', placeholder: 'Ex: Avaliação de Desempenho' %> +
+ +
+ <%= form.label :description, "Descrição" %> + <%= form.text_area :description, class: 'form-control', rows: 4, + placeholder: 'Descreva o propósito deste formulário...' %> +
+ +
+ <%= form.label :due_date, "Prazo (opcional)" %> + <%= form.datetime_select :due_date, class: 'form-control' %> +
+ +
+ <%= form.submit "Criar Formulário", class: 'btn btn-success' %> + <%= link_to 'Cancelar', admin_forms_path, class: 'btn btn-secondary' %> +
+ <% end %> +
+
+
diff --git a/app/views/admin/forms/show.html.erb b/app/views/admin/forms/show.html.erb index 2e00d7fa15..1e89ded70f 100644 --- a/app/views/admin/forms/show.html.erb +++ b/app/views/admin/forms/show.html.erb @@ -1,35 +1,35 @@ - - -

Respostas

-
- - - - - - - - - - - <% @form.form_responses.each do |response| %> - - - - - - - <% end %> - -
AlunoStatusRespondido emAções
<%= response.user.name %> - <% if response.completed? %> - Respondido - <% else %> - Pendente - <% end %> - <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> - <% if response.completed? %> - <%= link_to 'Ver Respostas', view_response_admin_form_path(@form, response_id: response.id), class: 'btn btn-sm btn-info' %> - <% end %> -
-
+ + +

Respostas

+
+ + + + + + + + + + + <% @form.form_responses.each do |response| %> + + + + + + + <% end %> + +
AlunoStatusRespondido emAções
<%= response.user.name %> + <% if response.completed? %> + Respondido + <% else %> + Pendente + <% end %> + <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> + <% if response.completed? %> + <%= link_to 'Ver Respostas', view_response_admin_form_path(@form, response_id: response.id), class: 'btn btn-sm btn-info' %> + <% end %> +
+
diff --git a/app/views/admin/users/destroy.html.erb b/app/views/admin/users/destroy.html.erb index c80f2fdb02..fa3ee39e77 100644 --- a/app/views/admin/users/destroy.html.erb +++ b/app/views/admin/users/destroy.html.erb @@ -1,2 +1,2 @@ -

Admin::Users#destroy

-

Find me in app/views/admin/users/destroy.html.erb

+

Admin::Users#destroy

+

Find me in app/views/admin/users/destroy.html.erb

diff --git a/app/views/admin/users/edit.html.erb b/app/views/admin/users/edit.html.erb index 363be9579e..fe0288b5ad 100644 --- a/app/views/admin/users/edit.html.erb +++ b/app/views/admin/users/edit.html.erb @@ -1,2 +1,2 @@ -

Admin::Users#edit

-

Find me in app/views/admin/users/edit.html.erb

+

Admin::Users#edit

+

Find me in app/views/admin/users/edit.html.erb

diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index c2a6c281c8..e5159a28d9 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -1,36 +1,36 @@ - - -
-

Gerenciar Usuários

- - - - - - - - - - - - - - <% @users.each do |user| %> - - - - - - - - - <% end %> - -
IDNomeEmailRoleCriado emAções
<%= user.id %><%= user.name %><%= user.email %><%= user.role %><%= user.created_at.strftime("%d/%m/%Y") %> - <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> - <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> - <%= link_to 'Deletar', admin_user_path(user), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> -
- - <%= link_to 'Dashboard', admin_root_path, class: 'btn btn-secondary' %> -
+ + +
+

Gerenciar Usuários

+ + + + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + + + <% end %> + +
IDNomeEmailRoleCriado emAções
<%= user.id %><%= user.name %><%= user.email %><%= user.role %><%= user.created_at.strftime("%d/%m/%Y") %> + <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> + <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> + <%= link_to 'Deletar', admin_user_path(user), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> +
+ + <%= link_to 'Dashboard', admin_root_path, class: 'btn btn-secondary' %> +
diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb index 9ce4ddb33b..2283782643 100644 --- a/app/views/admin/users/show.html.erb +++ b/app/views/admin/users/show.html.erb @@ -1,2 +1,2 @@ -

Admin::Users#show

-

Find me in app/views/admin/users/show.html.erb

+

Admin::Users#show

+

Find me in app/views/admin/users/show.html.erb

diff --git a/app/views/admin/users/update.html.erb b/app/views/admin/users/update.html.erb index 2851bd8864..e9ab64e08d 100644 --- a/app/views/admin/users/update.html.erb +++ b/app/views/admin/users/update.html.erb @@ -1,2 +1,2 @@ -

Admin::Users#update

-

Find me in app/views/admin/users/update.html.erb

+

Admin::Users#update

+

Find me in app/views/admin/users/update.html.erb

diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb index b12dd0cbeb..b9bed0ecf9 100644 --- a/app/views/devise/confirmations/new.html.erb +++ b/app/views/devise/confirmations/new.html.erb @@ -1,16 +1,16 @@ -

Resend confirmation instructions

- -<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> -
- -
- <%= f.submit "Resend confirmation instructions" %> -
-<% end %> - -<%= render "devise/shared/links" %> +

Resend confirmation instructions

+ +<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +
+ +
+ <%= f.submit "Resend confirmation instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index dc55f64f69..491d767fcc 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -1,5 +1,5 @@ -

Welcome <%= @email %>!

- -

You can confirm your account email through the link below:

- -

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

+

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb index 32f4ba8038..d9a6e51fe1 100644 --- a/app/views/devise/mailer/email_changed.html.erb +++ b/app/views/devise/mailer/email_changed.html.erb @@ -1,7 +1,7 @@ -

Hello <%= @email %>!

- -<% if @resource.try(:unconfirmed_email?) %> -

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

-<% else %> -

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

-<% end %> +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb index b41daf476a..51e6aaffb5 100644 --- a/app/views/devise/mailer/password_change.html.erb +++ b/app/views/devise/mailer/password_change.html.erb @@ -1,3 +1,3 @@ -

Hello <%= @resource.email %>!

- -

We're contacting you to notify you that your password has been changed.

+

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index f667dc12fe..572feaa9e4 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -1,8 +1,8 @@ -

Hello <%= @resource.email %>!

- -

Someone has requested a link to change your password. You can do this through the link below.

- -

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

- -

If you didn't request this, please ignore this email.

-

Your password won't change until you access the link above and create a new one.

+

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb index 41e148bf2a..78b9581c6b 100644 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -1,7 +1,7 @@ -

Hello <%= @resource.email %>!

- -

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

- -

Click the link below to unlock your account:

- -

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

+

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb index 5fbb9ff0a7..3c86fbae19 100644 --- a/app/views/devise/passwords/edit.html.erb +++ b/app/views/devise/passwords/edit.html.erb @@ -1,25 +1,25 @@ -

Change your password

- -<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - <%= f.hidden_field :reset_password_token %> - -
- <%= f.label :password, "New password" %>
- <% if @minimum_password_length %> - (<%= @minimum_password_length %> characters minimum)
- <% end %> - <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> -
- -
- <%= f.label :password_confirmation, "Confirm new password" %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.submit "Change my password" %> -
-<% end %> - -<%= render "devise/shared/links" %> +

Change your password

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + <%= f.hidden_field :reset_password_token %> + +
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum)
+ <% end %> + <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Change my password" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb index 9b486b81b9..8ba261e05b 100644 --- a/app/views/devise/passwords/new.html.erb +++ b/app/views/devise/passwords/new.html.erb @@ -1,16 +1,16 @@ -

Forgot your password?

- -<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.submit "Send me reset password instructions" %> -
-<% end %> - -<%= render "devise/shared/links" %> +

Forgot your password?

+ +<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Send me reset password instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index b82e3365a3..cc7ee46dc8 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -1,43 +1,43 @@ -

Edit <%= resource_name.to_s.humanize %>

- -<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- - <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> -
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
- <% end %> - -
- <%= f.label :password %> (leave blank if you don't want to change it)
- <%= f.password_field :password, autocomplete: "new-password" %> - <% if @minimum_password_length %> -
- <%= @minimum_password_length %> characters minimum - <% end %> -
- -
- <%= f.label :password_confirmation %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.label :current_password %> (we need your current password to confirm your changes)
- <%= f.password_field :current_password, autocomplete: "current-password" %> -
- -
- <%= f.submit "Update" %> -
-<% end %> - -

Cancel my account

- -
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
- -<%= link_to "Back", :back %> +

Edit <%= resource_name.to_s.humanize %>

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %> + +
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %> + <% if @minimum_password_length %> +
+ <%= @minimum_password_length %> characters minimum + <% end %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %> +
+ +
+ <%= f.submit "Update" %> +
+<% end %> + +

Cancel my account

+ +
Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index d655b66f6f..d5d325ee2a 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -1,29 +1,29 @@ -

Sign up

- -<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %> - <% if @minimum_password_length %> - (<%= @minimum_password_length %> characters minimum) - <% end %>
- <%= f.password_field :password, autocomplete: "new-password" %> -
- -
- <%= f.label :password_confirmation %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.submit "Sign up" %> -
-<% end %> - -<%= render "devise/shared/links" %> +

Sign up

+ +<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %> + <% if @minimum_password_length %> + (<%= @minimum_password_length %> characters minimum) + <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %> +
+ +
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %> +
+ +
+ <%= f.submit "Sign up" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 5ede96489d..f2d036d228 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -1,26 +1,26 @@ -

Log in

- -<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %>
- <%= f.password_field :password, autocomplete: "current-password" %> -
- - <% if devise_mapping.rememberable? %> -
- <%= f.check_box :remember_me %> - <%= f.label :remember_me %> -
- <% end %> - -
- <%= f.submit "Log in" %> -
-<% end %> - -<%= render "devise/shared/links" %> +

Log in

+ +<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %> +
+ + <% if devise_mapping.rememberable? %> +
+ <%= f.check_box :remember_me %> + <%= f.label :remember_me %> +
+ <% end %> + +
+ <%= f.submit "Log in" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb index cabfe307ef..47a4a0ef59 100644 --- a/app/views/devise/shared/_error_messages.html.erb +++ b/app/views/devise/shared/_error_messages.html.erb @@ -1,15 +1,15 @@ -<% if resource.errors.any? %> -
-

- <%= I18n.t("errors.messages.not_saved", - count: resource.errors.count, - resource: resource.class.model_name.human.downcase) - %> -

-
    - <% resource.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
-<% end %> +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+
    + <% resource.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+<% end %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 7a75304bad..2262dc2976 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -1,25 +1,25 @@ -<%- if controller_name != 'sessions' %> - <%= link_to "Log in", new_session_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.registerable? && controller_name != 'registrations' %> - <%= link_to "Sign up", new_registration_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> - <%= link_to "Forgot your password?", new_password_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> - <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> - <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
- <% end %> -<% end %> +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
+ <% end %> +<% end %> diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb index ffc34de8d1..7eb31c85a1 100644 --- a/app/views/devise/unlocks/new.html.erb +++ b/app/views/devise/unlocks/new.html.erb @@ -1,16 +1,16 @@ -

Resend unlock instructions

- -<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.submit "Resend unlock instructions" %> -
-<% end %> - -<%= render "devise/shared/links" %> +

Resend unlock instructions

+ +<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= render "devise/shared/error_messages", resource: resource %> + +
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %> +
+ +
+ <%= f.submit "Resend unlock instructions" %> +
+<% end %> + +<%= render "devise/shared/links" %> diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 4701c60ffc..0e42e60e41 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,94 +1,94 @@ - - -
-
-
-

Bem-vindo, <%= current_user.name %>!

-

Email: <%= current_user.email %>

-
- <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> -
- - -
-

📋 Formulários Pendentes

- - <% if @pending_forms.any? %> -
- <% @pending_forms.each do |form| %> -
-
-
-
<%= form.title %>
-

- Turma: <%= form.klass.name %> -

-

- <%= truncate(form.description, length: 100) %> -

- - <% if form.due_date.present? %> -

- - Prazo: - - <%= l(form.due_date, format: :short) %> - - -

- <% end %> - -
- <%= link_to 'Responder Formulário', answer_student_form_path(form), class: 'btn btn-primary btn-sm' %> -
-
-
-
- <% end %> -
- <% else %> -
- ✓ Parabéns! Você respondeu todos os formulários pendentes. -
- <% end %> -
- - -
-

✓ Formulários Respondidos

- - <% if @completed_forms.any? %> -
- - - - - - - - - - - <% @completed_forms.each do |form| %> - - - - - - - <% end %> - -
TítuloTurmaRespondido emAções
<%= form.title %><%= form.klass.name %> - <% response = form.form_responses.find_by(user: current_user) %> - <%= l(response.submitted_at, format: :short) if response&.submitted_at %> - - <%= link_to 'Ver', student_form_path(form), class: 'btn btn-sm btn-info' %> -
-
- <% else %> -
- Nenhum formulário respondido ainda. -
- <% end %> -
-
+ + +
+
+
+

Bem-vindo, <%= current_user.name %>!

+

Email: <%= current_user.email %>

+
+ <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> +
+ + +
+

📋 Formulários Pendentes

+ + <% if @pending_forms.any? %> +
+ <% @pending_forms.each do |form| %> +
+
+
+
<%= form.title %>
+

+ Turma: <%= form.klass.name %> +

+

+ <%= truncate(form.description, length: 100) %> +

+ + <% if form.due_date.present? %> +

+ + Prazo: + + <%= l(form.due_date, format: :short) %> + + +

+ <% end %> + +
+ <%= link_to 'Responder Formulário', answer_student_form_path(form), class: 'btn btn-primary btn-sm' %> +
+
+
+
+ <% end %> +
+ <% else %> +
+ ✓ Parabéns! Você respondeu todos os formulários pendentes. +
+ <% end %> +
+ + +
+

✓ Formulários Respondidos

+ + <% if @completed_forms.any? %> +
+ + + + + + + + + + + <% @completed_forms.each do |form| %> + + + + + + + <% end %> + +
TítuloTurmaRespondido emAções
<%= form.title %><%= form.klass.name %> + <% response = form.form_responses.find_by(user: current_user) %> + <%= l(response.submitted_at, format: :short) if response&.submitted_at %> + + <%= link_to 'Ver', student_form_path(form), class: 'btn btn-sm btn-info' %> +
+
+ <% else %> +
+ Nenhum formulário respondido ainda. +
+ <% end %> +
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index b4e08b61db..adc3733b0a 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,28 +1,28 @@ - - - - <%= content_for(:title) || "Projeto Camaar" %> - - - - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - - <%= yield :head %> - - <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> - <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> - - - - - - <%# Includes all stylesheet files in app/assets/stylesheets %> - <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> - <%= javascript_importmap_tags %> - - - - <%= yield %> - - + + + + <%= content_for(:title) || "Projeto Camaar" %> + + + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= yield :head %> + + <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> + <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> + + + + + + <%# Includes all stylesheet files in app/assets/stylesheets %> + <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 3aac9002ed..e825dcc70f 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -1,13 +1,13 @@ - - - - - - - - - <%= yield %> - - + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb index 37f0bddbd7..aab62e324b 100644 --- a/app/views/layouts/mailer.text.erb +++ b/app/views/layouts/mailer.text.erb @@ -1 +1 @@ -<%= yield %> +<%= yield %> diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb index 76a2c73c81..c1758fdaa2 100644 --- a/app/views/pwa/manifest.json.erb +++ b/app/views/pwa/manifest.json.erb @@ -1,22 +1,22 @@ -{ - "name": "ProjetoCamaar", - "icons": [ - { - "src": "/icon.png", - "type": "image/png", - "sizes": "512x512" - }, - { - "src": "/icon.png", - "type": "image/png", - "sizes": "512x512", - "purpose": "maskable" - } - ], - "start_url": "/", - "display": "standalone", - "scope": "/", - "description": "ProjetoCamaar.", - "theme_color": "red", - "background_color": "red" -} +{ + "name": "ProjetoCamaar", + "icons": [ + { + "src": "/icon.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "/icon.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "maskable" + } + ], + "start_url": "/", + "display": "standalone", + "scope": "/", + "description": "ProjetoCamaar.", + "theme_color": "red", + "background_color": "red" +} diff --git a/app/views/pwa/service-worker.js b/app/views/pwa/service-worker.js index b3a13fb7bb..9f8b0b5605 100644 --- a/app/views/pwa/service-worker.js +++ b/app/views/pwa/service-worker.js @@ -1,26 +1,26 @@ -// Add a service worker for processing Web Push notifications: -// -// self.addEventListener("push", async (event) => { -// const { title, options } = await event.data.json() -// event.waitUntil(self.registration.showNotification(title, options)) -// }) -// -// self.addEventListener("notificationclick", function(event) { -// event.notification.close() -// event.waitUntil( -// clients.matchAll({ type: "window" }).then((clientList) => { -// for (let i = 0; i < clientList.length; i++) { -// let client = clientList[i] -// let clientPath = (new URL(client.url)).pathname -// -// if (clientPath == event.notification.data.path && "focus" in client) { -// return client.focus() -// } -// } -// -// if (clients.openWindow) { -// return clients.openWindow(event.notification.data.path) -// } -// }) -// ) -// }) +// Add a service worker for processing Web Push notifications: +// +// self.addEventListener("push", async (event) => { +// const { title, options } = await event.data.json() +// event.waitUntil(self.registration.showNotification(title, options)) +// }) +// +// self.addEventListener("notificationclick", function(event) { +// event.notification.close() +// event.waitUntil( +// clients.matchAll({ type: "window" }).then((clientList) => { +// for (let i = 0; i < clientList.length; i++) { +// let client = clientList[i] +// let clientPath = (new URL(client.url)).pathname +// +// if (clientPath == event.notification.data.path && "focus" in client) { +// return client.focus() +// } +// } +// +// if (clients.openWindow) { +// return clients.openWindow(event.notification.data.path) +// } +// }) +// ) +// }) diff --git a/app/views/student/forms/answer.html.erb b/app/views/student/forms/answer.html.erb index 38d5f2847c..6b6e036ff4 100644 --- a/app/views/student/forms/answer.html.erb +++ b/app/views/student/forms/answer.html.erb @@ -1,110 +1,110 @@ - - -
-
-
-

<%= @form.title %>

-

<%= @form.klass.name %>

-
- <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> -
- - <% if @form.description.present? %> -
- Instruções:
- <%= simple_format(@form.description) %> -
- <% end %> - - <% if @form.due_date.present? %> -
- ⏰ Prazo: <%= l(@form.due_date, format: :long) %> -
- <% end %> - - <% if @form_response.form_answers.empty? %> -
- Erro: Não há campos para preencher neste formulário. -
- <% else %> - <%= form_with(model: @form_response, local: true, url: submit_answer_student_form_path(@form), method: :post) do |form| %> - - <% if @form_response.errors.any? %> -
-

<%= pluralize(@form_response.errors.count, "erro") %> encontrado:

-
    - <% @form_response.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - - <%= form.fields_for :form_answers do |answer_form| %> - <% field = answer_form.object.form_template_field %> - -
-
- - - <% case field.field_type %> - <% when 'text' %> - <%= answer_form.text_field :answer, class: 'form-control', placeholder: field.label %> - - <% when 'textarea' %> - <%= answer_form.text_area :answer, class: 'form-control', rows: 4, placeholder: field.label %> - - <% when 'email' %> - <%= answer_form.email_field :answer, class: 'form-control', placeholder: field.label %> - - <% when 'number' %> - <%= answer_form.number_field :answer, class: 'form-control', placeholder: field.label %> - - <% when 'date' %> - <%= answer_form.date_field :answer, class: 'form-control' %> - - <% when 'select' %> - <% options = field.options.present? ? JSON.parse(field.options) : [] %> - <%= answer_form.select :answer, - options, - { prompt: 'Selecione uma opção' }, - class: 'form-select' %> - - <% when 'radio' %> - <% options = field.options.present? ? JSON.parse(field.options) : [] %> -
- <% options.each do |option| %> -
- <%= answer_form.radio_button :answer, option, class: 'form-check-input' %> - <%= answer_form.label :answer, option, class: 'form-check-label' %> -
- <% end %> -
- - <% when 'checkbox' %> - <% options = field.options.present? ? JSON.parse(field.options) : [] %> -
- <% options.each do |option| %> -
- <%= answer_form.check_box :answer, { multiple: true }, option, nil, class: 'form-check-input' %> - <%= answer_form.label :answer, option, class: 'form-check-label' %> -
- <% end %> -
- <% end %> -
-
- <% end %> - -
- <%= form.submit "Enviar Respostas", class: 'btn btn-success btn-lg' %> - <%= link_to 'Cancelar', root_path, class: 'btn btn-secondary btn-lg' %> -
- <% end %> - <% end %> -
+ + +
+
+
+

<%= @form.title %>

+

<%= @form.klass.name %>

+
+ <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> +
+ + <% if @form.description.present? %> +
+ Instruções:
+ <%= simple_format(@form.description) %> +
+ <% end %> + + <% if @form.due_date.present? %> +
+ ⏰ Prazo: <%= l(@form.due_date, format: :long) %> +
+ <% end %> + + <% if @form_response.form_answers.empty? %> +
+ Erro: Não há campos para preencher neste formulário. +
+ <% else %> + <%= form_with(model: @form_response, local: true, url: submit_answer_student_form_path(@form), method: :post) do |form| %> + + <% if @form_response.errors.any? %> +
+

<%= pluralize(@form_response.errors.count, "erro") %> encontrado:

+
    + <% @form_response.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + + <%= form.fields_for :form_answers do |answer_form| %> + <% field = answer_form.object.form_template_field %> + +
+
+ + + <% case field.field_type %> + <% when 'text' %> + <%= answer_form.text_field :answer, class: 'form-control', placeholder: field.label %> + + <% when 'textarea' %> + <%= answer_form.text_area :answer, class: 'form-control', rows: 4, placeholder: field.label %> + + <% when 'email' %> + <%= answer_form.email_field :answer, class: 'form-control', placeholder: field.label %> + + <% when 'number' %> + <%= answer_form.number_field :answer, class: 'form-control', placeholder: field.label %> + + <% when 'date' %> + <%= answer_form.date_field :answer, class: 'form-control' %> + + <% when 'select' %> + <% options = field.options.present? ? JSON.parse(field.options) : [] %> + <%= answer_form.select :answer, + options, + { prompt: 'Selecione uma opção' }, + class: 'form-select' %> + + <% when 'radio' %> + <% options = field.options.present? ? JSON.parse(field.options) : [] %> +
+ <% options.each do |option| %> +
+ <%= answer_form.radio_button :answer, option, class: 'form-check-input' %> + <%= answer_form.label :answer, option, class: 'form-check-label' %> +
+ <% end %> +
+ + <% when 'checkbox' %> + <% options = field.options.present? ? JSON.parse(field.options) : [] %> +
+ <% options.each do |option| %> +
+ <%= answer_form.check_box :answer, { multiple: true }, option, nil, class: 'form-check-input' %> + <%= answer_form.label :answer, option, class: 'form-check-label' %> +
+ <% end %> +
+ <% end %> +
+
+ <% end %> + +
+ <%= form.submit "Enviar Respostas", class: 'btn btn-success btn-lg' %> + <%= link_to 'Cancelar', root_path, class: 'btn btn-secondary btn-lg' %> +
+ <% end %> + <% end %> +
diff --git a/app/views/student/forms/show.html.erb b/app/views/student/forms/show.html.erb index a539983c23..64f787eabd 100644 --- a/app/views/student/forms/show.html.erb +++ b/app/views/student/forms/show.html.erb @@ -1,57 +1,57 @@ - - -
-
-
-

<%= @form.title %>

-

<%= @form.klass.name %>

-
- <%= link_to 'Voltar', student_forms_path, class: 'btn btn-secondary' %> -
- -
-
-
Informações
-

Descrição: <%= @form.description %>

-

- Prazo: - <% if @form.due_date.present? %> - <%= l(@form.due_date, format: :long) %> - <% else %> - Sem prazo - <% end %> -

-
-
- - <% if @form_response&.completed? %> -
- ✓ Formulário respondido em: <%= l(@form_response.submitted_at, format: :long) %> -
- -

Suas Respostas

-
- - - - - - - - - <% @form_response.form_answers.each do |answer| %> - - - - - <% end %> - -
PerguntaSua Resposta
<%= answer.form_template_field.label %><%= answer.answer %>
-
- <% else %> -
- Este formulário ainda não foi respondido. - <%= link_to 'Responder agora', answer_student_form_path(@form), class: 'btn btn-primary' %> -
- <% end %> -
+ + +
+
+
+

<%= @form.title %>

+

<%= @form.klass.name %>

+
+ <%= link_to 'Voltar', student_forms_path, class: 'btn btn-secondary' %> +
+ +
+
+
Informações
+

Descrição: <%= @form.description %>

+

+ Prazo: + <% if @form.due_date.present? %> + <%= l(@form.due_date, format: :long) %> + <% else %> + Sem prazo + <% end %> +

+
+
+ + <% if @form_response&.completed? %> +
+ ✓ Formulário respondido em: <%= l(@form_response.submitted_at, format: :long) %> +
+ +

Suas Respostas

+
+ + + + + + + + + <% @form_response.form_answers.each do |answer| %> + + + + + <% end %> + +
PerguntaSua Resposta
<%= answer.form_template_field.label %><%= answer.answer %>
+
+ <% else %> +
+ Este formulário ainda não foi respondido. + <%= link_to 'Responder agora', answer_student_form_path(@form), class: 'btn btn-primary' %> +
+ <% end %> +
diff --git a/bin/brakeman b/bin/brakeman index ace1c9ba08..c88c99b01a 100644 --- a/bin/brakeman +++ b/bin/brakeman @@ -1,7 +1,7 @@ -#!/usr/bin/env ruby -require "rubygems" -require "bundler/setup" - -ARGV.unshift("--ensure-latest") - -load Gem.bin_path("brakeman", "brakeman") +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +ARGV.unshift("--ensure-latest") + +load Gem.bin_path("brakeman", "brakeman") diff --git a/bin/bundle b/bin/bundle index 50da5fdf9e..b189df99cc 100644 --- a/bin/bundle +++ b/bin/bundle @@ -1,109 +1,109 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# -# This file was generated by Bundler. -# -# The application 'bundle' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require "rubygems" - -m = Module.new do - module_function - - def invoked_as_script? - File.expand_path($0) == File.expand_path(__FILE__) - end - - def env_var_version - ENV["BUNDLER_VERSION"] - end - - def cli_arg_version - return unless invoked_as_script? # don't want to hijack other binstubs - return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` - bundler_version = nil - update_index = nil - ARGV.each_with_index do |a, i| - if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) - bundler_version = a - end - next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ - bundler_version = $1 - update_index = i - end - bundler_version - end - - def gemfile - gemfile = ENV["BUNDLE_GEMFILE"] - return gemfile if gemfile && !gemfile.empty? - - File.expand_path("../Gemfile", __dir__) - end - - def lockfile - lockfile = - case File.basename(gemfile) - when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") - else "#{gemfile}.lock" - end - File.expand_path(lockfile) - end - - def lockfile_version - return unless File.file?(lockfile) - lockfile_contents = File.read(lockfile) - return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ - Regexp.last_match(1) - end - - def bundler_requirement - @bundler_requirement ||= - env_var_version || - cli_arg_version || - bundler_requirement_for(lockfile_version) - end - - def bundler_requirement_for(version) - return "#{Gem::Requirement.default}.a" unless version - - bundler_gem_version = Gem::Version.new(version) - - bundler_gem_version.approximate_recommendation - end - - def load_bundler! - ENV["BUNDLE_GEMFILE"] ||= gemfile - - activate_bundler - end - - def activate_bundler - gem_error = activation_error_handling do - gem "bundler", bundler_requirement - end - return if gem_error.nil? - require_error = activation_error_handling do - require "bundler/version" - end - return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) - warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" - exit 42 - end - - def activation_error_handling - yield - nil - rescue StandardError, LoadError => e - e - end -end - -m.load_bundler! - -if m.invoked_as_script? - load Gem.bin_path("bundler", "bundle") -end +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/bin/cucumber b/bin/cucumber index eb5e962e86..f66aa9b822 100644 --- a/bin/cucumber +++ b/bin/cucumber @@ -1,11 +1,11 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first -if vendored_cucumber_bin - load File.expand_path(vendored_cucumber_bin) -else - require 'rubygems' unless ENV['NO_RUBYGEMS'] - require 'cucumber' - load Cucumber::BINARY -end +#!/usr/bin/env ruby +# frozen_string_literal: true + +vendored_cucumber_bin = Dir["#{File.dirname(__FILE__)}/../vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +if vendored_cucumber_bin + load File.expand_path(vendored_cucumber_bin) +else + require 'rubygems' unless ENV['NO_RUBYGEMS'] + require 'cucumber' + load Cucumber::BINARY +end diff --git a/bin/dev b/bin/dev index 5f91c20545..d67c5c69af 100644 --- a/bin/dev +++ b/bin/dev @@ -1,2 +1,2 @@ -#!/usr/bin/env ruby -exec "./bin/rails", "server", *ARGV +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index 57567d69b4..a54c836e53 100644 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -1,14 +1,14 @@ -#!/bin/bash -e - -# Enable jemalloc for reduced memory usage and latency. -if [ -z "${LD_PRELOAD+x}" ]; then - LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) - export LD_PRELOAD -fi - -# If running the rails server then create or migrate existing database -if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then - ./bin/rails db:prepare -fi - -exec "${@}" +#!/bin/bash -e + +# Enable jemalloc for reduced memory usage and latency. +if [ -z "${LD_PRELOAD+x}" ]; then + LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) + export LD_PRELOAD +fi + +# If running the rails server then create or migrate existing database +if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/bin/importmap b/bin/importmap index 36502ab16c..ced9093a1f 100644 --- a/bin/importmap +++ b/bin/importmap @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby - -require_relative "../config/application" -require "importmap/commands" +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/bin/jobs b/bin/jobs index dcf59f309a..ff7026792a 100644 --- a/bin/jobs +++ b/bin/jobs @@ -1,6 +1,6 @@ -#!/usr/bin/env ruby - -require_relative "../config/environment" -require "solid_queue/cli" - -SolidQueue::Cli.start(ARGV) +#!/usr/bin/env ruby + +require_relative "../config/environment" +require "solid_queue/cli" + +SolidQueue::Cli.start(ARGV) diff --git a/bin/kamal b/bin/kamal index cbe59b95ed..3cf88036f6 100644 --- a/bin/kamal +++ b/bin/kamal @@ -1,27 +1,27 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# -# This file was generated by Bundler. -# -# The application 'kamal' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) - -bundle_binstub = File.expand_path("bundle", __dir__) - -if File.file?(bundle_binstub) - if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") - load(bundle_binstub) - else - abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. -Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") - end -end - -require "rubygems" -require "bundler/setup" - -load Gem.bin_path("kamal", "kamal") +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kamal' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kamal", "kamal") diff --git a/bin/rails b/bin/rails index efc0377492..4c8cea5178 100644 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby -APP_PATH = File.expand_path("../config/application", __dir__) -require_relative "../config/boot" -require "rails/commands" +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/bin/rake b/bin/rake index 4fbf10b960..4ca7073f83 100644 --- a/bin/rake +++ b/bin/rake @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby -require_relative "../config/boot" -require "rake" -Rake.application.run +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/bin/rubocop b/bin/rubocop index 40330c0ff1..11fa676929 100644 --- a/bin/rubocop +++ b/bin/rubocop @@ -1,8 +1,8 @@ -#!/usr/bin/env ruby -require "rubygems" -require "bundler/setup" - -# explicit rubocop config increases performance slightly while avoiding config confusion. -ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) - -load Gem.bin_path("rubocop", "rubocop") +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/setup b/bin/setup index be3db3c0d6..d86f655dc7 100644 --- a/bin/setup +++ b/bin/setup @@ -1,34 +1,34 @@ -#!/usr/bin/env ruby -require "fileutils" - -APP_ROOT = File.expand_path("..", __dir__) - -def system!(*args) - system(*args, exception: true) -end - -FileUtils.chdir APP_ROOT do - # This script is a way to set up or update your development environment automatically. - # This script is idempotent, so that you can run it at any time and get an expectable outcome. - # Add necessary setup steps to this file. - - puts "== Installing dependencies ==" - system("bundle check") || system!("bundle install") - - # puts "\n== Copying sample files ==" - # unless File.exist?("config/database.yml") - # FileUtils.cp "config/database.yml.sample", "config/database.yml" - # end - - puts "\n== Preparing database ==" - system! "bin/rails db:prepare" - - puts "\n== Removing old logs and tempfiles ==" - system! "bin/rails log:clear tmp:clear" - - unless ARGV.include?("--skip-server") - puts "\n== Starting development server ==" - STDOUT.flush # flush the output before exec(2) so that it displays - exec "bin/dev" - end -end +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end +end diff --git a/bin/thrust b/bin/thrust index 36bde2d832..7e1b8f6dd4 100644 --- a/bin/thrust +++ b/bin/thrust @@ -1,5 +1,5 @@ -#!/usr/bin/env ruby -require "rubygems" -require "bundler/setup" - -load Gem.bin_path("thruster", "thrust") +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("thruster", "thrust") diff --git a/class_members.json b/class_members.json index 733560ef03..26336310b0 100755 --- a/class_members.json +++ b/class_members.json @@ -1,413 +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" - } - } -] +[ + { + "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" + } + } +] diff --git a/classes.json b/classes.json index c42c269750..c3326609e5 100755 --- a/classes.json +++ b/classes.json @@ -1,29 +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" - } - } -] +[ + { + "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" + } + } +] diff --git a/config.ru b/config.ru index 4a3c09a688..fa780a6cf3 100644 --- a/config.ru +++ b/config.ru @@ -1,6 +1,6 @@ -# This file is used by Rack-based servers to start the application. - -require_relative "config/environment" - -run Rails.application -Rails.application.load_server +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/config/application.rb b/config/application.rb index 47d380417a..5c819f666e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,27 +1,27 @@ -require_relative "boot" - -require "rails/all" - -# Require the gems listed in Gemfile, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(*Rails.groups) - -module ProjetoCamaar - class Application < Rails::Application - # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 8.0 - - # Please, add to the `ignore` list any other `lib` subdirectories that do - # not contain `.rb` files, or that should not be reloaded or eager loaded. - # Common ones are `templates`, `generators`, or `middleware`, for example. - config.autoload_lib(ignore: %w[assets tasks]) - - # Configuration for the application, engines, and railties goes here. - # - # These settings can be overridden in specific environments using the files - # in config/environments, which are processed later. - # - # config.time_zone = "Central Time (US & Canada)" - # config.eager_load_paths << Rails.root.join("extras") - end -end +require_relative "boot" + +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module ProjetoCamaar + class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 8.0 + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + end +end diff --git a/config/boot.rb b/config/boot.rb index 988a5ddc46..9fa0430dc4 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,4 +1,4 @@ -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) - -require "bundler/setup" # Set up gems listed in the Gemfile. -require "bootsnap/setup" # Speed up boot time by caching expensive operations. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml index b9adc5aa3a..eedcefa720 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -1,17 +1,17 @@ -# Async adapter only works within the same process, so for manually triggering cable updates from a console, -# and seeing results in the browser, you must do so from the web console (running inside the dev process), -# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view -# to make the web console appear. -development: - adapter: async - -test: - adapter: test - -production: - adapter: solid_cable - connects_to: - database: - writing: cable - polling_interval: 0.1.seconds - message_retention: 1.day +# Async adapter only works within the same process, so for manually triggering cable updates from a console, +# and seeing results in the browser, you must do so from the web console (running inside the dev process), +# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view +# to make the web console appear. +development: + adapter: async + +test: + adapter: test + +production: + adapter: solid_cable + connects_to: + database: + writing: cable + polling_interval: 0.1.seconds + message_retention: 1.day diff --git a/config/cache.yml b/config/cache.yml index 19d490843b..2dee2eae6c 100644 --- a/config/cache.yml +++ b/config/cache.yml @@ -1,16 +1,16 @@ -default: &default - store_options: - # Cap age of oldest cache entry to fulfill retention policies - # max_age: <%= 60.days.to_i %> - max_size: <%= 256.megabytes %> - namespace: <%= Rails.env %> - -development: - <<: *default - -test: - <<: *default - -production: - database: cache - <<: *default +default: &default + store_options: + # Cap age of oldest cache entry to fulfill retention policies + # max_age: <%= 60.days.to_i %> + max_size: <%= 256.megabytes %> + namespace: <%= Rails.env %> + +development: + <<: *default + +test: + <<: *default + +production: + database: cache + <<: *default diff --git a/config/cucumber.yml b/config/cucumber.yml index 47a4663ae2..5a890bc7c5 100644 --- a/config/cucumber.yml +++ b/config/cucumber.yml @@ -1,8 +1,8 @@ -<% -rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" -rerun = rerun.strip.gsub /\s/, ' ' -rerun_opts = rerun.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" -std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags 'not @wip'" -%> -default: <%= std_opts %> features -rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags 'not @wip' +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun = rerun.strip.gsub /\s/, ' ' +rerun_opts = rerun.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags 'not @wip'" +%> +default: <%= std_opts %> features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags 'not @wip' diff --git a/config/database.yml b/config/database.yml index 2640cb5f30..729cfaf7a3 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,41 +1,41 @@ -# SQLite. Versions 3.8.0 and up are supported. -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem "sqlite3" -# -default: &default - adapter: sqlite3 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - timeout: 5000 - -development: - <<: *default - database: storage/development.sqlite3 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <<: *default - database: storage/test.sqlite3 - - -# Store production database in the storage/ directory, which by default -# is mounted as a persistent Docker volume in config/deploy.yml. -production: - primary: - <<: *default - database: storage/production.sqlite3 - cache: - <<: *default - database: storage/production_cache.sqlite3 - migrations_paths: db/cache_migrate - queue: - <<: *default - database: storage/production_queue.sqlite3 - migrations_paths: db/queue_migrate - cable: - <<: *default - database: storage/production_cable.sqlite3 - migrations_paths: db/cable_migrate +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: storage/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: storage/test.sqlite3 + + +# Store production database in the storage/ directory, which by default +# is mounted as a persistent Docker volume in config/deploy.yml. +production: + primary: + <<: *default + database: storage/production.sqlite3 + cache: + <<: *default + database: storage/production_cache.sqlite3 + migrations_paths: db/cache_migrate + queue: + <<: *default + database: storage/production_queue.sqlite3 + migrations_paths: db/queue_migrate + cable: + <<: *default + database: storage/production_cable.sqlite3 + migrations_paths: db/cable_migrate diff --git a/config/deploy.yml b/config/deploy.yml index 4a5018889d..13f9b9d453 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -1,116 +1,116 @@ -# Name of your application. Used to uniquely configure containers. -service: projeto_camaar - -# Name of the container image. -image: your-user/projeto_camaar - -# Deploy to these servers. -servers: - web: - - 192.168.0.1 - # job: - # hosts: - # - 192.168.0.1 - # cmd: bin/jobs - -# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. -# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer. -# -# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. -proxy: - ssl: true - host: app.example.com - -# Credentials for your image host. -registry: - # Specify the registry server, if you're not using Docker Hub - # server: registry.digitalocean.com / ghcr.io / ... - username: your-user - - # Always use an access token rather than real password when possible. - password: - - KAMAL_REGISTRY_PASSWORD - -# Inject ENV variables into containers (secrets come from .kamal/secrets). -env: - secret: - - RAILS_MASTER_KEY - clear: - # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. - # When you start using multiple servers, you should split out job processing to a dedicated machine. - SOLID_QUEUE_IN_PUMA: true - - # Set number of processes dedicated to Solid Queue (default: 1) - # JOB_CONCURRENCY: 3 - - # Set number of cores available to the application on each server (default: 1). - # WEB_CONCURRENCY: 2 - - # Match this to any external database server to configure Active Record correctly - # Use projeto_camaar-db for a db accessory server on same machine via local kamal docker network. - # DB_HOST: 192.168.0.2 - - # Log everything from Rails - # RAILS_LOG_LEVEL: debug - -# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: -# "bin/kamal logs -r job" will tail logs from the first server in the job section. -aliases: - console: app exec --interactive --reuse "bin/rails console" - shell: app exec --interactive --reuse "bash" - logs: app logs -f - dbc: app exec --interactive --reuse "bin/rails dbconsole" - - -# Use a persistent storage volume for sqlite database files and local Active Storage files. -# Recommended to change this to a mounted volume path that is backed up off server. -volumes: - - "projeto_camaar_storage:/rails/storage" - - -# Bridge fingerprinted assets, like JS and CSS, between versions to avoid -# hitting 404 on in-flight requests. Combines all files from new and old -# version inside the asset_path. -asset_path: /rails/public/assets - -# Configure the image builder. -builder: - arch: amd64 - - # # Build image via remote server (useful for faster amd64 builds on arm64 computers) - # remote: ssh://docker@docker-builder-server - # - # # Pass arguments and secrets to the Docker build process - # args: - # RUBY_VERSION: ruby-3.4.7 - # secrets: - # - GITHUB_TOKEN - # - RAILS_MASTER_KEY - -# Use a different ssh user than root -# ssh: -# user: app - -# Use accessory services (secrets come from .kamal/secrets). -# accessories: -# db: -# image: mysql:8.0 -# host: 192.168.0.2 -# # Change to 3306 to expose port to the world instead of just local network. -# port: "127.0.0.1:3306:3306" -# env: -# clear: -# MYSQL_ROOT_HOST: '%' -# secret: -# - MYSQL_ROOT_PASSWORD -# files: -# - config/mysql/production.cnf:/etc/mysql/my.cnf -# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql -# directories: -# - data:/var/lib/mysql -# redis: -# image: redis:7.0 -# host: 192.168.0.2 -# port: 6379 -# directories: -# - data:/data +# Name of your application. Used to uniquely configure containers. +service: projeto_camaar + +# Name of the container image. +image: your-user/projeto_camaar + +# Deploy to these servers. +servers: + web: + - 192.168.0.1 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs + +# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. +# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer. +# +# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. +proxy: + ssl: true + host: app.example.com + +# Credentials for your image host. +registry: + # Specify the registry server, if you're not using Docker Hub + # server: registry.digitalocean.com / ghcr.io / ... + username: your-user + + # Always use an access token rather than real password when possible. + password: + - KAMAL_REGISTRY_PASSWORD + +# Inject ENV variables into containers (secrets come from .kamal/secrets). +env: + secret: + - RAILS_MASTER_KEY + clear: + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + SOLID_QUEUE_IN_PUMA: true + + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # Use projeto_camaar-db for a db accessory server on same machine via local kamal docker network. + # DB_HOST: 192.168.0.2 + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. +aliases: + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole" + + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +volumes: + - "projeto_camaar_storage:/rails/storage" + + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +asset_path: /rails/public/assets + +# Configure the image builder. +builder: + arch: amd64 + + # # Build image via remote server (useful for faster amd64 builds on arm64 computers) + # remote: ssh://docker@docker-builder-server + # + # # Pass arguments and secrets to the Docker build process + # args: + # RUBY_VERSION: ruby-3.4.7 + # secrets: + # - GITHUB_TOKEN + # - RAILS_MASTER_KEY + +# Use a different ssh user than root +# ssh: +# user: app + +# Use accessory services (secrets come from .kamal/secrets). +# accessories: +# db: +# image: mysql:8.0 +# host: 192.168.0.2 +# # Change to 3306 to expose port to the world instead of just local network. +# port: "127.0.0.1:3306:3306" +# env: +# clear: +# MYSQL_ROOT_HOST: '%' +# secret: +# - MYSQL_ROOT_PASSWORD +# files: +# - config/mysql/production.cnf:/etc/mysql/my.cnf +# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql +# directories: +# - data:/var/lib/mysql +# redis: +# image: redis:7.0 +# host: 192.168.0.2 +# port: 6379 +# directories: +# - data:/data diff --git a/config/environment.rb b/config/environment.rb index cac5315775..df9ac347b7 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,5 @@ -# Load the Rails application. -require_relative "application" - -# Initialize the Rails application. -Rails.application.initialize! +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 1a0bf09e40..e7934578f1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,76 +1,76 @@ -require "active_support/core_ext/integer/time" - -Rails.application.configure do - # Configure 'rails notes' to inspect Cucumber files - config.annotations.register_directories('features') - config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } - - # Settings specified here will take precedence over those in config/application.rb. - - # Make code changes take effect immediately without server restart. - config.enable_reloading = true - - # Do not eager load code on boot. - config.eager_load = false - - # Show full error reports. - config.consider_all_requests_local = true - - # Enable server timing. - config.server_timing = true - - # Enable/disable Action Controller caching. By default Action Controller caching is disabled. - # Run rails dev:cache to toggle Action Controller caching. - if Rails.root.join("tmp/caching-dev.txt").exist? - config.action_controller.perform_caching = true - config.action_controller.enable_fragment_cache_logging = true - config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } - else - config.action_controller.perform_caching = false - end - - # Change to :null_store to avoid any caching. - config.cache_store = :memory_store - - # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local - - # Don't care if the mailer can't send. - config.action_mailer.raise_delivery_errors = false - - # Make template changes take effect immediately. - config.action_mailer.perform_caching = false - - # Set localhost to be used by links generated in mailer templates. - config.action_mailer.default_url_options = { host: "localhost", port: 3000 } - - # Print deprecation notices to the Rails logger. - config.active_support.deprecation = :log - - # Raise an error on page load if there are pending migrations. - config.active_record.migration_error = :page_load - - # Highlight code that triggered database queries in logs. - config.active_record.verbose_query_logs = true - - # Append comments with runtime information tags to SQL queries in logs. - config.active_record.query_log_tags_enabled = true - - # Highlight code that enqueued background job in logs. - config.active_job.verbose_enqueue_logs = true - - # Raises error for missing translations. - # config.i18n.raise_on_missing_translations = true - - # Annotate rendered view with file names. - config.action_view.annotate_rendered_view_with_filenames = true - - # Uncomment if you wish to allow Action Cable access from any origin. - # config.action_cable.disable_request_forgery_protection = true - - # Raise error when a before_action's only/except options reference missing actions. - config.action_controller.raise_on_missing_callback_actions = true - - # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. - # config.generators.apply_rubocop_autocorrect_after_generate! -end +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Configure 'rails notes' to inspect Cucumber files + config.annotations.register_directories('features') + config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + + # Settings specified here will take precedence over those in config/application.rb. + + # Make code changes take effect immediately without server restart. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Enable/disable Action Controller caching. By default Action Controller caching is disabled. + # Run rails dev:cache to toggle Action Controller caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + end + + # Change to :null_store to avoid any caching. + config.cache_store = :memory_store + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Make template changes take effect immediately. + config.action_mailer.perform_caching = false + + # Set localhost to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "localhost", port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! +end diff --git a/config/environments/production.rb b/config/environments/production.rb index bdcd01d1bf..a99211e546 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,90 +1,90 @@ -require "active_support/core_ext/integer/time" - -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.enable_reloading = false - - # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). - config.eager_load = true - - # Full error reports are disabled. - config.consider_all_requests_local = false - - # Turn on fragment caching in view templates. - config.action_controller.perform_caching = true - - # Cache assets for far-future expiry since they are all digest stamped. - config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.asset_host = "http://assets.example.com" - - # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local - - # Assume all access to the app is happening through a SSL-terminating reverse proxy. - config.assume_ssl = true - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true - - # Skip http-to-https redirect for the default health check endpoint. - # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } - - # Log to STDOUT with the current request id as a default log tag. - config.log_tags = [ :request_id ] - config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) - - # Change to "debug" to log everything (including potentially personally-identifiable information!) - config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") - - # Prevent health checks from clogging up the logs. - config.silence_healthcheck_path = "/up" - - # Don't log any deprecations. - config.active_support.report_deprecations = false - - # Replace the default in-process memory cache store with a durable alternative. - config.cache_store = :solid_cache_store - - # Replace the default in-process and non-durable queuing backend for Active Job. - config.active_job.queue_adapter = :solid_queue - config.solid_queue.connects_to = { database: { writing: :queue } } - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Set host to be used by links generated in mailer templates. - config.action_mailer.default_url_options = { host: "example.com" } - - # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. - # config.action_mailer.smtp_settings = { - # user_name: Rails.application.credentials.dig(:smtp, :user_name), - # password: Rails.application.credentials.dig(:smtp, :password), - # address: "smtp.example.com", - # port: 587, - # authentication: :plain - # } - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true - - # Do not dump schema after migrations. - config.active_record.dump_schema_after_migration = false - - # Only use :id for inspections in production. - config.active_record.attributes_for_inspect = [ :id ] - - # Enable DNS rebinding protection and other `Host` header attacks. - # config.hosts = [ - # "example.com", # Allow requests from example.com - # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` - # ] - # - # Skip DNS rebinding protection for the default health check endpoint. - # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } -end +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). + config.eager_load = true + + # Full error reports are disabled. + config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. + config.action_controller.perform_caching = true + + # Cache assets for far-future expiry since they are all digest stamped. + config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" } + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Skip http-to-https redirect for the default health check endpoint. + # config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } + + # Log to STDOUT with the current request id as a default log tag. + config.log_tags = [ :request_id ] + config.logger = ActiveSupport::TaggedLogging.logger(STDOUT) + + # Change to "debug" to log everything (including potentially personally-identifiable information!) + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = "/up" + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Replace the default in-process memory cache store with a durable alternative. + config.cache_store = :solid_cache_store + + # Replace the default in-process and non-durable queuing backend for Active Job. + config.active_job.queue_adapter = :solid_queue + config.solid_queue.connects_to = { database: { writing: :queue } } + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit. + # config.action_mailer.smtp_settings = { + # user_name: Rails.application.credentials.dig(:smtp, :user_name), + # password: Rails.application.credentials.dig(:smtp, :password), + # address: "smtp.example.com", + # port: 587, + # authentication: :plain + # } + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [ :id ] + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/config/environments/test.rb b/config/environments/test.rb index e6b5c1b020..1cd799ca8a 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,57 +1,57 @@ -# The test environment is used exclusively to run your application's -# test suite. You never need to work with it otherwise. Remember that -# your test database is "scratch space" for the test suite and is wiped -# and recreated between test runs. Don't rely on the data there! - -Rails.application.configure do - # Configure 'rails notes' to inspect Cucumber files - config.annotations.register_directories('features') - config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } - - # Settings specified here will take precedence over those in config/application.rb. - - # While tests run files are not watched, reloading is not necessary. - config.enable_reloading = false - - # Eager loading loads your entire application. When running a single test locally, - # this is usually not necessary, and can slow down your test suite. However, it's - # recommended that you enable it in continuous integration systems to ensure eager - # loading is working properly before deploying your code. - config.eager_load = ENV["CI"].present? - - # Configure public file server for tests with cache-control for performance. - config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } - - # Show full error reports. - config.consider_all_requests_local = true - config.cache_store = :null_store - - # Render exception templates for rescuable exceptions and raise for other exceptions. - config.action_dispatch.show_exceptions = :rescuable - - # Disable request forgery protection in test environment. - config.action_controller.allow_forgery_protection = false - - # Store uploaded files on the local file system in a temporary directory. - config.active_storage.service = :test - - # Tell Action Mailer not to deliver emails to the real world. - # The :test delivery method accumulates sent emails in the - # ActionMailer::Base.deliveries array. - config.action_mailer.delivery_method = :test - - # Set host to be used by links generated in mailer templates. - config.action_mailer.default_url_options = { host: "example.com" } - - # Print deprecation notices to the stderr. - config.active_support.deprecation = :stderr - - # Raises error for missing translations. - # config.i18n.raise_on_missing_translations = true - - # Annotate rendered view with file names. - # config.action_view.annotate_rendered_view_with_filenames = true - - # Raise error when a before_action's only/except options reference missing actions. - config.action_controller.raise_on_missing_callback_actions = true -end +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Configure 'rails notes' to inspect Cucumber files + config.annotations.register_directories('features') + config.annotations.register_extensions('feature') { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with cache-control for performance. + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + + # Show full error reports. + config.consider_all_requests_local = true + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Set host to be used by links generated in mailer templates. + config.action_mailer.default_url_options = { host: "example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/config/importmap.rb b/config/importmap.rb index 1f3c43482a..e9a3255b9e 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -1,11 +1,11 @@ -# Pin npm packages by running ./bin/importmap - -pin "application" -pin "@hotwired/turbo-rails", to: "turbo.min.js" -pin "@hotwired/stimulus", to: "stimulus.min.js" -pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" -pin_all_from "app/javascript/controllers", under: "controllers" - -pin "application", preload: true -pin_all_from "app/javascript/controllers", under: "controllers" - +# Pin npm packages by running ./bin/importmap + +pin "application" +pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@hotwired/stimulus", to: "stimulus.min.js" +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin_all_from "app/javascript/controllers", under: "controllers" + +pin "application", preload: true +pin_all_from "app/javascript/controllers", under: "controllers" + diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 487324424f..681584803b 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -1,7 +1,7 @@ -# Be sure to restart your server when you modify this file. - -# Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = "1.0" - -# Add additional assets to the asset load path. -# Rails.application.config.assets.paths << Emoji.images_path +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index b3076b38fe..669fa428e7 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -1,25 +1,25 @@ -# Be sure to restart your server when you modify this file. - -# Define an application-wide content security policy. -# See the Securing Rails Applications Guide for more information: -# https://guides.rubyonrails.org/security.html#content-security-policy-header - -# Rails.application.configure do -# config.content_security_policy do |policy| -# policy.default_src :self, :https -# policy.font_src :self, :https, :data -# policy.img_src :self, :https, :data -# policy.object_src :none -# policy.script_src :self, :https -# policy.style_src :self, :https -# # Specify URI for violation reports -# # policy.report_uri "/csp-violation-report-endpoint" -# end -# -# # Generate session nonces for permitted importmap, inline scripts, and inline styles. -# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } -# config.content_security_policy_nonce_directives = %w(script-src style-src) -# -# # Report violations without enforcing the policy. -# # config.content_security_policy_report_only = true -# end +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 6a80e52d1e..08fe9efe75 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -1,313 +1,313 @@ -# frozen_string_literal: true - -# Assuming you have not yet modified this file, each configuration option below -# is set to its default value. Note that some are commented out while others -# are not: uncommented lines are intended to protect your configuration from -# breaking changes in upgrades (i.e., in the event that future versions of -# Devise change the default values for those options). -# -# Use this hook to configure devise mailer, warden hooks and so forth. -# Many of these configuration options can be set straight in your model. -Devise.setup do |config| - # The secret key used by Devise. Devise uses this key to generate - # random tokens. Changing this key will render invalid all existing - # confirmation, reset password and unlock tokens in the database. - # Devise will use the `secret_key_base` as its `secret_key` - # by default. You can change it below and use your own secret key. - # config.secret_key = '2ad97e09e6fff98164473ba02c88057fcf40f900ac26b54ead2301614d1f82e35cd938a1bdfdab1e8ab7734e87e09abd5a56219dc0e6d53055aa9995907d7f5a' - - # ==> Controller configuration - # Configure the parent class to the devise controllers. - # config.parent_controller = 'DeviseController' - - # ==> Mailer Configuration - # Configure the e-mail address which will be shown in Devise::Mailer, - # note that it will be overwritten if you use your own mailer class - # with default "from" parameter. - config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' - - # Configure the class responsible to send e-mails. - # config.mailer = 'Devise::Mailer' - - # Configure the parent class responsible to send e-mails. - # config.parent_mailer = 'ActionMailer::Base' - - # ==> ORM configuration - # Load and configure the ORM. Supports :active_record (default) and - # :mongoid (bson_ext recommended) by default. Other ORMs may be - # available as additional gems. - require 'devise/orm/active_record' - - # ==> Configuration for any authentication mechanism - # Configure which keys are used when authenticating a user. The default is - # just :email. You can configure it to use [:username, :subdomain], so for - # authenticating a user, both parameters are required. Remember that those - # parameters are used only when authenticating and not when retrieving from - # session. If you need permissions, you should implement that in a before filter. - # You can also supply a hash where the value is a boolean determining whether - # or not authentication should be aborted when the value is not present. - # config.authentication_keys = [:email] - - # Configure parameters from the request object used for authentication. Each entry - # given should be a request method and it will automatically be passed to the - # find_for_authentication method and considered in your model lookup. For instance, - # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. - # The same considerations mentioned for authentication_keys also apply to request_keys. - # config.request_keys = [] - - # Configure which authentication keys should be case-insensitive. - # These keys will be downcased upon creating or modifying a user and when used - # to authenticate or find a user. Default is :email. - config.case_insensitive_keys = [:email] - - # Configure which authentication keys should have whitespace stripped. - # These keys will have whitespace before and after removed upon creating or - # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [:email] - - # Tell if authentication through request.params is enabled. True by default. - # It can be set to an array that will enable params authentication only for the - # given strategies, for example, `config.params_authenticatable = [:database]` will - # enable it only for database (email + password) authentication. - # config.params_authenticatable = true - - # Tell if authentication through HTTP Auth is enabled. False by default. - # It can be set to an array that will enable http authentication only for the - # given strategies, for example, `config.http_authenticatable = [:database]` will - # enable it only for database authentication. - # For API-only applications to support authentication "out-of-the-box", you will likely want to - # enable this with :database unless you are using a custom strategy. - # The supported strategies are: - # :database = Support basic authentication with authentication key + password - # config.http_authenticatable = false - - # If 401 status code should be returned for AJAX requests. True by default. - # config.http_authenticatable_on_xhr = true - - # The realm used in Http Basic Authentication. 'Application' by default. - # config.http_authentication_realm = 'Application' - - # It will change confirmation, password recovery and other workflows - # to behave the same regardless if the e-mail provided was right or wrong. - # Does not affect registerable. - # config.paranoid = true - - # By default Devise will store the user in session. You can skip storage for - # particular strategies by setting this option. - # Notice that if you are skipping storage for all authentication paths, you - # may want to disable generating routes to Devise's sessions controller by - # passing skip: :sessions to `devise_for` in your config/routes.rb - config.skip_session_storage = [:http_auth] - - # By default, Devise cleans up the CSRF token on authentication to - # avoid CSRF token fixation attacks. This means that, when using AJAX - # requests for sign in and sign up, you need to get a new CSRF token - # from the server. You can disable this option at your own risk. - # config.clean_up_csrf_token_on_authentication = true - - # When false, Devise will not attempt to reload routes on eager load. - # This can reduce the time taken to boot the app but if your application - # requires the Devise mappings to be loaded during boot time the application - # won't boot properly. - # config.reload_routes = true - - # ==> Configuration for :database_authenticatable - # For bcrypt, this is the cost for hashing the password and defaults to 12. If - # using other algorithms, it sets how many times you want the password to be hashed. - # The number of stretches used for generating the hashed password are stored - # with the hashed password. This allows you to change the stretches without - # invalidating existing passwords. - # - # Limiting the stretches to just one in testing will increase the performance of - # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use - # a value less than 10 in other environments. Note that, for bcrypt (the default - # algorithm), the cost increases exponentially with the number of stretches (e.g. - # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). - config.stretches = Rails.env.test? ? 1 : 12 - - # Set up a pepper to generate the hashed password. - # config.pepper = 'b1bb2e7e71e5d6e712355e53017528d30755ba9f83596f33463863b9b1d7eafa3403aa09e95983e6daef3c80f6e6be03d7e3ca2d5c8250b34dad18b4f1d1dba5' - - # Send a notification to the original email when the user's email is changed. - # config.send_email_changed_notification = false - - # Send a notification email when the user's password is changed. - # config.send_password_change_notification = false - - # ==> Configuration for :confirmable - # A period that the user is allowed to access the website even without - # confirming their account. For instance, if set to 2.days, the user will be - # able to access the website for two days without confirming their account, - # access will be blocked just in the third day. - # You can also set it to nil, which will allow the user to access the website - # without confirming their account. - # Default is 0.days, meaning the user cannot access the website without - # confirming their account. - # config.allow_unconfirmed_access_for = 2.days - - # A period that the user is allowed to confirm their account before their - # token becomes invalid. For example, if set to 3.days, the user can confirm - # their account within 3 days after the mail was sent, but on the fourth day - # their account can't be confirmed with the token any more. - # Default is nil, meaning there is no restriction on how long a user can take - # before confirming their account. - # config.confirm_within = 3.days - - # If true, requires any email changes to be confirmed (exactly the same way as - # initial account confirmation) to be applied. Requires additional unconfirmed_email - # db field (see migrations). Until confirmed, new email is stored in - # unconfirmed_email column, and copied to email column on successful confirmation. - config.reconfirmable = true - - # Defines which key will be used when confirming an account - # config.confirmation_keys = [:email] - - # ==> Configuration for :rememberable - # The time the user will be remembered without asking for credentials again. - # config.remember_for = 2.weeks - - # Invalidates all the remember me tokens when the user signs out. - config.expire_all_remember_me_on_sign_out = true - - # If true, extends the user's remember period when remembered via cookie. - # config.extend_remember_period = false - - # Options to be passed to the created cookie. For instance, you can set - # secure: true in order to force SSL only cookies. - # config.rememberable_options = {} - - # ==> Configuration for :validatable - # Range for password length. - config.password_length = 6..128 - - # Email regex used to validate email formats. It simply asserts that - # one (and only one) @ exists in the given string. This is mainly - # to give user feedback and not to assert the e-mail validity. - config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ - - # ==> Configuration for :timeoutable - # The time you want to timeout the user session without activity. After this - # time the user will be asked for credentials again. Default is 30 minutes. - # config.timeout_in = 30.minutes - - # ==> Configuration for :lockable - # Defines which strategy will be used to lock an account. - # :failed_attempts = Locks an account after a number of failed attempts to sign in. - # :none = No lock strategy. You should handle locking by yourself. - # config.lock_strategy = :failed_attempts - - # Defines which key will be used when locking and unlocking an account - # config.unlock_keys = [:email] - - # Defines which strategy will be used to unlock an account. - # :email = Sends an unlock link to the user email - # :time = Re-enables login after a certain amount of time (see :unlock_in below) - # :both = Enables both strategies - # :none = No unlock strategy. You should handle unlocking by yourself. - # config.unlock_strategy = :both - - # Number of authentication tries before locking an account if lock_strategy - # is failed attempts. - # config.maximum_attempts = 20 - - # Time interval to unlock the account if :time is enabled as unlock_strategy. - # config.unlock_in = 1.hour - - # Warn on the last attempt before the account is locked. - # config.last_attempt_warning = true - - # ==> Configuration for :recoverable - # - # Defines which key will be used when recovering the password for an account - # config.reset_password_keys = [:email] - - # Time interval you can reset your password with a reset password key. - # Don't put a too small interval or your users won't have the time to - # change their passwords. - config.reset_password_within = 6.hours - - # When set to false, does not sign a user in automatically after their password is - # reset. Defaults to true, so a user is signed in automatically after a reset. - # config.sign_in_after_reset_password = true - - # ==> Configuration for :encryptable - # Allow you to use another hashing or encryption algorithm besides bcrypt (default). - # You can use :sha1, :sha512 or algorithms from others authentication tools as - # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 - # for default behavior) and :restful_authentication_sha1 (then you should set - # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). - # - # Require the `devise-encryptable` gem when using anything other than bcrypt - # config.encryptor = :sha512 - - # ==> Scopes configuration - # Turn scoped views on. Before rendering "sessions/new", it will first check for - # "users/sessions/new". It's turned off by default because it's slower if you - # are using only default views. - # config.scoped_views = false - - # Configure the default scope given to Warden. By default it's the first - # devise role declared in your routes (usually :user). - # config.default_scope = :user - - # Set this configuration to false if you want /users/sign_out to sign out - # only the current scope. By default, Devise signs out all scopes. - # config.sign_out_all_scopes = true - - # ==> Navigation configuration - # Lists the formats that should be treated as navigational. Formats like - # :html should redirect to the sign in page when the user does not have - # access, but formats like :xml or :json, should return 401. - # - # If you have any extra navigational formats, like :iphone or :mobile, you - # should add them to the navigational formats lists. - # - # The "*/*" below is required to match Internet Explorer requests. - # config.navigational_formats = ['*/*', :html, :turbo_stream] - - # The default HTTP method used to sign out a resource. Default is :delete. - config.sign_out_via = :delete - - # ==> OmniAuth - # Add a new OmniAuth provider. Check the wiki for more information on setting - # up on your models and hooks. - # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' - - # ==> Warden configuration - # If you want to use other strategies, that are not supported by Devise, or - # change the failure app, you can configure them inside the config.warden block. - # - # config.warden do |manager| - # manager.intercept_401 = false - # manager.default_strategies(scope: :user).unshift :some_external_strategy - # end - - # ==> Mountable engine configurations - # When using Devise inside an engine, let's call it `MyEngine`, and this engine - # is mountable, there are some extra configurations to be taken into account. - # The following options are available, assuming the engine is mounted as: - # - # mount MyEngine, at: '/my_engine' - # - # The router that invoked `devise_for`, in the example above, would be: - # config.router_name = :my_engine - # - # When using OmniAuth, Devise cannot automatically set OmniAuth path, - # so you need to do it manually. For the users scope, it would be: - # config.omniauth_path_prefix = '/my_engine/users/auth' - - # ==> Hotwire/Turbo configuration - # When using Devise with Hotwire/Turbo, the http status for error responses - # and some redirects must match the following. The default in Devise for existing - # apps is `200 OK` and `302 Found` respectively, but new apps are generated with - # these new defaults that match Hotwire/Turbo behavior. - # Note: These might become the new default in future versions of Devise. - config.responder.error_status = :unprocessable_entity - config.responder.redirect_status = :see_other - - # ==> Configuration for :registerable - - # When set to false, does not sign a user in automatically after their password is - # changed. Defaults to true, so a user is signed in automatically after changing a password. - # config.sign_in_after_change_password = true -end +# frozen_string_literal: true + +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '2ad97e09e6fff98164473ba02c88057fcf40f900ac26b54ead2301614d1f82e35cd938a1bdfdab1e8ab7734e87e09abd5a56219dc0e6d53055aa9995907d7f5a' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 12. If + # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 12 + + # Set up a pepper to generate the hashed password. + # config.pepper = 'b1bb2e7e71e5d6e712355e53017528d30755ba9f83596f33463863b9b1d7eafa3403aa09e95983e6daef3c80f6e6be03d7e3ca2d5c8250b34dad18b4f1d1dba5' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html, :turbo_stream] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Hotwire/Turbo configuration + # When using Devise with Hotwire/Turbo, the http status for error responses + # and some redirects must match the following. The default in Devise for existing + # apps is `200 OK` and `302 Found` respectively, but new apps are generated with + # these new defaults that match Hotwire/Turbo behavior. + # Note: These might become the new default in future versions of Devise. + config.responder.error_status = :unprocessable_entity + config.responder.redirect_status = :see_other + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true +end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index c0b717f7ec..a9e8f11d72 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,8 +1,8 @@ -# Be sure to restart your server when you modify this file. - -# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. -# Use this to limit dissemination of sensitive information. -# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. -Rails.application.config.filter_parameters += [ - :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc -] +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +] diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 3860f659ea..25a09fb6d7 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,16 +1,16 @@ -# Be sure to restart your server when you modify this file. - -# Add new inflection rules using the following format. Inflections -# are locale specific, and you may define rules for as many different -# locales as you wish. All of these examples are active by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, "\\1en" -# inflect.singular /^(ox)en/i, "\\1" -# inflect.irregular "person", "people" -# inflect.uncountable %w( fish sheep ) -# end - -# These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym "RESTful" -# end +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 260e1c4ba6..a0ede6aa45 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -1,65 +1,65 @@ -# Additional translations at https://github.com/heartcombo/devise/wiki/I18n - -en: - devise: - confirmations: - confirmed: "Your email address has been successfully confirmed." - send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." - failure: - already_authenticated: "You are already signed in." - inactive: "Your account is not activated yet." - invalid: "Invalid %{authentication_keys} or password." - locked: "Your account is locked." - last_attempt: "You have one more attempt before your account is locked." - not_found_in_database: "Invalid %{authentication_keys} or password." - timeout: "Your session expired. Please sign in again to continue." - unauthenticated: "You need to sign in or sign up before continuing." - unconfirmed: "You have to confirm your email address before continuing." - mailer: - confirmation_instructions: - subject: "Confirmation instructions" - reset_password_instructions: - subject: "Reset password instructions" - unlock_instructions: - subject: "Unlock instructions" - email_changed: - subject: "Email Changed" - password_change: - subject: "Password Changed" - omniauth_callbacks: - failure: "Could not authenticate you from %{kind} because \"%{reason}\"." - success: "Successfully authenticated from %{kind} account." - passwords: - no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." - updated: "Your password has been changed successfully. You are now signed in." - updated_not_active: "Your password has been changed successfully." - registrations: - destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." - signed_up: "Welcome! You have signed up successfully." - signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." - signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." - signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." - updated: "Your account has been updated successfully." - updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again." - sessions: - signed_in: "Signed in successfully." - signed_out: "Signed out successfully." - already_signed_out: "Signed out successfully." - unlocks: - send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." - send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." - unlocked: "Your account has been unlocked successfully. Please sign in to continue." - errors: - messages: - already_confirmed: "was already confirmed, please try signing in" - confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" - expired: "has expired, please request a new one" - not_found: "not found" - not_locked: "was not locked" - not_saved: - one: "1 error prohibited this %{resource} from being saved:" - other: "%{count} errors prohibited this %{resource} from being saved:" +# Additional translations at https://github.com/heartcombo/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + email_changed: + subject: "Email Changed" + password_change: + subject: "Password Changed" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." + updated: "Your account has been updated successfully." + updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again." + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c349ae5e3..6894d67159 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,31 +1,31 @@ -# Files in the config/locales directory are used for internationalization and -# are automatically loaded by Rails. If you want to use locales other than -# English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t "hello" -# -# In views, this is aliased to just `t`: -# -# <%= t("hello") %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more about the API, please read the Rails Internationalization guide -# at https://guides.rubyonrails.org/i18n.html. -# -# Be aware that YAML interprets the following case-insensitive strings as -# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings -# must be quoted to be interpreted as strings. For example: -# -# en: -# "yes": yup -# enabled: "ON" - -en: - hello: "Hello world" +# Files in the config/locales directory are used for internationalization and +# are automatically loaded by Rails. If you want to use locales other than +# English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more about the API, please read the Rails Internationalization guide +# at https://guides.rubyonrails.org/i18n.html. +# +# Be aware that YAML interprets the following case-insensitive strings as +# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings +# must be quoted to be interpreted as strings. For example: +# +# en: +# "yes": yup +# enabled: "ON" + +en: + hello: "Hello world" diff --git a/config/puma.rb b/config/puma.rb index a248513b24..a6a7823f0f 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,41 +1,41 @@ -# This configuration file will be evaluated by Puma. The top-level methods that -# are invoked here are part of Puma's configuration DSL. For more information -# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. -# -# Puma starts a configurable number of processes (workers) and each process -# serves each request in a thread from an internal thread pool. -# -# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You -# should only set this value when you want to run 2 or more workers. The -# default is already 1. -# -# The ideal number of threads per worker depends both on how much time the -# application spends waiting for IO operations and on how much you wish to -# prioritize throughput over latency. -# -# As a rule of thumb, increasing the number of threads will increase how much -# traffic a given process can handle (throughput), but due to CRuby's -# Global VM Lock (GVL) it has diminishing returns and will degrade the -# response time (latency) of the application. -# -# The default is set to 3 threads as it's deemed a decent compromise between -# throughput and latency for the average Rails application. -# -# Any libraries that use a connection pool or another resource pool should -# be configured to provide at least as many connections as the number of -# threads. This includes Active Record's `pool` parameter in `database.yml`. -threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) -threads threads_count, threads_count - -# Specifies the `port` that Puma will listen on to receive requests; default is 3000. -port ENV.fetch("PORT", 3000) - -# Allow puma to be restarted by `bin/rails restart` command. -plugin :tmp_restart - -# Run the Solid Queue supervisor inside of Puma for single-server deployments -plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] - -# Specify the PID file. Defaults to tmp/pids/server.pid in development. -# In other environments, only set the PID file if requested. -pidfile ENV["PIDFILE"] if ENV["PIDFILE"] +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. +# +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You +# should only set this value when you want to run 2 or more workers. The +# default is already 1. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Run the Solid Queue supervisor inside of Puma for single-server deployments +plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/config/queue.yml b/config/queue.yml index 9eace59c41..4dbaa79c7b 100644 --- a/config/queue.yml +++ b/config/queue.yml @@ -1,18 +1,18 @@ -default: &default - dispatchers: - - polling_interval: 1 - batch_size: 500 - workers: - - queues: "*" - threads: 3 - processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> - polling_interval: 0.1 - -development: - <<: *default - -test: - <<: *default - -production: - <<: *default +default: &default + dispatchers: + - polling_interval: 1 + batch_size: 500 + workers: + - queues: "*" + threads: 3 + processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> + polling_interval: 0.1 + +development: + <<: *default + +test: + <<: *default + +production: + <<: *default diff --git a/config/recurring.yml b/config/recurring.yml index b4207f9b07..e22a760473 100644 --- a/config/recurring.yml +++ b/config/recurring.yml @@ -1,15 +1,15 @@ -# examples: -# periodic_cleanup: -# class: CleanSoftDeletedRecordsJob -# queue: background -# args: [ 1000, { batch_size: 500 } ] -# schedule: every hour -# periodic_cleanup_with_command: -# command: "SoftDeletedRecord.due.delete_all" -# priority: 2 -# schedule: at 5am every day - -production: - clear_solid_queue_finished_jobs: - command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)" - schedule: every hour at minute 12 +# examples: +# periodic_cleanup: +# class: CleanSoftDeletedRecordsJob +# queue: background +# args: [ 1000, { batch_size: 500 } ] +# schedule: every hour +# periodic_cleanup_with_command: +# command: "SoftDeletedRecord.due.delete_all" +# priority: 2 +# schedule: at 5am every day + +production: + clear_solid_queue_finished_jobs: + command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)" + schedule: every hour at minute 12 diff --git a/config/routes.rb b/config/routes.rb index da4905938e..c350d707df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,44 +1,44 @@ -# config/routes.rb - -Rails.application.routes.draw do - devise_for :users, skip: [:registrations] - - root "home#index" - - namespace :admin do - root "dashboard#index" - resources :users - resources :imports, only: [:index] do - collection do - post :import_klasses - end - end - - # Form Templates - resources :form_templates do - resources :form_template_fields, only: [:create, :update, :destroy] - end - - # Forms - resources :forms do - member do - patch :publish - patch :close - get :view_response - end - end - end - - # Student namespace - namespace :student do - root "dashboard#index" - resources :forms, only: [:index, :show] do - member do - get :answer - post :submit_answer - end - end - end - - get "home", to: "home#index" -end +# config/routes.rb + +Rails.application.routes.draw do + devise_for :users, skip: [:registrations] + + root "home#index" + + namespace :admin do + root "dashboard#index" + resources :users + resources :imports, only: [:index] do + collection do + post :import_klasses + end + end + + # Form Templates + resources :form_templates do + resources :form_template_fields, only: [:create, :update, :destroy] + end + + # Forms + resources :forms do + member do + patch :publish + patch :close + get :view_response + end + end + end + + # Student namespace + namespace :student do + root "dashboard#index" + resources :forms, only: [:index, :show] do + member do + get :answer + post :submit_answer + end + end + end + + get "home", to: "home#index" +end diff --git a/config/storage.yml b/config/storage.yml index 4942ab6694..aafbb0cdb5 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -1,34 +1,34 @@ -test: - service: Disk - root: <%= Rails.root.join("tmp/storage") %> - -local: - service: Disk - root: <%= Rails.root.join("storage") %> - -# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 -# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> -# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket-<%= Rails.env %> - -# Remember not to checkin your GCS keyfile to a repository -# google: -# service: GCS -# project: your_project -# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> -# bucket: your_own_bucket-<%= Rails.env %> - -# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) -# microsoft: -# service: AzureStorage -# storage_account_name: your_account_name -# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> -# container: your_container_name-<%= Rails.env %> - -# mirror: -# service: Mirror -# primary: local -# mirrors: [ amazon, google, microsoft ] +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/db/cable_schema.rb b/db/cable_schema.rb index 23666604a5..78695bdc33 100644 --- a/db/cable_schema.rb +++ b/db/cable_schema.rb @@ -1,11 +1,11 @@ -ActiveRecord::Schema[7.1].define(version: 1) do - create_table "solid_cable_messages", force: :cascade do |t| - t.binary "channel", limit: 1024, null: false - t.binary "payload", limit: 536870912, null: false - t.datetime "created_at", null: false - t.integer "channel_hash", limit: 8, null: false - t.index ["channel"], name: "index_solid_cable_messages_on_channel" - t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash" - t.index ["created_at"], name: "index_solid_cable_messages_on_created_at" - end -end +ActiveRecord::Schema[7.1].define(version: 1) do + create_table "solid_cable_messages", force: :cascade do |t| + t.binary "channel", limit: 1024, null: false + t.binary "payload", limit: 536870912, null: false + t.datetime "created_at", null: false + t.integer "channel_hash", limit: 8, null: false + t.index ["channel"], name: "index_solid_cable_messages_on_channel" + t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash" + t.index ["created_at"], name: "index_solid_cable_messages_on_created_at" + end +end diff --git a/db/cache_schema.rb b/db/cache_schema.rb index 81a410d188..2fef893df4 100644 --- a/db/cache_schema.rb +++ b/db/cache_schema.rb @@ -1,12 +1,12 @@ -ActiveRecord::Schema[7.2].define(version: 1) do - create_table "solid_cache_entries", force: :cascade do |t| - t.binary "key", limit: 1024, null: false - t.binary "value", limit: 536870912, null: false - t.datetime "created_at", null: false - t.integer "key_hash", limit: 8, null: false - t.integer "byte_size", limit: 4, null: false - t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size" - t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size" - t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true - end -end +ActiveRecord::Schema[7.2].define(version: 1) do + create_table "solid_cache_entries", force: :cascade do |t| + t.binary "key", limit: 1024, null: false + t.binary "value", limit: 536870912, null: false + t.datetime "created_at", null: false + t.integer "key_hash", limit: 8, null: false + t.integer "byte_size", limit: 4, null: false + t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size" + t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size" + t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true + end +end diff --git a/db/migrate/20251207235542_devise_create_users.rb b/db/migrate/20251207235542_devise_create_users.rb index 547948148c..078e5968de 100644 --- a/db/migrate/20251207235542_devise_create_users.rb +++ b/db/migrate/20251207235542_devise_create_users.rb @@ -1,54 +1,54 @@ -# frozen_string_literal: true - - -class DeviseCreateUsers < ActiveRecord::Migration[8.0] - def change - create_table :users do |t| - ## Database authenticatable - t.string :email, null: false, default: "" - t.string :encrypted_password, null: false, default: "" - - - ## Recoverable - t.string :reset_password_token - t.datetime :reset_password_sent_at - - - ## Rememberable - t.datetime :remember_created_at - - - ## Trackable - # t.integer :sign_in_count, default: 0, null: false - # t.datetime :current_sign_in_at - # t.datetime :last_sign_in_at - # t.string :current_sign_in_ip - # t.string :last_sign_in_ip - - - ## Confirmable - # t.string :confirmation_token - # t.datetime :confirmed_at - # t.datetime :confirmation_sent_at - # t.string :unconfirmed_email # Only if using reconfirmable - - - ## Lockable - # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts - # t.string :unlock_token # Only if unlock strategy is :email or :both - # t.datetime :locked_at - - ## Custom fields - t.string :name, null: false - t.integer :role, default: 0 # 0: user, 1: admin - - t.timestamps null: false - end - - - add_index :users, :email, unique: true - add_index :users, :reset_password_token, unique: true - # add_index :users, :confirmation_token, unique: true - # add_index :users, :unlock_token, unique: true - end +# frozen_string_literal: true + + +class DeviseCreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + + ## Rememberable + t.datetime :remember_created_at + + + ## Trackable + # t.integer :sign_in_count, default: 0, null: false + # t.datetime :current_sign_in_at + # t.datetime :last_sign_in_at + # t.string :current_sign_in_ip + # t.string :last_sign_in_ip + + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + ## Custom fields + t.string :name, null: false + t.integer :role, default: 0 # 0: user, 1: admin + + t.timestamps null: false + end + + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end end \ No newline at end of file diff --git a/db/migrate/20251208005928_create_klasses.rb b/db/migrate/20251208005928_create_klasses.rb index f88ca856bf..553d20f010 100644 --- a/db/migrate/20251208005928_create_klasses.rb +++ b/db/migrate/20251208005928_create_klasses.rb @@ -1,12 +1,12 @@ -class CreateKlasses < ActiveRecord::Migration[8.0] - def change - create_table :klasses do |t| - t.string :code - t.string :name - t.string :semester - t.text :description - - t.timestamps - end - end -end +class CreateKlasses < ActiveRecord::Migration[8.0] + def change + create_table :klasses do |t| + t.string :code + t.string :name + t.string :semester + t.text :description + + t.timestamps + end + end +end diff --git a/db/migrate/20251208005929_create_class_members.rb b/db/migrate/20251208005929_create_class_members.rb index b28a347480..af8c1c5d5b 100644 --- a/db/migrate/20251208005929_create_class_members.rb +++ b/db/migrate/20251208005929_create_class_members.rb @@ -1,11 +1,11 @@ -class CreateClassMembers < ActiveRecord::Migration[8.0] - def change - create_table :class_members do |t| - t.references :user, null: false, foreign_key: true - t.references :klass, null: false, foreign_key: true - t.string :role - - t.timestamps - end - end -end +class CreateClassMembers < ActiveRecord::Migration[8.0] + def change + create_table :class_members do |t| + t.references :user, null: false, foreign_key: true + t.references :klass, null: false, foreign_key: true + t.string :role + + t.timestamps + end + end +end diff --git a/db/migrate/20251208005930_add_fields_to_users.rb b/db/migrate/20251208005930_add_fields_to_users.rb index 9512a3a320..4f38502d4c 100644 --- a/db/migrate/20251208005930_add_fields_to_users.rb +++ b/db/migrate/20251208005930_add_fields_to_users.rb @@ -1,8 +1,8 @@ -class AddFieldsToUsers < ActiveRecord::Migration[8.0] - def change - add_column :users, :matricula, :string - add_column :users, :curso, :string - add_column :users, :formacao, :string - add_column :users, :ocupacao, :string - end -end +class AddFieldsToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :matricula, :string + add_column :users, :curso, :string + add_column :users, :formacao, :string + add_column :users, :ocupacao, :string + end +end diff --git a/db/migrate/20251208031921_create_form_templates.rb b/db/migrate/20251208031921_create_form_templates.rb index 0bc2016630..a1a5518544 100644 --- a/db/migrate/20251208031921_create_form_templates.rb +++ b/db/migrate/20251208031921_create_form_templates.rb @@ -1,11 +1,11 @@ -class CreateFormTemplates < ActiveRecord::Migration[8.0] - def change - create_table :form_templates do |t| - t.string :name - t.text :description - t.references :user, null: false, foreign_key: true - - t.timestamps - end - end -end +class CreateFormTemplates < ActiveRecord::Migration[8.0] + def change + create_table :form_templates do |t| + t.string :name + t.text :description + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031922_create_form_template_fields.rb b/db/migrate/20251208031922_create_form_template_fields.rb index f25f2ab4aa..5b9ef2f249 100644 --- a/db/migrate/20251208031922_create_form_template_fields.rb +++ b/db/migrate/20251208031922_create_form_template_fields.rb @@ -1,14 +1,14 @@ -class CreateFormTemplateFields < ActiveRecord::Migration[8.0] - def change - create_table :form_template_fields do |t| - t.references :form_template, null: false, foreign_key: true - t.string :field_type - t.string :label - t.boolean :required - t.json :options - t.integer :position - - t.timestamps - end - end -end +class CreateFormTemplateFields < ActiveRecord::Migration[8.0] + def change + create_table :form_template_fields do |t| + t.references :form_template, null: false, foreign_key: true + t.string :field_type + t.string :label + t.boolean :required + t.json :options + t.integer :position + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031923_create_forms.rb b/db/migrate/20251208031923_create_forms.rb index 617a29eec5..8cb0fe86f3 100644 --- a/db/migrate/20251208031923_create_forms.rb +++ b/db/migrate/20251208031923_create_forms.rb @@ -1,14 +1,14 @@ -class CreateForms < ActiveRecord::Migration[8.0] - def change - create_table :forms do |t| - t.references :form_template, null: false, foreign_key: true - t.references :klass, null: false, foreign_key: true - t.string :title - t.text :description - t.datetime :due_date - t.integer :status - - t.timestamps - end - end -end +class CreateForms < ActiveRecord::Migration[8.0] + def change + create_table :forms do |t| + t.references :form_template, null: false, foreign_key: true + t.references :klass, null: false, foreign_key: true + t.string :title + t.text :description + t.datetime :due_date + t.integer :status + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031924_create_form_responses.rb b/db/migrate/20251208031924_create_form_responses.rb index 7edf9dd3a3..bec7f4b73c 100644 --- a/db/migrate/20251208031924_create_form_responses.rb +++ b/db/migrate/20251208031924_create_form_responses.rb @@ -1,11 +1,11 @@ -class CreateFormResponses < ActiveRecord::Migration[8.0] - def change - create_table :form_responses do |t| - t.references :form, null: false, foreign_key: true - t.references :user, null: false, foreign_key: true - t.datetime :submitted_at - - t.timestamps - end - end -end +class CreateFormResponses < ActiveRecord::Migration[8.0] + def change + create_table :form_responses do |t| + t.references :form, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + t.datetime :submitted_at + + t.timestamps + end + end +end diff --git a/db/migrate/20251208031925_create_form_answers.rb b/db/migrate/20251208031925_create_form_answers.rb index 6f8eae517a..e8eb20dc5b 100644 --- a/db/migrate/20251208031925_create_form_answers.rb +++ b/db/migrate/20251208031925_create_form_answers.rb @@ -1,11 +1,11 @@ -class CreateFormAnswers < ActiveRecord::Migration[8.0] - def change - create_table :form_answers do |t| - t.references :form_response, null: false, foreign_key: true - t.references :form_template_field, null: false, foreign_key: true - t.text :answer - - t.timestamps - end - end -end +class CreateFormAnswers < ActiveRecord::Migration[8.0] + def change + create_table :form_answers do |t| + t.references :form_response, null: false, foreign_key: true + t.references :form_template_field, null: false, foreign_key: true + t.text :answer + + t.timestamps + end + end +end diff --git a/db/queue_schema.rb b/db/queue_schema.rb index 85194b6a88..adde0582b4 100644 --- a/db/queue_schema.rb +++ b/db/queue_schema.rb @@ -1,129 +1,129 @@ -ActiveRecord::Schema[7.1].define(version: 1) do - create_table "solid_queue_blocked_executions", force: :cascade do |t| - t.bigint "job_id", null: false - t.string "queue_name", null: false - t.integer "priority", default: 0, null: false - t.string "concurrency_key", null: false - t.datetime "expires_at", null: false - t.datetime "created_at", null: false - t.index [ "concurrency_key", "priority", "job_id" ], name: "index_solid_queue_blocked_executions_for_release" - t.index [ "expires_at", "concurrency_key" ], name: "index_solid_queue_blocked_executions_for_maintenance" - t.index [ "job_id" ], name: "index_solid_queue_blocked_executions_on_job_id", unique: true - end - - create_table "solid_queue_claimed_executions", force: :cascade do |t| - t.bigint "job_id", null: false - t.bigint "process_id" - t.datetime "created_at", null: false - t.index [ "job_id" ], name: "index_solid_queue_claimed_executions_on_job_id", unique: true - t.index [ "process_id", "job_id" ], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id" - end - - create_table "solid_queue_failed_executions", force: :cascade do |t| - t.bigint "job_id", null: false - t.text "error" - t.datetime "created_at", null: false - t.index [ "job_id" ], name: "index_solid_queue_failed_executions_on_job_id", unique: true - end - - create_table "solid_queue_jobs", force: :cascade do |t| - t.string "queue_name", null: false - t.string "class_name", null: false - t.text "arguments" - t.integer "priority", default: 0, null: false - t.string "active_job_id" - t.datetime "scheduled_at" - t.datetime "finished_at" - t.string "concurrency_key" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index [ "active_job_id" ], name: "index_solid_queue_jobs_on_active_job_id" - t.index [ "class_name" ], name: "index_solid_queue_jobs_on_class_name" - t.index [ "finished_at" ], name: "index_solid_queue_jobs_on_finished_at" - t.index [ "queue_name", "finished_at" ], name: "index_solid_queue_jobs_for_filtering" - t.index [ "scheduled_at", "finished_at" ], name: "index_solid_queue_jobs_for_alerting" - end - - create_table "solid_queue_pauses", force: :cascade do |t| - t.string "queue_name", null: false - t.datetime "created_at", null: false - t.index [ "queue_name" ], name: "index_solid_queue_pauses_on_queue_name", unique: true - end - - create_table "solid_queue_processes", force: :cascade do |t| - t.string "kind", null: false - t.datetime "last_heartbeat_at", null: false - t.bigint "supervisor_id" - t.integer "pid", null: false - t.string "hostname" - t.text "metadata" - t.datetime "created_at", null: false - t.string "name", null: false - t.index [ "last_heartbeat_at" ], name: "index_solid_queue_processes_on_last_heartbeat_at" - t.index [ "name", "supervisor_id" ], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true - t.index [ "supervisor_id" ], name: "index_solid_queue_processes_on_supervisor_id" - end - - create_table "solid_queue_ready_executions", force: :cascade do |t| - t.bigint "job_id", null: false - t.string "queue_name", null: false - t.integer "priority", default: 0, null: false - t.datetime "created_at", null: false - t.index [ "job_id" ], name: "index_solid_queue_ready_executions_on_job_id", unique: true - t.index [ "priority", "job_id" ], name: "index_solid_queue_poll_all" - t.index [ "queue_name", "priority", "job_id" ], name: "index_solid_queue_poll_by_queue" - end - - create_table "solid_queue_recurring_executions", force: :cascade do |t| - t.bigint "job_id", null: false - t.string "task_key", null: false - t.datetime "run_at", null: false - t.datetime "created_at", null: false - t.index [ "job_id" ], name: "index_solid_queue_recurring_executions_on_job_id", unique: true - t.index [ "task_key", "run_at" ], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true - end - - create_table "solid_queue_recurring_tasks", force: :cascade do |t| - t.string "key", null: false - t.string "schedule", null: false - t.string "command", limit: 2048 - t.string "class_name" - t.text "arguments" - t.string "queue_name" - t.integer "priority", default: 0 - t.boolean "static", default: true, null: false - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index [ "key" ], name: "index_solid_queue_recurring_tasks_on_key", unique: true - t.index [ "static" ], name: "index_solid_queue_recurring_tasks_on_static" - end - - create_table "solid_queue_scheduled_executions", force: :cascade do |t| - t.bigint "job_id", null: false - t.string "queue_name", null: false - t.integer "priority", default: 0, null: false - t.datetime "scheduled_at", null: false - t.datetime "created_at", null: false - t.index [ "job_id" ], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true - t.index [ "scheduled_at", "priority", "job_id" ], name: "index_solid_queue_dispatch_all" - end - - create_table "solid_queue_semaphores", force: :cascade do |t| - t.string "key", null: false - t.integer "value", default: 1, null: false - t.datetime "expires_at", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index [ "expires_at" ], name: "index_solid_queue_semaphores_on_expires_at" - t.index [ "key", "value" ], name: "index_solid_queue_semaphores_on_key_and_value" - t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true - end - - add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade -end +ActiveRecord::Schema[7.1].define(version: 1) do + create_table "solid_queue_blocked_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.string "concurrency_key", null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.index [ "concurrency_key", "priority", "job_id" ], name: "index_solid_queue_blocked_executions_for_release" + t.index [ "expires_at", "concurrency_key" ], name: "index_solid_queue_blocked_executions_for_maintenance" + t.index [ "job_id" ], name: "index_solid_queue_blocked_executions_on_job_id", unique: true + end + + create_table "solid_queue_claimed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.bigint "process_id" + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_claimed_executions_on_job_id", unique: true + t.index [ "process_id", "job_id" ], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id" + end + + create_table "solid_queue_failed_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.text "error" + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_failed_executions_on_job_id", unique: true + end + + create_table "solid_queue_jobs", force: :cascade do |t| + t.string "queue_name", null: false + t.string "class_name", null: false + t.text "arguments" + t.integer "priority", default: 0, null: false + t.string "active_job_id" + t.datetime "scheduled_at" + t.datetime "finished_at" + t.string "concurrency_key" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "active_job_id" ], name: "index_solid_queue_jobs_on_active_job_id" + t.index [ "class_name" ], name: "index_solid_queue_jobs_on_class_name" + t.index [ "finished_at" ], name: "index_solid_queue_jobs_on_finished_at" + t.index [ "queue_name", "finished_at" ], name: "index_solid_queue_jobs_for_filtering" + t.index [ "scheduled_at", "finished_at" ], name: "index_solid_queue_jobs_for_alerting" + end + + create_table "solid_queue_pauses", force: :cascade do |t| + t.string "queue_name", null: false + t.datetime "created_at", null: false + t.index [ "queue_name" ], name: "index_solid_queue_pauses_on_queue_name", unique: true + end + + create_table "solid_queue_processes", force: :cascade do |t| + t.string "kind", null: false + t.datetime "last_heartbeat_at", null: false + t.bigint "supervisor_id" + t.integer "pid", null: false + t.string "hostname" + t.text "metadata" + t.datetime "created_at", null: false + t.string "name", null: false + t.index [ "last_heartbeat_at" ], name: "index_solid_queue_processes_on_last_heartbeat_at" + t.index [ "name", "supervisor_id" ], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true + t.index [ "supervisor_id" ], name: "index_solid_queue_processes_on_supervisor_id" + end + + create_table "solid_queue_ready_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_ready_executions_on_job_id", unique: true + t.index [ "priority", "job_id" ], name: "index_solid_queue_poll_all" + t.index [ "queue_name", "priority", "job_id" ], name: "index_solid_queue_poll_by_queue" + end + + create_table "solid_queue_recurring_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "task_key", null: false + t.datetime "run_at", null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_recurring_executions_on_job_id", unique: true + t.index [ "task_key", "run_at" ], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true + end + + create_table "solid_queue_recurring_tasks", force: :cascade do |t| + t.string "key", null: false + t.string "schedule", null: false + t.string "command", limit: 2048 + t.string "class_name" + t.text "arguments" + t.string "queue_name" + t.integer "priority", default: 0 + t.boolean "static", default: true, null: false + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "key" ], name: "index_solid_queue_recurring_tasks_on_key", unique: true + t.index [ "static" ], name: "index_solid_queue_recurring_tasks_on_static" + end + + create_table "solid_queue_scheduled_executions", force: :cascade do |t| + t.bigint "job_id", null: false + t.string "queue_name", null: false + t.integer "priority", default: 0, null: false + t.datetime "scheduled_at", null: false + t.datetime "created_at", null: false + t.index [ "job_id" ], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true + t.index [ "scheduled_at", "priority", "job_id" ], name: "index_solid_queue_dispatch_all" + end + + create_table "solid_queue_semaphores", force: :cascade do |t| + t.string "key", null: false + t.integer "value", default: 1, null: false + t.datetime "expires_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index [ "expires_at" ], name: "index_solid_queue_semaphores_on_expires_at" + t.index [ "key", "value" ], name: "index_solid_queue_semaphores_on_key_and_value" + t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true + end + + add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade + add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade +end diff --git a/db/seeds.rb b/db/seeds.rb index 4fbd6ed970..caebeb27d4 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,9 +1,9 @@ -# This file should ensure the existence of records required to run the application in every environment (production, -# development, test). The code here should be idempotent so that it can be executed at any point in every environment. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Example: -# -# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| -# MovieGenre.find_or_create_by!(name: genre_name) -# end +# This file should ensure the existence of records required to run the application in every environment (production, +# development, test). The code here should be idempotent so that it can be executed at any point in every environment. +# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). +# +# Example: +# +# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| +# MovieGenre.find_or_create_by!(name: genre_name) +# end diff --git a/er.pending_forms.count b/er.pending_forms.count index e6d7a0973b..1030d2abdd 100644 --- a/er.pending_forms.count +++ b/er.pending_forms.count @@ -1,21 +1,21 @@ -=> -[#, - #] +=> +[#, + #] diff --git a/features/definir_senha_cadastro.feature b/features/definir_senha_cadastro.feature index 153affd8fd..5d800450b7 100644 --- a/features/definir_senha_cadastro.feature +++ b/features/definir_senha_cadastro.feature @@ -1,40 +1,40 @@ -Feature: Definir senha após cadastro - Como Usuário - Quero definir uma senha para o meu usuário a partir do email do sistema de solicitação de cadastro - A fim de acessar o sistema - - Background: - Given que recebi um email de convite para cadastro - And o email contém um link de ativação - - Scenario: Definir senha com sucesso (feliz) - Given que clico no link de ativação do email - When eu sou redirecionado para a página de definição de senha - And eu preencho uma senha segura - And eu confirmo a senha - And eu clico em "Definir senha" - Then a minha senha deve ser criada - And eu devo receber a mensagem "Senha definida com sucesso" - And eu devo ser redirecionado para login - - Scenario: Senhas não conferem (triste) - Given que estou na página de definição de senha - When eu preencho a senha "Senha123!" - And eu confirmo com uma senha diferente "Senha456!" - And eu clico em "Definir senha" - Then o sistema deve exibir a mensagem "As senhas não conferem" - And a senha não deve ser criada - - Scenario: Senha muito fraca (triste) - Given que estou na página de definição de senha - When eu preencho uma senha fraca "123" - And eu confirmo a mesma senha - And eu clico em "Definir senha" - Then o sistema deve exibir a mensagem "Senha fraca. Use letras, números e caracteres especiais" - And a senha não deve ser criada - - Scenario: Link de ativação expirado (triste) - Given que o link de ativação expirou - When eu clico no link - Then o sistema deve exibir a mensagem "Link de ativação expirado" +Feature: Definir senha após cadastro + Como Usuário + Quero definir uma senha para o meu usuário a partir do email do sistema de solicitação de cadastro + A fim de acessar o sistema + + Background: + Given que recebi um email de convite para cadastro + And o email contém um link de ativação + + Scenario: Definir senha com sucesso (feliz) + Given que clico no link de ativação do email + When eu sou redirecionado para a página de definição de senha + And eu preencho uma senha segura + And eu confirmo a senha + And eu clico em "Definir senha" + Then a minha senha deve ser criada + And eu devo receber a mensagem "Senha definida com sucesso" + And eu devo ser redirecionado para login + + Scenario: Senhas não conferem (triste) + Given que estou na página de definição de senha + When eu preencho a senha "Senha123!" + And eu confirmo com uma senha diferente "Senha456!" + And eu clico em "Definir senha" + Then o sistema deve exibir a mensagem "As senhas não conferem" + And a senha não deve ser criada + + Scenario: Senha muito fraca (triste) + Given que estou na página de definição de senha + When eu preencho uma senha fraca "123" + And eu confirmo a mesma senha + And eu clico em "Definir senha" + Then o sistema deve exibir a mensagem "Senha fraca. Use letras, números e caracteres especiais" + And a senha não deve ser criada + + Scenario: Link de ativação expirado (triste) + Given que o link de ativação expirou + When eu clico no link + Then o sistema deve exibir a mensagem "Link de ativação expirado" And eu devo ser orientado a solicitar um novo link \ No newline at end of file diff --git a/features/exportar_relatorios_csv.feature b/features/exportar_relatorios_csv.feature index ef7f22c047..710ddb9f5b 100644 --- a/features/exportar_relatorios_csv.feature +++ b/features/exportar_relatorios_csv.feature @@ -1,34 +1,34 @@ -Feature: Baixar resultados de formulário em CSV - Como Administrador - Quero baixar um arquivo CSV contendo os resultados de um formulário - A fim de avaliar o desempenho das turmas - - Background: - Given que estou autenticado como administrador - And que existe um formulário com respostas - - Scenario: Exportar resultados com sucesso (feliz) - Given que um formulário tem respostas de participantes - When eu clico em "Exportar para CSV" - Then um arquivo CSV deve ser gerado - And o arquivo deve conter todas as respostas - And o arquivo deve estar pronto para download - - Scenario: Arquivo CSV com estrutura correta (feliz) - Given que vou exportar resultados de um formulário - When eu clico em "Exportar para CSV" - Then o arquivo deve ter colunas com: ID da resposta, Participante, Data de resposta, Respostas - And cada linha deve representar uma resposta individual - And caracteres especiais devem estar codificados corretamente - - Scenario: Exportar formulário sem respostas (triste) - Given que um formulário não possui respostas - When eu tento exportar para CSV - Then o sistema deve exibir a mensagem "Não há dados para exportar" - And nenhum arquivo deve ser criado - - Scenario: Exportar apenas respostas de uma turma específica (feliz) - Given que um formulário foi respondido por múltiplas turmas - When eu seleciono uma turma específica - And eu clico em "Exportar para CSV" +Feature: Baixar resultados de formulário em CSV + Como Administrador + Quero baixar um arquivo CSV contendo os resultados de um formulário + A fim de avaliar o desempenho das turmas + + Background: + Given que estou autenticado como administrador + And que existe um formulário com respostas + + Scenario: Exportar resultados com sucesso (feliz) + Given que um formulário tem respostas de participantes + When eu clico em "Exportar para CSV" + Then um arquivo CSV deve ser gerado + And o arquivo deve conter todas as respostas + And o arquivo deve estar pronto para download + + Scenario: Arquivo CSV com estrutura correta (feliz) + Given que vou exportar resultados de um formulário + When eu clico em "Exportar para CSV" + Then o arquivo deve ter colunas com: ID da resposta, Participante, Data de resposta, Respostas + And cada linha deve representar uma resposta individual + And caracteres especiais devem estar codificados corretamente + + Scenario: Exportar formulário sem respostas (triste) + Given que um formulário não possui respostas + When eu tento exportar para CSV + Then o sistema deve exibir a mensagem "Não há dados para exportar" + And nenhum arquivo deve ser criado + + Scenario: Exportar apenas respostas de uma turma específica (feliz) + Given que um formulário foi respondido por múltiplas turmas + When eu seleciono uma turma específica + And eu clico em "Exportar para CSV" Then o arquivo deve conter apenas as respostas da turma selecionada \ No newline at end of file diff --git a/features/formularios_discentes_docentes.feature b/features/formularios_discentes_docentes.feature index 5b58f02c5e..f26a720dd6 100644 --- a/features/formularios_discentes_docentes.feature +++ b/features/formularios_discentes_docentes.feature @@ -1,39 +1,39 @@ -Feature: Escolher criar formulário para docentes ou discentes - Como Administrador - Quero escolher criar um formulário para os docentes ou os dicentes de uma turma - A fim de avaliar o desempenho de uma matéria - - Background: - Given que estou autenticado como administrador - And que estou criando um novo formulário - - Scenario: Criar formulário para alunos (discentes) (feliz) - Given que estou na página de criar formulário - When eu seleciono um template - And eu escolho as turmas - And eu seleciono "Discentes" como tipo de avaliador - And eu clico em "Criar formulário" - Then o formulário deve ser criado apenas para alunos - And apenas alunos da turma devem receber notificação - - Scenario: Criar formulário para professores (docentes) (feliz) - Given que estou na página de criar formulário - When eu seleciono um template - And eu escolho as turmas - And eu seleciono "Docentes" como tipo de avaliador - And eu clico em "Criar formulário" - Then o formulário deve ser criado apenas para professores - And apenas professores da turma devem receber notificação - - Scenario: Avaliadores corretos veem apenas seus formulários (feliz) - Given que um formulário foi criado para "Discentes" - When um professor da turma faz login - Then ele não deve ver este formulário nos pendentes - And quando um aluno faz login, ele vê o formulário - - Scenario: Criar dois formulários diferentes para mesma turma (feliz) - Given que estou criando dois formulários para a mesma turma - When eu crio um formulário para "Discentes" - And eu crio outro formulário para "Docentes" - Then os dois formulários devem coexistir +Feature: Escolher criar formulário para docentes ou discentes + Como Administrador + Quero escolher criar um formulário para os docentes ou os dicentes de uma turma + A fim de avaliar o desempenho de uma matéria + + Background: + Given que estou autenticado como administrador + And que estou criando um novo formulário + + Scenario: Criar formulário para alunos (discentes) (feliz) + Given que estou na página de criar formulário + When eu seleciono um template + And eu escolho as turmas + And eu seleciono "Discentes" como tipo de avaliador + And eu clico em "Criar formulário" + Then o formulário deve ser criado apenas para alunos + And apenas alunos da turma devem receber notificação + + Scenario: Criar formulário para professores (docentes) (feliz) + Given que estou na página de criar formulário + When eu seleciono um template + And eu escolho as turmas + And eu seleciono "Docentes" como tipo de avaliador + And eu clico em "Criar formulário" + Then o formulário deve ser criado apenas para professores + And apenas professores da turma devem receber notificação + + Scenario: Avaliadores corretos veem apenas seus formulários (feliz) + Given que um formulário foi criado para "Discentes" + When um professor da turma faz login + Then ele não deve ver este formulário nos pendentes + And quando um aluno faz login, ele vê o formulário + + Scenario: Criar dois formulários diferentes para mesma turma (feliz) + Given que estou criando dois formulários para a mesma turma + When eu crio um formulário para "Discentes" + And eu crio outro formulário para "Docentes" + Then os dois formulários devem coexistir And cada grupo receberá apenas o formulário destinado a ele \ No newline at end of file diff --git a/features/gerenciar_turmas_departamento.feature b/features/gerenciar_turmas_departamento.feature index 25e9ee59a6..2cda84c0ba 100644 --- a/features/gerenciar_turmas_departamento.feature +++ b/features/gerenciar_turmas_departamento.feature @@ -1,32 +1,32 @@ -Feature: Gerenciar turmas apenas do departamento - Como Administrador - Quero gerenciar somente as turmas do departamento o qual eu pertenço - A fim de avaliar o desempenho das turmas no semestre atual - - Background: - Given que estou autenticado como administrador - And que pertenço ao departamento "Engenharia de Computação" - - Scenario: Visualizar apenas turmas do departamento (feliz) - Given que existem turmas de diferentes departamentos - When eu acesso a listagem de turmas - Then eu devo ver apenas as turmas do "Departamento de Engenharia de Computação" - And eu não devo ver turmas de outros departamentos - - Scenario: Criar formulário apenas para turmas do departamento (feliz) - Given que estou criando um formulário - When eu seleciono um template - Then a listagem de turmas deve mostrar apenas turmas do meu departamento - And eu não devo ter a opção de selecionar turmas de outros departamentos - - Scenario: Administrador de outro departamento vê apenas suas turmas (feliz) - Given que um outro administrador pertence ao "Departamento de Engenharia Mecânica" - When ele acessa o sistema - Then ele deve ver apenas turmas do "Departamento de Engenharia Mecânica" - And não deve ver minhas turmas - - Scenario: Impossível atualizar turma de outro departamento (triste) - Given que estou autenticado como administrador - When eu tento acessar dados de uma turma de outro departamento - Then o sistema deve retornar erro 403 (Acesso Negado) +Feature: Gerenciar turmas apenas do departamento + Como Administrador + Quero gerenciar somente as turmas do departamento o qual eu pertenço + A fim de avaliar o desempenho das turmas no semestre atual + + Background: + Given que estou autenticado como administrador + And que pertenço ao departamento "Engenharia de Computação" + + Scenario: Visualizar apenas turmas do departamento (feliz) + Given que existem turmas de diferentes departamentos + When eu acesso a listagem de turmas + Then eu devo ver apenas as turmas do "Departamento de Engenharia de Computação" + And eu não devo ver turmas de outros departamentos + + Scenario: Criar formulário apenas para turmas do departamento (feliz) + Given que estou criando um formulário + When eu seleciono um template + Then a listagem de turmas deve mostrar apenas turmas do meu departamento + And eu não devo ter a opção de selecionar turmas de outros departamentos + + Scenario: Administrador de outro departamento vê apenas suas turmas (feliz) + Given que um outro administrador pertence ao "Departamento de Engenharia Mecânica" + When ele acessa o sistema + Then ele deve ver apenas turmas do "Departamento de Engenharia Mecânica" + And não deve ver minhas turmas + + Scenario: Impossível atualizar turma de outro departamento (triste) + Given que estou autenticado como administrador + When eu tento acessar dados de uma turma de outro departamento + Then o sistema deve retornar erro 403 (Acesso Negado) And a operação não deve ser permitida \ No newline at end of file diff --git a/features/redefinir_senha.feature b/features/redefinir_senha.feature index 36e0803578..6f6e66d0a0 100644 --- a/features/redefinir_senha.feature +++ b/features/redefinir_senha.feature @@ -1,38 +1,38 @@ -Feature: Redefinir senha - Como Usuário - Quero redefinir uma senha para o meu usuário a partir do email recebido após a solicitação da troca de senha - A fim de recuperar o meu acesso ao sistema - - Background: - Given que estou na página de login - - Scenario: Solicitar redefinição de senha com sucesso (feliz) - Given que clico em "Esqueci minha senha" - When eu preencho meu email "usuario@example.com" - And eu clico em "Enviar link de recuperação" - Then um email deve ser enviado para "usuario@example.com" - And o email deve conter um link de redefinição - And eu devo ver a mensagem "Email de recuperação enviado" - - Scenario: Redefinir senha com novo link (feliz) - Given que recebi um email de recuperação de senha - And eu clico no link de recuperação - When eu sou redirecionado para a página de redefinição - And eu preencho a nova senha - And eu confirmo a nova senha - And eu clico em "Atualizar senha" - Then a minha senha deve ser atualizada - And eu devo receber a mensagem "Senha atualizada com sucesso" - - Scenario: Link de recuperação expirado (triste) - Given que o link de recuperação expirou - When eu clico no link - Then o sistema deve exibir a mensagem "Link expirado" - And eu devo ser orientado a solicitar um novo link - - Scenario: Usuário não encontrado (triste) - Given que estou na página de recuperação - When eu preencho um email inexistente "naoexisto@example.com" - And eu clico em "Enviar link de recuperação" - Then o sistema deve exibir a mensagem "Email não encontrado" +Feature: Redefinir senha + Como Usuário + Quero redefinir uma senha para o meu usuário a partir do email recebido após a solicitação da troca de senha + A fim de recuperar o meu acesso ao sistema + + Background: + Given que estou na página de login + + Scenario: Solicitar redefinição de senha com sucesso (feliz) + Given que clico em "Esqueci minha senha" + When eu preencho meu email "usuario@example.com" + And eu clico em "Enviar link de recuperação" + Then um email deve ser enviado para "usuario@example.com" + And o email deve conter um link de redefinição + And eu devo ver a mensagem "Email de recuperação enviado" + + Scenario: Redefinir senha com novo link (feliz) + Given que recebi um email de recuperação de senha + And eu clico no link de recuperação + When eu sou redirecionado para a página de redefinição + And eu preencho a nova senha + And eu confirmo a nova senha + And eu clico em "Atualizar senha" + Then a minha senha deve ser atualizada + And eu devo receber a mensagem "Senha atualizada com sucesso" + + Scenario: Link de recuperação expirado (triste) + Given que o link de recuperação expirou + When eu clico no link + Then o sistema deve exibir a mensagem "Link expirado" + And eu devo ser orientado a solicitar um novo link + + Scenario: Usuário não encontrado (triste) + Given que estou na página de recuperação + When eu preencho um email inexistente "naoexisto@example.com" + And eu clico em "Enviar link de recuperação" + Then o sistema deve exibir a mensagem "Email não encontrado" And nenhum email deve ser enviado \ No newline at end of file diff --git a/features/support/env.rb b/features/support/env.rb index 3b97d14087..27476bfb89 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,53 +1,53 @@ -# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. -# It is recommended to regenerate this file in the future when you upgrade to a -# newer version of cucumber-rails. Consider adding your own code to a new file -# instead of editing this one. Cucumber will automatically load all features/**/*.rb -# files. - - -require 'cucumber/rails' - -# By default, any exception happening in your Rails application will bubble up -# to Cucumber so that your scenario will fail. This is a different from how -# your application behaves in the production environment, where an error page will -# be rendered instead. -# -# Sometimes we want to override this default behaviour and allow Rails to rescue -# exceptions and display an error page (just like when the app is running in production). -# Typical scenarios where you want to do this is when you test your error pages. -# There are two ways to allow Rails to rescue exceptions: -# -# 1) Tag your scenario (or feature) with @allow-rescue -# -# 2) Set the value below to true. Beware that doing this globally is not -# recommended as it will mask a lot of errors for you! -# -ActionController::Base.allow_rescue = false - -# Remove/comment out the lines below if your app doesn't have a database. -# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. -begin - DatabaseCleaner.strategy = :transaction -rescue NameError - raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." -end - -# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. -# See the DatabaseCleaner documentation for details. Example: -# -# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do -# # { except: [:widgets] } may not do what you expect here -# # as Cucumber::Rails::Database.javascript_strategy overrides -# # this setting. -# DatabaseCleaner.strategy = :truncation -# end -# -# Before('not @no-txn', 'not @selenium', 'not @culerity', 'not @celerity', 'not @javascript') do -# DatabaseCleaner.strategy = :transaction -# end -# - -# Possible values are :truncation and :transaction -# The :transaction strategy is faster, but might give you threading problems. -# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature -Cucumber::Rails::Database.javascript_strategy = :truncation +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +require 'cucumber/rails' + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false + +# Remove/comment out the lines below if your app doesn't have a database. +# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. +begin + DatabaseCleaner.strategy = :transaction +rescue NameError + raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +end + +# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. +# See the DatabaseCleaner documentation for details. Example: +# +# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do +# # { except: [:widgets] } may not do what you expect here +# # as Cucumber::Rails::Database.javascript_strategy overrides +# # this setting. +# DatabaseCleaner.strategy = :truncation +# end +# +# Before('not @no-txn', 'not @selenium', 'not @culerity', 'not @celerity', 'not @javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# + +# Possible values are :truncation and :transaction +# The :transaction strategy is faster, but might give you threading problems. +# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature +Cucumber::Rails::Database.javascript_strategy = :truncation diff --git a/features/visualizar_formularios_criados.feature b/features/visualizar_formularios_criados.feature index b8bbf4823a..a9344d903a 100644 --- a/features/visualizar_formularios_criados.feature +++ b/features/visualizar_formularios_criados.feature @@ -1,31 +1,31 @@ -Feature: Visualizar formulários criados - Como Administrador - Quero visualizar os formulários criados - A fim de poder gerar um relatório a partir das respostas - - Background: - Given que estou autenticado como administrador - - Scenario: Listar todos os formulários criados (feliz) - Given que criei vários formulários - When eu acesso a seção "Meus Formulários" - Then eu devo ver uma lista de todos os formulários - And cada formulário deve mostrar: nome, template usado, turmas, status, data de criação - - Scenario: Formulário mostra quantidade de respostas (feliz) - Given que um formulário foi respondido por alguns participantes - When eu visualizo a lista de formulários - Then cada formulário deve exibir a quantidade de respostas recebidas - And eu devo conseguir comparar o progresso entre formulários - - Scenario: Acessar detalhes do formulário (feliz) - Given que existe um formulário criado - When eu clico em um formulário - Then eu devo ver os detalhes: questões, data de abertura/fechamento - And eu devo ter a opção de visualizar respostas - - Scenario: Formulários apenas do meu departamento (feliz) - Given que administro apenas um departamento - When eu acesso a seção "Meus Formulários" - Then eu devo ver apenas os formulários criados para turmas do meu departamento +Feature: Visualizar formulários criados + Como Administrador + Quero visualizar os formulários criados + A fim de poder gerar um relatório a partir das respostas + + Background: + Given que estou autenticado como administrador + + Scenario: Listar todos os formulários criados (feliz) + Given que criei vários formulários + When eu acesso a seção "Meus Formulários" + Then eu devo ver uma lista de todos os formulários + And cada formulário deve mostrar: nome, template usado, turmas, status, data de criação + + Scenario: Formulário mostra quantidade de respostas (feliz) + Given que um formulário foi respondido por alguns participantes + When eu visualizo a lista de formulários + Then cada formulário deve exibir a quantidade de respostas recebidas + And eu devo conseguir comparar o progresso entre formulários + + Scenario: Acessar detalhes do formulário (feliz) + Given que existe um formulário criado + When eu clico em um formulário + Then eu devo ver os detalhes: questões, data de abertura/fechamento + And eu devo ter a opção de visualizar respostas + + Scenario: Formulários apenas do meu departamento (feliz) + Given que administro apenas um departamento + When eu acesso a seção "Meus Formulários" + Then eu devo ver apenas os formulários criados para turmas do meu departamento And eu não devo ver formulários de outros departamentos \ No newline at end of file diff --git a/features/visualizar_formularios_nao_respondidos.feature b/features/visualizar_formularios_nao_respondidos.feature index af689f404d..7fabafcf31 100644 --- a/features/visualizar_formularios_nao_respondidos.feature +++ b/features/visualizar_formularios_nao_respondidos.feature @@ -1,31 +1,31 @@ -Feature: Visualizar formulários não respondidos - Como Participante de uma turma - Quero visualizar os formulários não respondidos das turmas em que estou matriculado - A fim de poder escolher qual irei responder - - Background: - Given que estou autenticado como participante - And que estou matriculado em 3 turmas diferentes - - Scenario: Visualizar lista de formulários não respondidos (feliz) - Given que existem formulários disponíveis nas minhas turmas - When eu acesso a seção "Formulários Pendentes" - Then eu devo ver apenas os formulários que ainda não respondi - And cada formulário deve mostrar: turma, disciplina, data de abertura e fechamento - - Scenario: Formulários respondidos não aparecem na lista (feliz) - Given que já respondi um formulário - When eu acesso a seção "Formulários Pendentes" - Then o formulário que respondi não deve aparecer na lista - And apenas formulários novos ou em aberto devem ser mostrados - - Scenario: Nenhum formulário pendente (triste) - Given que não há formulários pendentes para responder - When eu acesso a seção "Formulários Pendentes" - Then eu devo ver a mensagem "Não há formulários pendentes" - - Scenario: Filtrar formulários por turma (feliz) - Given que existem formulários não respondidos de diferentes turmas - When eu seleciono uma turma específica - Then eu devo ver apenas os formulários daquela turma +Feature: Visualizar formulários não respondidos + Como Participante de uma turma + Quero visualizar os formulários não respondidos das turmas em que estou matriculado + A fim de poder escolher qual irei responder + + Background: + Given que estou autenticado como participante + And que estou matriculado em 3 turmas diferentes + + Scenario: Visualizar lista de formulários não respondidos (feliz) + Given que existem formulários disponíveis nas minhas turmas + When eu acesso a seção "Formulários Pendentes" + Then eu devo ver apenas os formulários que ainda não respondi + And cada formulário deve mostrar: turma, disciplina, data de abertura e fechamento + + Scenario: Formulários respondidos não aparecem na lista (feliz) + Given que já respondi um formulário + When eu acesso a seção "Formulários Pendentes" + Then o formulário que respondi não deve aparecer na lista + And apenas formulários novos ou em aberto devem ser mostrados + + Scenario: Nenhum formulário pendente (triste) + Given que não há formulários pendentes para responder + When eu acesso a seção "Formulários Pendentes" + Then eu devo ver a mensagem "Não há formulários pendentes" + + Scenario: Filtrar formulários por turma (feliz) + Given que existem formulários não respondidos de diferentes turmas + When eu seleciono uma turma específica + Then eu devo ver apenas os formulários daquela turma And os formulários de outras turmas devem ser ocultados \ No newline at end of file diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake index 0caa4d2553..53eb8401c4 100644 --- a/lib/tasks/cucumber.rake +++ b/lib/tasks/cucumber.rake @@ -1,69 +1,69 @@ -# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. -# It is recommended to regenerate this file in the future when you upgrade to a -# newer version of cucumber-rails. Consider adding your own code to a new file -# instead of editing this one. Cucumber will automatically load all features/**/*.rb -# files. - - -unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks - -vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first -$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? - -begin - require 'cucumber/rake/task' - - namespace :cucumber do - Cucumber::Rake::Task.new({ok: 'test:prepare'}, 'Run features that should pass') do |t| - t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. - t.fork = true # You may get faster startup if you set this to false - t.profile = 'default' - end - - Cucumber::Rake::Task.new({wip: 'test:prepare'}, 'Run features that are being worked on') do |t| - t.binary = vendored_cucumber_bin - t.fork = true # You may get faster startup if you set this to false - t.profile = 'wip' - end - - Cucumber::Rake::Task.new({rerun: 'test:prepare'}, 'Record failing features and run only them if any exist') do |t| - t.binary = vendored_cucumber_bin - t.fork = true # You may get faster startup if you set this to false - t.profile = 'rerun' - end - - desc 'Run all features' - task all: [:ok, :wip] - - task :statsetup do - require 'rails/code_statistics' - ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') - ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') - end - - end - - desc 'Alias for cucumber:ok' - task cucumber: 'cucumber:ok' - - task default: :cucumber - - task features: :cucumber do - STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" - end - - # In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon. - task 'test:prepare' do - end - - task stats: 'cucumber:statsetup' - - -rescue LoadError - desc 'cucumber rake task not available (cucumber not installed)' - task :cucumber do - abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' - end -end - -end +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({ok: 'test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({wip: 'test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({rerun: 'test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task all: [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + + end + + desc 'Alias for cucumber:ok' + task cucumber: 'cucumber:ok' + + task default: :cucumber + + task features: :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon. + task 'test:prepare' do + end + + task stats: 'cucumber:statsetup' + + +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/public/400.html b/public/400.html index 282dbc8cc9..c022a0a5ce 100644 --- a/public/400.html +++ b/public/400.html @@ -1,114 +1,114 @@ - - - - - - - The server cannot process the request due to a client error (400 Bad Request) - - - - - - - - - - - - - -
-
- -
-
-

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

-
-
- - - - + + + + + + + The server cannot process the request due to a client error (400 Bad Request) + + + + + + + + + + + + + +
+
+ +
+
+

The server cannot process the request due to a client error. Please check the request and try again. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/404.html b/public/404.html index c0670bc877..6e595fa692 100644 --- a/public/404.html +++ b/public/404.html @@ -1,114 +1,114 @@ - - - - - - - The page you were looking for doesn’t exist (404 Not found) - - - - - - - - - - - - - -
-
- -
-
-

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

-
-
- - - - + + + + + + + The page you were looking for doesn’t exist (404 Not found) + + + + + + + + + + + + + +
+
+ +
+
+

The page you were looking for doesn’t exist. You may have mistyped the address or the page may have moved. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/406-unsupported-browser.html b/public/406-unsupported-browser.html index 9532a9ccd0..55f2adbd30 100644 --- a/public/406-unsupported-browser.html +++ b/public/406-unsupported-browser.html @@ -1,114 +1,114 @@ - - - - - - - Your browser is not supported (406 Not Acceptable) - - - - - - - - - - - - - -
-
- -
-
-

Your browser is not supported.
Please upgrade your browser to continue.

-
-
- - - - + + + + + + + Your browser is not supported (406 Not Acceptable) + + + + + + + + + + + + + +
+
+ +
+
+

Your browser is not supported.
Please upgrade your browser to continue.

+
+
+ + + + diff --git a/public/422.html b/public/422.html index 8bcf06014f..ffa738eb82 100644 --- a/public/422.html +++ b/public/422.html @@ -1,114 +1,114 @@ - - - - - - - The change you wanted was rejected (422 Unprocessable Entity) - - - - - - - - - - - - - -
-
- -
-
-

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

-
-
- - - - + + + + + + + The change you wanted was rejected (422 Unprocessable Entity) + + + + + + + + + + + + + +
+
+ +
+
+

The change you wanted was rejected. Maybe you tried to change something you didn’t have access to. If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/500.html b/public/500.html index d77718c3a4..906fa66b82 100644 --- a/public/500.html +++ b/public/500.html @@ -1,114 +1,114 @@ - - - - - - - We’re sorry, but something went wrong (500 Internal Server Error) - - - - - - - - - - - - - -
-
- -
-
-

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

-
-
- - - - + + + + + + + We’re sorry, but something went wrong (500 Internal Server Error) + + + + + + + + + + + + + +
+
+ +
+
+

We’re sorry, but something went wrong.
If you’re the application owner check the logs for more information.

+
+
+ + + + diff --git a/public/icon.svg b/public/icon.svg index 04b34bf83f..dc412bd059 100644 --- a/public/icon.svg +++ b/public/icon.svg @@ -1,3 +1,3 @@ - - - + + + diff --git a/public/robots.txt b/public/robots.txt index c19f78ab68..e28a4082a3 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1 +1 @@ -# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/spec/factories/class_members.rb b/spec/factories/class_members.rb index 4cc3e519c6..76fb6f9461 100644 --- a/spec/factories/class_members.rb +++ b/spec/factories/class_members.rb @@ -1,9 +1,9 @@ -# spec/factories/class_members.rb - -FactoryBot.define do - factory :class_member do - user - klass - role { 'dicente' } - end +# spec/factories/class_members.rb + +FactoryBot.define do + factory :class_member do + user + klass + role { 'dicente' } + end end \ No newline at end of file diff --git a/spec/factories/form_answers.rb b/spec/factories/form_answers.rb index 4dd59fc2da..878eb500b8 100644 --- a/spec/factories/form_answers.rb +++ b/spec/factories/form_answers.rb @@ -1,9 +1,9 @@ -# spec/factories/form_answers.rb - -FactoryBot.define do - factory :form_answer do - association :form_response - association :form_template_field - answer { Faker::Lorem.sentence } - end +# spec/factories/form_answers.rb + +FactoryBot.define do + factory :form_answer do + association :form_response + association :form_template_field + answer { Faker::Lorem.sentence } + end end \ No newline at end of file diff --git a/spec/factories/form_responses.rb b/spec/factories/form_responses.rb index a99a941798..cc182100be 100644 --- a/spec/factories/form_responses.rb +++ b/spec/factories/form_responses.rb @@ -1,9 +1,9 @@ -# spec/factories/form_responses.rb - -FactoryBot.define do - factory :form_response do - association :form - association :user - submitted_at { nil } - end +# spec/factories/form_responses.rb + +FactoryBot.define do + factory :form_response do + association :form + association :user + submitted_at { nil } + end end \ No newline at end of file diff --git a/spec/factories/form_template_fields.rb b/spec/factories/form_template_fields.rb index b8afe559c8..ab628a9175 100644 --- a/spec/factories/form_template_fields.rb +++ b/spec/factories/form_template_fields.rb @@ -1,12 +1,12 @@ -# spec/factories/form_template_fields.rb - -FactoryBot.define do - factory :form_template_field do - association :form_template - field_type { 'text' } - label { Faker::Lorem.word } - required { false } - options { [] } - position { 1 } - end +# spec/factories/form_template_fields.rb + +FactoryBot.define do + factory :form_template_field do + association :form_template + field_type { 'text' } + label { Faker::Lorem.word } + required { false } + options { [] } + position { 1 } + end end \ No newline at end of file diff --git a/spec/factories/form_templates.rb b/spec/factories/form_templates.rb index cf66d49ad9..dd67fc85e0 100644 --- a/spec/factories/form_templates.rb +++ b/spec/factories/form_templates.rb @@ -1,9 +1,9 @@ -# spec/factories/form_templates.rb - -FactoryBot.define do - factory :form_template do - name { Faker::Lorem.sentence } - description { Faker::Lorem.paragraph } - association :user, factory: :user - end +# spec/factories/form_templates.rb + +FactoryBot.define do + factory :form_template do + name { Faker::Lorem.sentence } + description { Faker::Lorem.paragraph } + association :user, factory: :user + end end \ No newline at end of file diff --git a/spec/factories/forms.rb b/spec/factories/forms.rb index fbb05c9858..75ae2b8287 100644 --- a/spec/factories/forms.rb +++ b/spec/factories/forms.rb @@ -1,12 +1,12 @@ -# spec/factories/forms.rb - -FactoryBot.define do - factory :form do - association :form_template - association :klass - title { Faker::Lorem.sentence } - description { Faker::Lorem.paragraph } - due_date { 7.days.from_now } - status { 0 } - end +# spec/factories/forms.rb + +FactoryBot.define do + factory :form do + association :form_template + association :klass + title { Faker::Lorem.sentence } + description { Faker::Lorem.paragraph } + due_date { 7.days.from_now } + status { 0 } + end end \ No newline at end of file diff --git a/spec/factories/klasses.rb b/spec/factories/klasses.rb index a4d5f3f302..0a75624ece 100644 --- a/spec/factories/klasses.rb +++ b/spec/factories/klasses.rb @@ -1,10 +1,10 @@ -# spec/factories/klasses.rb - -FactoryBot.define do - factory :klass do - sequence(:code) { |n| "CIC#{1000 + n}" } - name { Faker::Lorem.sentence } - semester { "2021.2" } - description { Faker::Lorem.paragraph } - end +# spec/factories/klasses.rb + +FactoryBot.define do + factory :klass do + sequence(:code) { |n| "CIC#{1000 + n}" } + name { Faker::Lorem.sentence } + semester { "2021.2" } + description { Faker::Lorem.paragraph } + end end \ No newline at end of file diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 9a44b27c91..bf15967269 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,11 +1,11 @@ -# spec/factories/users.rb - -FactoryBot.define do - factory :user do - name { Faker::Name.name } - email { Faker::Internet.email } - password { 'senha123' } - password_confirmation { 'senha123' } - role { :user } - end -end +# spec/factories/users.rb + +FactoryBot.define do + factory :user do + name { Faker::Name.name } + email { Faker::Internet.email } + password { 'senha123' } + password_confirmation { 'senha123' } + role { :user } + end +end diff --git a/spec/helpers/admin/dashboard_helper_spec.rb b/spec/helpers/admin/dashboard_helper_spec.rb index 628ccf8243..a3a3bb6619 100644 --- a/spec/helpers/admin/dashboard_helper_spec.rb +++ b/spec/helpers/admin/dashboard_helper_spec.rb @@ -1,15 +1,15 @@ -require 'rails_helper' - -# Specs in this file have access to a helper object that includes -# the Admin::DashboardHelper. For example: -# -# describe Admin::DashboardHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end -RSpec.describe Admin::DashboardHelper, type: :helper do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Admin::DashboardHelper. For example: +# +# describe Admin::DashboardHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Admin::DashboardHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/admin/users_helper_spec.rb b/spec/helpers/admin/users_helper_spec.rb index f26854ec30..69de7849b6 100644 --- a/spec/helpers/admin/users_helper_spec.rb +++ b/spec/helpers/admin/users_helper_spec.rb @@ -1,15 +1,15 @@ -require 'rails_helper' - -# Specs in this file have access to a helper object that includes -# the Admin::UsersHelper. For example: -# -# describe Admin::UsersHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end -RSpec.describe Admin::UsersHelper, type: :helper do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the Admin::UsersHelper. For example: +# +# describe Admin::UsersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe Admin::UsersHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb index e537d8d9a8..455345673e 100644 --- a/spec/helpers/home_helper_spec.rb +++ b/spec/helpers/home_helper_spec.rb @@ -1,15 +1,15 @@ -require 'rails_helper' - -# Specs in this file have access to a helper object that includes -# the HomeHelper. For example: -# -# describe HomeHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end -RSpec.describe HomeHelper, type: :helper do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the HomeHelper. For example: +# +# describe HomeHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe HomeHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/class_member_spec.rb b/spec/models/class_member_spec.rb index 4f3dfc6ded..b6b0baaf55 100644 --- a/spec/models/class_member_spec.rb +++ b/spec/models/class_member_spec.rb @@ -1,51 +1,51 @@ -# spec/models/class_member_spec.rb - -require 'rails_helper' - -RSpec.describe ClassMember, type: :model do - describe 'validações' do - it { should validate_presence_of(:role) } - - it 'não permite role inválido' do - user = create(:user) - klass = create(:klass) - - class_member = build(:class_member, user: user, klass: klass, role: 'invalido') - - expect(class_member).not_to be_valid - end - - it 'não permite mesmo usuário em mesma turma duas vezes' do - user = create(:user) - klass = create(:klass) - - create(:class_member, user: user, klass: klass, role: 'dicente') - - duplicate = build(:class_member, user: user, klass: klass, role: 'dicente') - - expect(duplicate).not_to be_valid - end - end - - describe 'associações' do - it { should belong_to(:user) } - it { should belong_to(:klass) } - end - - describe 'roles válidos' do - let(:user) { create(:user) } - let(:klass) { create(:klass) } - - it 'permite role dicente' do - class_member = build(:class_member, user: user, klass: klass, role: 'dicente') - - expect(class_member).to be_valid - end - - it 'permite role docente' do - class_member = build(:class_member, user: user, klass: klass, role: 'docente') - - expect(class_member).to be_valid - end - end +# spec/models/class_member_spec.rb + +require 'rails_helper' + +RSpec.describe ClassMember, type: :model do + describe 'validações' do + it { should validate_presence_of(:role) } + + it 'não permite role inválido' do + user = create(:user) + klass = create(:klass) + + class_member = build(:class_member, user: user, klass: klass, role: 'invalido') + + expect(class_member).not_to be_valid + end + + it 'não permite mesmo usuário em mesma turma duas vezes' do + user = create(:user) + klass = create(:klass) + + create(:class_member, user: user, klass: klass, role: 'dicente') + + duplicate = build(:class_member, user: user, klass: klass, role: 'dicente') + + expect(duplicate).not_to be_valid + end + end + + describe 'associações' do + it { should belong_to(:user) } + it { should belong_to(:klass) } + end + + describe 'roles válidos' do + let(:user) { create(:user) } + let(:klass) { create(:klass) } + + it 'permite role dicente' do + class_member = build(:class_member, user: user, klass: klass, role: 'dicente') + + expect(class_member).to be_valid + end + + it 'permite role docente' do + class_member = build(:class_member, user: user, klass: klass, role: 'docente') + + expect(class_member).to be_valid + end + end end \ No newline at end of file diff --git a/spec/models/form_answer_spec.rb b/spec/models/form_answer_spec.rb index 261a0d6357..4c6232e882 100644 --- a/spec/models/form_answer_spec.rb +++ b/spec/models/form_answer_spec.rb @@ -1,127 +1,127 @@ -# spec/models/form_answer_spec.rb - -require 'rails_helper' - -RSpec.describe FormAnswer, type: :model do - let(:user) { create(:user, role: :admin) } - let(:student) { create(:user, role: :user) } - let(:klass) { create(:klass) } - let(:form_template) { create(:form_template, user: user) } - let(:form) { create(:form, form_template: form_template, klass: klass) } - let(:field) { create(:form_template_field, form_template: form_template, field_type: 'text', position: 1) } - let(:form_response) { create(:form_response, form: form, user: student) } - let(:form_answer) { create(:form_answer, form_response: form_response, form_template_field: field) } - - describe 'associations' do - it { is_expected.to belong_to(:form_response) } - it { is_expected.to belong_to(:form_template_field) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:form_response_id) } - it { is_expected.to validate_presence_of(:form_template_field_id) } - it { is_expected.to validate_presence_of(:answer) } - end - - describe '#create' do - it 'cria resposta válida' do - expect { - create(:form_answer, - form_response: form_response, - form_template_field: field, - answer: 'Resposta do aluno') - }.to change(FormAnswer, :count).by(1) - end - - it 'não cria answer sem form_response' do - answer = build(:form_answer, - form_response: nil, - form_template_field: field) - expect(answer).not_to be_valid - end - - it 'não cria answer sem form_template_field' do - answer = build(:form_answer, - form_response: form_response, - form_template_field: nil) - expect(answer).not_to be_valid - end - - it 'não cria answer sem resposta' do - answer = build(:form_answer, - form_response: form_response, - form_template_field: field, - answer: nil) - expect(answer).not_to be_valid - end - end - - describe 'resposta de diferentes tipos de campo' do - it 'pode armazenar resposta de campo text' do - text_field = create(:form_template_field, - form_template: form_template, - field_type: 'text', - position: 1) - answer = create(:form_answer, - form_response: form_response, - form_template_field: text_field, - answer: 'Resposta simples') - - expect(answer.answer).to eq('Resposta simples') - end - - it 'pode armazenar resposta de campo textarea' do - textarea_field = create(:form_template_field, - form_template: form_template, - field_type: 'textarea', - position: 1) - long_answer = 'Esta é uma resposta muito longa que pode conter múltiplas linhas' - answer = create(:form_answer, - form_response: form_response, - form_template_field: textarea_field, - answer: long_answer) - - expect(answer.answer).to eq(long_answer) - end - - it 'pode armazenar resposta de campo email' do - email_field = create(:form_template_field, - form_template: form_template, - field_type: 'email', - position: 1) - answer = create(:form_answer, - form_response: form_response, - form_template_field: email_field, - answer: 'student@example.com') - - expect(answer.answer).to eq('student@example.com') - end - - it 'pode armazenar resposta de campo number' do - number_field = create(:form_template_field, - form_template: form_template, - field_type: 'number', - position: 1) - answer = create(:form_answer, - form_response: form_response, - form_template_field: number_field, - answer: '42') - - expect(answer.answer).to eq('42') - end - - it 'pode armazenar resposta de campo multiple_choice' do - choice_field = create(:form_template_field, - form_template: form_template, - field_type: 'multiple_choice', - position: 1, - options: ['Opção A', 'Opção B', 'Opção C']) - answer = create(:form_answer, - form_response: form_response, - form_template_field: choice_field, - answer: 'Opção B') - - expect(answer.answer).to eq('Opção B') - end - end +# spec/models/form_answer_spec.rb + +require 'rails_helper' + +RSpec.describe FormAnswer, type: :model do + let(:user) { create(:user, role: :admin) } + let(:student) { create(:user, role: :user) } + let(:klass) { create(:klass) } + let(:form_template) { create(:form_template, user: user) } + let(:form) { create(:form, form_template: form_template, klass: klass) } + let(:field) { create(:form_template_field, form_template: form_template, field_type: 'text', position: 1) } + let(:form_response) { create(:form_response, form: form, user: student) } + let(:form_answer) { create(:form_answer, form_response: form_response, form_template_field: field) } + + describe 'associations' do + it { is_expected.to belong_to(:form_response) } + it { is_expected.to belong_to(:form_template_field) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:form_response_id) } + it { is_expected.to validate_presence_of(:form_template_field_id) } + it { is_expected.to validate_presence_of(:answer) } + end + + describe '#create' do + it 'cria resposta válida' do + expect { + create(:form_answer, + form_response: form_response, + form_template_field: field, + answer: 'Resposta do aluno') + }.to change(FormAnswer, :count).by(1) + end + + it 'não cria answer sem form_response' do + answer = build(:form_answer, + form_response: nil, + form_template_field: field) + expect(answer).not_to be_valid + end + + it 'não cria answer sem form_template_field' do + answer = build(:form_answer, + form_response: form_response, + form_template_field: nil) + expect(answer).not_to be_valid + end + + it 'não cria answer sem resposta' do + answer = build(:form_answer, + form_response: form_response, + form_template_field: field, + answer: nil) + expect(answer).not_to be_valid + end + end + + describe 'resposta de diferentes tipos de campo' do + it 'pode armazenar resposta de campo text' do + text_field = create(:form_template_field, + form_template: form_template, + field_type: 'text', + position: 1) + answer = create(:form_answer, + form_response: form_response, + form_template_field: text_field, + answer: 'Resposta simples') + + expect(answer.answer).to eq('Resposta simples') + end + + it 'pode armazenar resposta de campo textarea' do + textarea_field = create(:form_template_field, + form_template: form_template, + field_type: 'textarea', + position: 1) + long_answer = 'Esta é uma resposta muito longa que pode conter múltiplas linhas' + answer = create(:form_answer, + form_response: form_response, + form_template_field: textarea_field, + answer: long_answer) + + expect(answer.answer).to eq(long_answer) + end + + it 'pode armazenar resposta de campo email' do + email_field = create(:form_template_field, + form_template: form_template, + field_type: 'email', + position: 1) + answer = create(:form_answer, + form_response: form_response, + form_template_field: email_field, + answer: 'student@example.com') + + expect(answer.answer).to eq('student@example.com') + end + + it 'pode armazenar resposta de campo number' do + number_field = create(:form_template_field, + form_template: form_template, + field_type: 'number', + position: 1) + answer = create(:form_answer, + form_response: form_response, + form_template_field: number_field, + answer: '42') + + expect(answer.answer).to eq('42') + end + + it 'pode armazenar resposta de campo multiple_choice' do + choice_field = create(:form_template_field, + form_template: form_template, + field_type: 'multiple_choice', + position: 1, + options: ['Opção A', 'Opção B', 'Opção C']) + answer = create(:form_answer, + form_response: form_response, + form_template_field: choice_field, + answer: 'Opção B') + + expect(answer.answer).to eq('Opção B') + end + end end \ No newline at end of file diff --git a/spec/models/form_response_spec.rb b/spec/models/form_response_spec.rb index 0db5efd50b..0519cc5cb8 100644 --- a/spec/models/form_response_spec.rb +++ b/spec/models/form_response_spec.rb @@ -1,83 +1,83 @@ -# spec/models/form_response_spec.rb - -require 'rails_helper' - -RSpec.describe FormResponse, type: :model do - let(:user) { create(:user, role: :admin) } - let(:student) { create(:user, role: :user) } - let(:klass) { create(:klass) } - let(:form_template) { create(:form_template, user: user) } - let(:form) { create(:form, form_template: form_template, klass: klass) } - let(:form_response) { create(:form_response, form: form, user: student) } - - describe 'associations' do - it { is_expected.to belong_to(:form) } - it { is_expected.to belong_to(:user) } - it { is_expected.to have_many(:form_answers).dependent(:destroy) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:form_id) } - it { is_expected.to validate_presence_of(:user_id) } - end - - describe 'uniqueness validation' do - it 'não permite duplicar resposta de mesmo user para mesmo form' do - create(:form_response, form: form, user: student) - - duplicate = build(:form_response, form: form, user: student) - expect(duplicate).not_to be_valid - end - - it 'permite mesmo user responder forms diferentes' do - form2 = create(:form, form_template: form_template, klass: klass) - - response1 = create(:form_response, form: form, user: student) - response2 = build(:form_response, form: form2, user: student) - - expect(response2).to be_valid - end - end - - describe '#create' do - it 'cria resposta válida' do - expect { - create(:form_response, form: form, user: student) - }.to change(FormResponse, :count).by(1) - end - - it 'não cria resposta sem form' do - response = build(:form_response, form: nil, user: student) - expect(response).not_to be_valid - end - - it 'não cria resposta sem user' do - response = build(:form_response, form: form, user: nil) - expect(response).not_to be_valid - end - end - - describe '#completed?' do - it 'retorna false quando submitted_at é nil' do - response = create(:form_response, form: form, user: student, submitted_at: nil) - expect(response.completed?).to be false - end - - it 'retorna true quando submitted_at está presente' do - response = create(:form_response, form: form, user: student, submitted_at: Time.current) - expect(response.completed?).to be true - end - end - - describe '#pending?' do - it 'retorna true quando resposta não foi enviada' do - response = create(:form_response, form: form, user: student, submitted_at: nil) - expect(response.pending?).to be true - end - - it 'retorna false quando resposta foi enviada' do - response = create(:form_response, form: form, user: student, submitted_at: Time.current) - expect(response.pending?).to be false - end - end +# spec/models/form_response_spec.rb + +require 'rails_helper' + +RSpec.describe FormResponse, type: :model do + let(:user) { create(:user, role: :admin) } + let(:student) { create(:user, role: :user) } + let(:klass) { create(:klass) } + let(:form_template) { create(:form_template, user: user) } + let(:form) { create(:form, form_template: form_template, klass: klass) } + let(:form_response) { create(:form_response, form: form, user: student) } + + describe 'associations' do + it { is_expected.to belong_to(:form) } + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:form_answers).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:form_id) } + it { is_expected.to validate_presence_of(:user_id) } + end + + describe 'uniqueness validation' do + it 'não permite duplicar resposta de mesmo user para mesmo form' do + create(:form_response, form: form, user: student) + + duplicate = build(:form_response, form: form, user: student) + expect(duplicate).not_to be_valid + end + + it 'permite mesmo user responder forms diferentes' do + form2 = create(:form, form_template: form_template, klass: klass) + + response1 = create(:form_response, form: form, user: student) + response2 = build(:form_response, form: form2, user: student) + + expect(response2).to be_valid + end + end + + describe '#create' do + it 'cria resposta válida' do + expect { + create(:form_response, form: form, user: student) + }.to change(FormResponse, :count).by(1) + end + + it 'não cria resposta sem form' do + response = build(:form_response, form: nil, user: student) + expect(response).not_to be_valid + end + + it 'não cria resposta sem user' do + response = build(:form_response, form: form, user: nil) + expect(response).not_to be_valid + end + end + + describe '#completed?' do + it 'retorna false quando submitted_at é nil' do + response = create(:form_response, form: form, user: student, submitted_at: nil) + expect(response.completed?).to be false + end + + it 'retorna true quando submitted_at está presente' do + response = create(:form_response, form: form, user: student, submitted_at: Time.current) + expect(response.completed?).to be true + end + end + + describe '#pending?' do + it 'retorna true quando resposta não foi enviada' do + response = create(:form_response, form: form, user: student, submitted_at: nil) + expect(response.pending?).to be true + end + + it 'retorna false quando resposta foi enviada' do + response = create(:form_response, form: form, user: student, submitted_at: Time.current) + expect(response.pending?).to be false + end + end end \ No newline at end of file diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index 6d6890b00e..75294ba184 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -1,94 +1,94 @@ -# spec/models/form_spec.rb - -require 'rails_helper' - -RSpec.describe Form, type: :model do - let(:user) { create(:user, role: :admin) } - let(:klass) { create(:klass) } - let(:form_template) { create(:form_template, user: user) } - let(:form) { create(:form, form_template: form_template, klass: klass) } - - describe 'associations' do - it { is_expected.to belong_to(:form_template) } - it { is_expected.to belong_to(:klass) } - it { is_expected.to have_many(:form_responses).dependent(:destroy) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:form_template_id) } - it { is_expected.to validate_presence_of(:klass_id) } - it { is_expected.to validate_presence_of(:title) } - end - - describe '#create' do - it 'cria um formulário válido' do - expect { - create(:form, form_template: form_template, klass: klass) - }.to change(Form, :count).by(1) - end - - it 'não cria form sem título' do - form_obj = build(:form, title: nil, form_template: form_template, klass: klass) - expect(form_obj).not_to be_valid - end - - it 'não cria form sem template' do - form_obj = build(:form, form_template: nil, klass: klass) - expect(form_obj).not_to be_valid - end - - it 'não cria form sem turma' do - form_obj = build(:form, form_template: form_template, klass: nil) - expect(form_obj).not_to be_valid - end - end - - describe 'enum status' do - it 'tem draft como status padrão' do - form_obj = create(:form, form_template: form_template, klass: klass) - expect(form_obj.status).to eq('draft') - end - - it 'pode ter status published' do - form.update(status: :published) - expect(form.published?).to be true - end - - it 'pode ter status closed' do - form.update(status: :closed) - expect(form.closed?).to be true - end - end - - describe '#pending_responses' do - it 'retorna alunos que não responderam' do - student1 = create(:user, role: :user) - student2 = create(:user, role: :user) - - create(:class_member, user: student1, klass: klass, role: 'dicente') - create(:class_member, user: student2, klass: klass, role: 'dicente') - - create(:form_response, form: form, user: student1) - - pending = form.pending_responses - expect(pending).to include(student2) - expect(pending).not_to include(student1) - end - end - - describe '#completed_responses' do - it 'retorna alunos que responderam' do - student1 = create(:user, role: :user) - student2 = create(:user, role: :user) - - create(:class_member, user: student1, klass: klass, role: 'dicente') - create(:class_member, user: student2, klass: klass, role: 'dicente') - - create(:form_response, form: form, user: student1) - - completed = form.completed_responses - expect(completed).to include(student1) - expect(completed).not_to include(student2) - end - end +# spec/models/form_spec.rb + +require 'rails_helper' + +RSpec.describe Form, type: :model do + let(:user) { create(:user, role: :admin) } + let(:klass) { create(:klass) } + let(:form_template) { create(:form_template, user: user) } + let(:form) { create(:form, form_template: form_template, klass: klass) } + + describe 'associations' do + it { is_expected.to belong_to(:form_template) } + it { is_expected.to belong_to(:klass) } + it { is_expected.to have_many(:form_responses).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:form_template_id) } + it { is_expected.to validate_presence_of(:klass_id) } + it { is_expected.to validate_presence_of(:title) } + end + + describe '#create' do + it 'cria um formulário válido' do + expect { + create(:form, form_template: form_template, klass: klass) + }.to change(Form, :count).by(1) + end + + it 'não cria form sem título' do + form_obj = build(:form, title: nil, form_template: form_template, klass: klass) + expect(form_obj).not_to be_valid + end + + it 'não cria form sem template' do + form_obj = build(:form, form_template: nil, klass: klass) + expect(form_obj).not_to be_valid + end + + it 'não cria form sem turma' do + form_obj = build(:form, form_template: form_template, klass: nil) + expect(form_obj).not_to be_valid + end + end + + describe 'enum status' do + it 'tem draft como status padrão' do + form_obj = create(:form, form_template: form_template, klass: klass) + expect(form_obj.status).to eq('draft') + end + + it 'pode ter status published' do + form.update(status: :published) + expect(form.published?).to be true + end + + it 'pode ter status closed' do + form.update(status: :closed) + expect(form.closed?).to be true + end + end + + describe '#pending_responses' do + it 'retorna alunos que não responderam' do + student1 = create(:user, role: :user) + student2 = create(:user, role: :user) + + create(:class_member, user: student1, klass: klass, role: 'dicente') + create(:class_member, user: student2, klass: klass, role: 'dicente') + + create(:form_response, form: form, user: student1) + + pending = form.pending_responses + expect(pending).to include(student2) + expect(pending).not_to include(student1) + end + end + + describe '#completed_responses' do + it 'retorna alunos que responderam' do + student1 = create(:user, role: :user) + student2 = create(:user, role: :user) + + create(:class_member, user: student1, klass: klass, role: 'dicente') + create(:class_member, user: student2, klass: klass, role: 'dicente') + + create(:form_response, form: form, user: student1) + + completed = form.completed_responses + expect(completed).to include(student1) + expect(completed).not_to include(student2) + end + end end \ No newline at end of file diff --git a/spec/models/form_template_field_spec.rb b/spec/models/form_template_field_spec.rb index 8a55e2bbd8..c027fdfe29 100644 --- a/spec/models/form_template_field_spec.rb +++ b/spec/models/form_template_field_spec.rb @@ -1,77 +1,77 @@ -# spec/models/form_template_field_spec.rb - -require 'rails_helper' - -RSpec.describe FormTemplateField, type: :model do - let(:user) { create(:user, role: :admin) } - let(:form_template) { create(:form_template, user: user) } - let(:field) do - create(:form_template_field, - form_template: form_template, - field_type: 'text', - label: 'Nome', - position: 1) - end - - describe 'associations' do - it { is_expected.to belong_to(:form_template) } - it { is_expected.to have_many(:form_answers).dependent(:destroy) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:field_type) } - it { is_expected.to validate_presence_of(:label) } - it { is_expected.to validate_presence_of(:position) } - it { is_expected.to validate_presence_of(:form_template_id) } - end - - describe 'field_type validation' do - it 'aceita field_type válido' do - field = build(:form_template_field, - form_template: form_template, - field_type: 'text') - expect(field).to be_valid - end - - it 'rejeita field_type inválido' do - field = build(:form_template_field, - form_template: form_template, - field_type: 'invalid') - expect(field).not_to be_valid - end - - it 'aceita todos os tipos válidos' do - valid_types = %w(text textarea number email multiple_choice) - valid_types.each do |type| - field = build(:form_template_field, - form_template: form_template, - field_type: type) - expect(field).to be_valid - end - end - end - - describe '#create' do - it 'cria field com sucesso' do - expect { - create(:form_template_field, - form_template: form_template, - field_type: 'text') - }.to change(FormTemplateField, :count).by(1) - end - - it 'não cria field sem label' do - field = build(:form_template_field, - form_template: form_template, - label: nil) - expect(field).not_to be_valid - end - - it 'não cria field sem position' do - field = build(:form_template_field, - form_template: form_template, - position: nil) - expect(field).not_to be_valid - end - end +# spec/models/form_template_field_spec.rb + +require 'rails_helper' + +RSpec.describe FormTemplateField, type: :model do + let(:user) { create(:user, role: :admin) } + let(:form_template) { create(:form_template, user: user) } + let(:field) do + create(:form_template_field, + form_template: form_template, + field_type: 'text', + label: 'Nome', + position: 1) + end + + describe 'associations' do + it { is_expected.to belong_to(:form_template) } + it { is_expected.to have_many(:form_answers).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:field_type) } + it { is_expected.to validate_presence_of(:label) } + it { is_expected.to validate_presence_of(:position) } + it { is_expected.to validate_presence_of(:form_template_id) } + end + + describe 'field_type validation' do + it 'aceita field_type válido' do + field = build(:form_template_field, + form_template: form_template, + field_type: 'text') + expect(field).to be_valid + end + + it 'rejeita field_type inválido' do + field = build(:form_template_field, + form_template: form_template, + field_type: 'invalid') + expect(field).not_to be_valid + end + + it 'aceita todos os tipos válidos' do + valid_types = %w(text textarea number email multiple_choice) + valid_types.each do |type| + field = build(:form_template_field, + form_template: form_template, + field_type: type) + expect(field).to be_valid + end + end + end + + describe '#create' do + it 'cria field com sucesso' do + expect { + create(:form_template_field, + form_template: form_template, + field_type: 'text') + }.to change(FormTemplateField, :count).by(1) + end + + it 'não cria field sem label' do + field = build(:form_template_field, + form_template: form_template, + label: nil) + expect(field).not_to be_valid + end + + it 'não cria field sem position' do + field = build(:form_template_field, + form_template: form_template, + position: nil) + expect(field).not_to be_valid + end + end end \ No newline at end of file diff --git a/spec/models/form_template_spec.rb b/spec/models/form_template_spec.rb index 9b1619e3e6..37c393890a 100644 --- a/spec/models/form_template_spec.rb +++ b/spec/models/form_template_spec.rb @@ -1,57 +1,57 @@ -# spec/models/form_template_spec.rb - -require 'rails_helper' - -RSpec.describe FormTemplate, type: :model do - let(:user) { create(:user, role: :admin) } - let(:form_template) { create(:form_template, user: user) } - - describe 'associations' do - it { is_expected.to belong_to(:user) } - it { is_expected.to have_many(:form_template_fields).dependent(:destroy) } - it { is_expected.to have_many(:forms).dependent(:destroy) } - end - - describe 'validations' do - it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_presence_of(:description) } - it { is_expected.to validate_presence_of(:user_id) } - end - - describe '#create' do - it 'cria um template válido' do - expect { - create(:form_template, user: user) - }.to change(FormTemplate, :count).by(1) - end - - it 'não cria template sem nome' do - template = build(:form_template, name: nil, user: user) - expect(template).not_to be_valid - end - - it 'não cria template sem descrição' do - template = build(:form_template, description: nil, user: user) - expect(template).not_to be_valid - end - - it 'não cria template sem user' do - template = build(:form_template, user: nil) - expect(template).not_to be_valid - end - end - - describe '#accepts_nested_attributes_for' do - it 'permite criar fields aninhados' do - template = create(:form_template, user: user) - template.form_template_fields.create!( - field_type: 'text', - label: 'Nome Completo', - required: true, - position: 1 - ) - - expect(template.form_template_fields.count).to eq(1) - end - end +# spec/models/form_template_spec.rb + +require 'rails_helper' + +RSpec.describe FormTemplate, type: :model do + let(:user) { create(:user, role: :admin) } + let(:form_template) { create(:form_template, user: user) } + + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:form_template_fields).dependent(:destroy) } + it { is_expected.to have_many(:forms).dependent(:destroy) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:user_id) } + end + + describe '#create' do + it 'cria um template válido' do + expect { + create(:form_template, user: user) + }.to change(FormTemplate, :count).by(1) + end + + it 'não cria template sem nome' do + template = build(:form_template, name: nil, user: user) + expect(template).not_to be_valid + end + + it 'não cria template sem descrição' do + template = build(:form_template, description: nil, user: user) + expect(template).not_to be_valid + end + + it 'não cria template sem user' do + template = build(:form_template, user: nil) + expect(template).not_to be_valid + end + end + + describe '#accepts_nested_attributes_for' do + it 'permite criar fields aninhados' do + template = create(:form_template, user: user) + template.form_template_fields.create!( + field_type: 'text', + label: 'Nome Completo', + required: true, + position: 1 + ) + + expect(template.form_template_fields.count).to eq(1) + end + end end \ No newline at end of file diff --git a/spec/models/klass_spec.rb b/spec/models/klass_spec.rb index e6d5650b24..47a1b60da3 100644 --- a/spec/models/klass_spec.rb +++ b/spec/models/klass_spec.rb @@ -1,62 +1,62 @@ -# spec/models/klass_spec.rb - -require 'rails_helper' - -RSpec.describe Klass, type: :model do - describe 'validações' do - it { should validate_presence_of(:code) } - it { should validate_presence_of(:name) } - it { should validate_presence_of(:semester) } - - it 'valida unicidade de code' do - create(:klass, code: 'CIC0097') - klass2 = build(:klass, code: 'CIC0097') - - expect(klass2).not_to be_valid - end - end - - describe 'associações' do - it { should have_many(:class_members) } - it { should have_many(:users).through(:class_members) } - end - - describe '#students' do - it 'retorna apenas os alunos da turma' do - klass = create(:klass) - student = create(:user) - teacher = create(:user) - - create(:class_member, klass: klass, user: student, role: 'dicente') - create(:class_member, klass: klass, user: teacher, role: 'docente') - - expect(klass.students).to include(student) - expect(klass.students).not_to include(teacher) - expect(klass.students.count).to eq(1) - end - end - - describe '#teachers' do - it 'retorna apenas os professores da turma' do - klass = create(:klass) - student = create(:user) - teacher = create(:user) - - create(:class_member, klass: klass, user: student, role: 'dicente') - create(:class_member, klass: klass, user: teacher, role: 'docente') - - expect(klass.teachers).to include(teacher) - expect(klass.teachers).not_to include(student) - expect(klass.teachers.count).to eq(1) - end - end - - describe 'scopes' do - it 'ordena por semestre decrescente' do - klass1 = create(:klass, semester: '2021.1') - klass2 = create(:klass, code: 'CIC0098', semester: '2021.2') - - expect(Klass.active).to eq([klass2, klass1]) - end - end +# spec/models/klass_spec.rb + +require 'rails_helper' + +RSpec.describe Klass, type: :model do + describe 'validações' do + it { should validate_presence_of(:code) } + it { should validate_presence_of(:name) } + it { should validate_presence_of(:semester) } + + it 'valida unicidade de code' do + create(:klass, code: 'CIC0097') + klass2 = build(:klass, code: 'CIC0097') + + expect(klass2).not_to be_valid + end + end + + describe 'associações' do + it { should have_many(:class_members) } + it { should have_many(:users).through(:class_members) } + end + + describe '#students' do + it 'retorna apenas os alunos da turma' do + klass = create(:klass) + student = create(:user) + teacher = create(:user) + + create(:class_member, klass: klass, user: student, role: 'dicente') + create(:class_member, klass: klass, user: teacher, role: 'docente') + + expect(klass.students).to include(student) + expect(klass.students).not_to include(teacher) + expect(klass.students.count).to eq(1) + end + end + + describe '#teachers' do + it 'retorna apenas os professores da turma' do + klass = create(:klass) + student = create(:user) + teacher = create(:user) + + create(:class_member, klass: klass, user: student, role: 'dicente') + create(:class_member, klass: klass, user: teacher, role: 'docente') + + expect(klass.teachers).to include(teacher) + expect(klass.teachers).not_to include(student) + expect(klass.teachers.count).to eq(1) + end + end + + describe 'scopes' do + it 'ordena por semestre decrescente' do + klass1 = create(:klass, semester: '2021.1') + klass2 = create(:klass, code: 'CIC0098', semester: '2021.2') + + expect(Klass.active).to eq([klass2, klass1]) + end + end end \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e532f3acdd..6a2e294b3a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,76 +1,76 @@ -# spec/models/user_spec.rb - -require 'rails_helper' - -RSpec.describe User, type: :model do - describe 'validações' do - it { should validate_presence_of(:name) } - it { should validate_presence_of(:email) } - - it 'validates email uniqueness' do - user1 = create(:user, email: 'unique@example.com') - user2 = build(:user, email: 'unique@example.com') - - expect(user2).not_to be_valid - end - end - - describe 'criação de usuário' do - it 'cria um usuário com dados válidos' do - user = User.create( - name: 'João Silva', - email: 'joao@example.com', - password: 'senha123', - password_confirmation: 'senha123', - role: :user - ) - - expect(user).to be_persisted - expect(user.user?).to be true - end - - it 'não cria usuário sem nome' do - user = User.new( - email: 'teste@example.com', - password: 'senha123', - password_confirmation: 'senha123' - ) - - expect(user).not_to be_valid - expect(user.errors[:name]).to include("can't be blank") - end - - it 'não cria usuário com email duplicado' do - User.create( - name: 'João', - email: 'joao@example.com', - password: 'senha123', - password_confirmation: 'senha123' - ) - - user2 = User.new( - name: 'Maria', - email: 'joao@example.com', - password: 'senha123', - password_confirmation: 'senha123' - ) - - expect(user2).not_to be_valid - end - end - - describe 'roles' do - it 'cria um usuário admin' do - admin = User.create( - name: 'Administrador', - email: 'admin@example.com', - password: 'senha123', - password_confirmation: 'senha123', - role: :admin - ) - - expect(admin.admin?).to be true - expect(admin.role).to eq('admin') - end - end +# spec/models/user_spec.rb + +require 'rails_helper' + +RSpec.describe User, type: :model do + describe 'validações' do + it { should validate_presence_of(:name) } + it { should validate_presence_of(:email) } + + it 'validates email uniqueness' do + user1 = create(:user, email: 'unique@example.com') + user2 = build(:user, email: 'unique@example.com') + + expect(user2).not_to be_valid + end + end + + describe 'criação de usuário' do + it 'cria um usuário com dados válidos' do + user = User.create( + name: 'João Silva', + email: 'joao@example.com', + password: 'senha123', + password_confirmation: 'senha123', + role: :user + ) + + expect(user).to be_persisted + expect(user.user?).to be true + end + + it 'não cria usuário sem nome' do + user = User.new( + email: 'teste@example.com', + password: 'senha123', + password_confirmation: 'senha123' + ) + + expect(user).not_to be_valid + expect(user.errors[:name]).to include("can't be blank") + end + + it 'não cria usuário com email duplicado' do + User.create( + name: 'João', + email: 'joao@example.com', + password: 'senha123', + password_confirmation: 'senha123' + ) + + user2 = User.new( + name: 'Maria', + email: 'joao@example.com', + password: 'senha123', + password_confirmation: 'senha123' + ) + + expect(user2).not_to be_valid + end + end + + describe 'roles' do + it 'cria um usuário admin' do + admin = User.create( + name: 'Administrador', + email: 'admin@example.com', + password: 'senha123', + password_confirmation: 'senha123', + role: :admin + ) + + expect(admin.admin?).to be true + expect(admin.role).to eq('admin') + end + end end \ No newline at end of file diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index da9885e867..f258b8a64f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,80 +1,80 @@ -# This file is copied to spec/ when you run 'rails generate rspec:install' -require 'spec_helper' -ENV['RAILS_ENV'] ||= 'test' -require_relative '../config/environment' -# Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? -require 'rspec/rails' -# Add additional requires below this line. Rails is not loaded until this point! - -require 'factory_bot_rails' - -# Requires supporting ruby files with custom matchers and macros, etc, in -# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are -# run as spec files by default. This means that files in spec/support that end -# in _spec.rb will both be required and run as specs, causing the specs to be -# run twice. It is recommended that you do not name files matching this glob to -# end with _spec.rb. You can configure this pattern with the --pattern -# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. -# -# The following line is provided for convenience purposes. It has the downside -# of increasing the boot-up time by auto-requiring all files in the support -# directory. Alternatively, in the individual `*_spec.rb` files, manually -# require only the support files necessary. -# -Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } - - -# Checks for pending migrations and applies them before tests are run. -# If you are not using ActiveRecord, you can remove these lines. -begin - ActiveRecord::Migration.maintain_test_schema! -rescue ActiveRecord::PendingMigrationError => e - abort e.to_s.strip -end -RSpec.configure do |config| - # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{::Rails.root}/spec/fixtures" - - # Use factories instead of fixtures - config.include FactoryBot::Syntax::Methods - - # If you're not using ActiveRecord, or you'd prefer not to run each of your - # examples within a transaction, remove the following line or assign false - # instead of true. - config.use_transactional_fixtures = true - - - # You can uncomment this line to turn off ActiveRecord support entirely. - # config.use_active_record = false - - - # RSpec Rails can automatically mix in different behaviours to your tests - # based on their file location, for example enabling you to call `get` and - # `post` in specs under `spec/controllers`. - # - # You can disable this behaviour by removing the line below, and instead - # explicitly tag your specs with their type, e.g.: - # - # RSpec.describe UsersController, type: :controller do - # # ... - # end - # - # The different available types are documented in the features, such as in - # [https://rspec.info/features/6-0/rspec-rails](https://rspec.info/features/6-0/rspec-rails) - config.infer_spec_type_from_file_location! - - - # Filter lines from Rails gems in backtraces. - config.filter_rails_from_backtrace! - # arbitrary gems may also be filtered via: - # config.filter_gems_from_backtrace("gem name") -end - -# Configure shoulda-matchers -Shoulda::Matchers.configure do |config| - config.integrate do |with| - with.test_framework :rspec - with.library :rails - end +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +require 'factory_bot_rails' + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } + + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + abort e.to_s.strip +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # Use factories instead of fixtures + config.include FactoryBot::Syntax::Methods + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # [https://rspec.info/features/6-0/rspec-rails](https://rspec.info/features/6-0/rspec-rails) + config.infer_spec_type_from_file_location! + + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end + +# Configure shoulda-matchers +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end end \ No newline at end of file diff --git a/spec/requests/admin/dashboard_spec.rb b/spec/requests/admin/dashboard_spec.rb index 9c39b690a8..6889c91fb8 100644 --- a/spec/requests/admin/dashboard_spec.rb +++ b/spec/requests/admin/dashboard_spec.rb @@ -1,11 +1,11 @@ -require 'rails_helper' - -RSpec.describe "Admin::Dashboards", type: :request do - describe "GET /index" do - it "returns http success" do - get "/admin/dashboard/index" - expect(response).to have_http_status(:success) - end - end - -end +require 'rails_helper' + +RSpec.describe "Admin::Dashboards", type: :request do + describe "GET /index" do + it "returns http success" do + get "/admin/dashboard/index" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/requests/admin/users_spec.rb b/spec/requests/admin/users_spec.rb index c4171b1c7e..a9bad99346 100644 --- a/spec/requests/admin/users_spec.rb +++ b/spec/requests/admin/users_spec.rb @@ -1,39 +1,39 @@ -require 'rails_helper' - -RSpec.describe "Admin::Users", type: :request do - describe "GET /index" do - it "returns http success" do - get "/admin/users/index" - expect(response).to have_http_status(:success) - end - end - - describe "GET /show" do - it "returns http success" do - get "/admin/users/show" - expect(response).to have_http_status(:success) - end - end - - describe "GET /edit" do - it "returns http success" do - get "/admin/users/edit" - expect(response).to have_http_status(:success) - end - end - - describe "GET /update" do - it "returns http success" do - get "/admin/users/update" - expect(response).to have_http_status(:success) - end - end - - describe "GET /destroy" do - it "returns http success" do - get "/admin/users/destroy" - expect(response).to have_http_status(:success) - end - end - -end +require 'rails_helper' + +RSpec.describe "Admin::Users", type: :request do + describe "GET /index" do + it "returns http success" do + get "/admin/users/index" + expect(response).to have_http_status(:success) + end + end + + describe "GET /show" do + it "returns http success" do + get "/admin/users/show" + expect(response).to have_http_status(:success) + end + end + + describe "GET /edit" do + it "returns http success" do + get "/admin/users/edit" + expect(response).to have_http_status(:success) + end + end + + describe "GET /update" do + it "returns http success" do + get "/admin/users/update" + expect(response).to have_http_status(:success) + end + end + + describe "GET /destroy" do + it "returns http success" do + get "/admin/users/destroy" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/requests/home_spec.rb b/spec/requests/home_spec.rb index fdbd64231d..fb70a144f2 100644 --- a/spec/requests/home_spec.rb +++ b/spec/requests/home_spec.rb @@ -1,11 +1,11 @@ -require 'rails_helper' - -RSpec.describe "Homes", type: :request do - describe "GET /index" do - it "returns http success" do - get "/home/index" - expect(response).to have_http_status(:success) - end - end - -end +require 'rails_helper' + +RSpec.describe "Homes", type: :request do + describe "GET /index" do + it "returns http success" do + get "/home/index" + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 327b58ea1f..2aa8dccabd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,94 +1,94 @@ -# This file was generated by the `rails generate rspec:install` command. Conventionally, all -# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. -# The generated `.rspec` file contains `--require spec_helper` which will cause -# this file to always be loaded, without a need to explicitly require it in any -# files. -# -# Given that it is always loaded, you are encouraged to keep this file as -# light-weight as possible. Requiring heavyweight dependencies from this file -# will add to the boot time of your test suite on EVERY test run, even for an -# individual file that may not need all of that loaded. Instead, consider making -# a separate helper file that requires the additional dependencies and performs -# the additional setup, and require it from the spec files that actually need -# it. -# -# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration -RSpec.configure do |config| - # rspec-expectations config goes here. You can use an alternate - # assertion/expectation library such as wrong or the stdlib/minitest - # assertions if you prefer. - config.expect_with :rspec do |expectations| - # This option will default to `true` in RSpec 4. It makes the `description` - # and `failure_message` of custom matchers include text for helper methods - # defined using `chain`, e.g.: - # be_bigger_than(2).and_smaller_than(4).description - # # => "be bigger than 2 and smaller than 4" - # ...rather than: - # # => "be bigger than 2" - expectations.include_chain_clauses_in_custom_matcher_descriptions = true - end - - # rspec-mocks config goes here. You can use an alternate test double - # library (such as bogus or mocha) by changing the `mock_with` option here. - config.mock_with :rspec do |mocks| - # Prevents you from mocking or stubbing a method that does not exist on - # a real object. This is generally recommended, and will default to - # `true` in RSpec 4. - mocks.verify_partial_doubles = true - end - - # This option will default to `:apply_to_host_groups` in RSpec 4 (and will - # have no way to turn it off -- the option exists only for backwards - # compatibility in RSpec 3). It causes shared context metadata to be - # inherited by the metadata hash of host groups and examples, rather than - # triggering implicit auto-inclusion in groups with matching metadata. - config.shared_context_metadata_behavior = :apply_to_host_groups - -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ - config.disable_monkey_patching! - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end -end +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/spec/views/admin/dashboard/index.html.erb_spec.rb b/spec/views/admin/dashboard/index.html.erb_spec.rb index d9dbe041a3..9deb898169 100644 --- a/spec/views/admin/dashboard/index.html.erb_spec.rb +++ b/spec/views/admin/dashboard/index.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "dashboard/index.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "dashboard/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/destroy.html.erb_spec.rb b/spec/views/admin/users/destroy.html.erb_spec.rb index c5955f41c7..40574699e3 100644 --- a/spec/views/admin/users/destroy.html.erb_spec.rb +++ b/spec/views/admin/users/destroy.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "users/destroy.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "users/destroy.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/edit.html.erb_spec.rb b/spec/views/admin/users/edit.html.erb_spec.rb index 7d1e936979..ac58b82e03 100644 --- a/spec/views/admin/users/edit.html.erb_spec.rb +++ b/spec/views/admin/users/edit.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "users/edit.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "users/edit.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/index.html.erb_spec.rb b/spec/views/admin/users/index.html.erb_spec.rb index 3e5309f6d6..2b78da784f 100644 --- a/spec/views/admin/users/index.html.erb_spec.rb +++ b/spec/views/admin/users/index.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "users/index.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "users/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/show.html.erb_spec.rb b/spec/views/admin/users/show.html.erb_spec.rb index 34ad7a4225..203ae1cd75 100644 --- a/spec/views/admin/users/show.html.erb_spec.rb +++ b/spec/views/admin/users/show.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "users/show.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "users/show.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/users/update.html.erb_spec.rb b/spec/views/admin/users/update.html.erb_spec.rb index 7d2a4b9c9d..dc4d50e902 100644 --- a/spec/views/admin/users/update.html.erb_spec.rb +++ b/spec/views/admin/users/update.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "users/update.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "users/update.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/home/index.html.erb_spec.rb b/spec/views/home/index.html.erb_spec.rb index 75bb045bc0..4112eb36ce 100644 --- a/spec/views/home/index.html.erb_spec.rb +++ b/spec/views/home/index.html.erb_spec.rb @@ -1,5 +1,5 @@ -require 'rails_helper' - -RSpec.describe "home/index.html.erb", type: :view do - pending "add some examples to (or delete) #{__FILE__}" -end +require 'rails_helper' + +RSpec.describe "home/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index cee29fd214..48f8dac049 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -1,5 +1,5 @@ -require "test_helper" - -class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] -end +require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0c22470ec1..7f805a09d3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,15 +1,15 @@ -ENV["RAILS_ENV"] ||= "test" -require_relative "../config/environment" -require "rails/test_help" - -module ActiveSupport - class TestCase - # Run tests in parallel with specified workers - parallelize(workers: :number_of_processors) - - # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. - fixtures :all - - # Add more helper methods to be used by all tests here... - end -end +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" + +module ActiveSupport + class TestCase + # Run tests in parallel with specified workers + parallelize(workers: :number_of_processors) + + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + # Add more helper methods to be used by all tests here... + end +end From 73b37c79a89f8c2fa2ddbcaea850a859912c7593 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 8 Dec 2025 22:47:50 -0300 Subject: [PATCH 34/63] fix/correcao do botao para sair a partir da pagina de importacao --- app/javascript/application.js | 1 + app/views/admin/imports/index.html.erb | 8 ++++++-- config/initializers/devise.rb | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/javascript/application.js b/app/javascript/application.js index aff7495165..582a4a6e08 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,4 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" import "controllers" +import "bootstrap" \ No newline at end of file diff --git a/app/views/admin/imports/index.html.erb b/app/views/admin/imports/index.html.erb index 41bfbf00d0..839d5caf94 100644 --- a/app/views/admin/imports/index.html.erb +++ b/app/views/admin/imports/index.html.erb @@ -10,14 +10,18 @@ <% if notice.present? %> <% end %> <% if alert.present? %> <% end %> diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 08fe9efe75..4fce4f90e8 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -266,7 +266,7 @@ # config.navigational_formats = ['*/*', :html, :turbo_stream] # The default HTTP method used to sign out a resource. Default is :delete. - config.sign_out_via = :delete + config.sign_out_via = [:get, :delete] # ==> OmniAuth # Add a new OmniAuth provider. Check the wiki for more information on setting From baa66a015525268cdf7319fd69d1de61ac9ac8ad Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 8 Dec 2025 22:52:54 -0300 Subject: [PATCH 35/63] fix/correcao do botao de fechar aviso na pagina de importacao --- app/views/admin/imports/index.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/imports/index.html.erb b/app/views/admin/imports/index.html.erb index 839d5caf94..9a17442a7f 100644 --- a/app/views/admin/imports/index.html.erb +++ b/app/views/admin/imports/index.html.erb @@ -10,7 +10,7 @@ <% if notice.present? %> @@ -19,7 +19,7 @@ <% if alert.present? %> From 72820a02497073c3846d05b31421821d399e1cd2 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 8 Dec 2025 23:06:50 -0300 Subject: [PATCH 36/63] fix/visualizacao do template criado --- app/views/admin/form_templates/show.html.erb | 168 ++++++------------- 1 file changed, 53 insertions(+), 115 deletions(-) diff --git a/app/views/admin/form_templates/show.html.erb b/app/views/admin/form_templates/show.html.erb index 75ddb4c15d..174d251c86 100644 --- a/app/views/admin/form_templates/show.html.erb +++ b/app/views/admin/form_templates/show.html.erb @@ -3,129 +3,67 @@
-

<%= @form.title %>

-

<%= @form.form_template.name %> • <%= @form.klass.name %>

+

<%= @form_template.name %>

+

<%= @form_template.description %>

- <% if @form.draft? %> - <%= link_to 'Editar', edit_admin_form_path(@form), class: 'btn btn-warning' %> - <%= button_to 'Publicar', publish_admin_form_path(@form), method: :patch, class: 'btn btn-success' %> - <% end %> - <% if @form.published? %> - <%= button_to 'Fechar', close_admin_form_path(@form), method: :patch, class: 'btn btn-danger' %> - <% end %> - <%= link_to 'Voltar', admin_forms_path, class: 'btn btn-secondary' %> + <%= link_to 'Editar Template', edit_admin_form_template_path(@form_template), class: 'btn btn-warning' %> + <%= link_to 'Voltar', admin_form_templates_path, class: 'btn btn-secondary' %>
-
-
-
-
-
Status
- <% case @form.status %> - <% when 'draft' %> - Rascunho - <% when 'published' %> - Publicado - <% when 'closed' %> - Fechado - <% end %> -
-
-
-
-
-
-
Respondidos
-

<%= @completed_count %> / <%= @form.klass.students.count %>

-
-
-
-
-
-
-
Pendentes
-

<%= @pending_count %>

-
-
+
+
+
Perguntas configuradas neste template
-
-
-
-
Prazo
-

<%= @form.due_date.present? ? l(@form.due_date, format: :long) : 'Sem prazo' %>

-
+
+
+ + + + + + + + + + + + <% @form_template.form_template_fields.order(:position).each do |field| %> + + + + + + + + <% end %> + +
PosiçãoPergunta (Label)Tipo de RespostaObrigatório?Opções
<%= field.position %><%= field.label %> + + <%= field.field_type %> + + + <% if field.required %> + Sim + <% else %> + Opcional + <% end %> + + <% if ['select', 'radio', 'checkbox'].include?(field.field_type) && field.options.present? %> + <%= field.options %> + <% else %> + - + <% end %> +
-
-
-
Descrição
-

<%= @form.description %>

+ <% if @form_template.form_template_fields.empty? %> +
+ Este template ainda não possui nenhuma pergunta cadastrada. + <%= link_to "Adicionar perguntas", edit_admin_form_template_path(@form_template), class: "alert-link" %>
-
- -

Campos do Formulário

-
- - - - - - - - - - - <% @form.form_template.form_template_fields.order(:position).each do |field| %> - - - - - - - <% end %> - -
PosiçãoLabelTipoObrigatório
<%= field.position %><%= field.label %><%= field.field_type %> - <% if field.required %> - Sim - <% else %> - Não - <% end %> -
-
- -

Respostas

-
- - - - - - - - - - - <% @form.form_responses.each do |response| %> - - - - - - - <% end %> - -
AlunoStatusRespondido emAções
<%= response.user.name %> - <% if response.completed? %> - Respondido - <% else %> - Pendente - <% end %> - <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> - <%= link_to 'Ver Respostas', '#', class: 'btn btn-sm btn-info' %> -
-
-
+ <% end %> +
\ No newline at end of file From 16b7054be5d3b4fc49c0e0db64cdb106228a57a6 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Mon, 8 Dec 2025 23:27:05 -0300 Subject: [PATCH 37/63] feat/delecao de template de formulario --- app/views/admin/form_templates/index.html.erb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/admin/form_templates/index.html.erb b/app/views/admin/form_templates/index.html.erb index 1b133fb564..3416ab166c 100644 --- a/app/views/admin/form_templates/index.html.erb +++ b/app/views/admin/form_templates/index.html.erb @@ -31,7 +31,10 @@ <%= link_to 'Ver', admin_form_template_path(template), class: 'btn btn-sm btn-info' %> <%= link_to 'Editar', edit_admin_form_template_path(template), class: 'btn btn-sm btn-warning' %> - <%= link_to 'Deletar', admin_form_template_path(template), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> + <%= button_to 'Deletar', admin_form_template_path(template), method: :delete, + onclick: "return confirm('Tem certeza que deseja apagar este template?');", + class: 'btn btn-link text-danger p-0 border-0 text-decoration-none align-baseline', + form: { style: 'display:inline-block;' } %> <% end %> From fa3bf4c8ce715b10d04723b1b605eb2776e25dbf Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Tue, 9 Dec 2025 00:24:48 -0300 Subject: [PATCH 38/63] feat/adicionar campo ao editar template ja existente --- app/javascript/application.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/javascript/application.js b/app/javascript/application.js index 582a4a6e08..0c7e9d5b90 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,4 +1,12 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" import "controllers" -import "bootstrap" \ No newline at end of file +// import "bootstrap" + +window.add_fields = function(link, association, content) { + var new_id = new Date().getTime(); + var regexp = new RegExp("new_" + association, "g"); + + // Insere o novo campo ANTES do botão clicado + link.insertAdjacentHTML('beforebegin', content.replace(regexp, new_id)); +} \ No newline at end of file From 97debf04ca4048ad3a12e3bf06769743e130ae07 Mon Sep 17 00:00:00 2001 From: Maria Carolina Date: Tue, 9 Dec 2025 00:27:50 -0300 Subject: [PATCH 39/63] fix/retirada do botao de voltar do dashboard --- app/views/admin/dashboard/index.html.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index edda358043..ccb83e2db8 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -79,5 +79,4 @@ - <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %>
From 4e7dc573028616eedb31b7d91846eca96b5a640d Mon Sep 17 00:00:00 2001 From: isaaacsc Date: Tue, 9 Dec 2025 14:56:12 -0300 Subject: [PATCH 40/63] mise para isaac --- mise.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 mise.toml diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000000..7f64b26fd7 --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +ruby = "3.4.7" From 7388e385e87004576be70427a989837e5e02ebe2 Mon Sep 17 00:00:00 2001 From: isaaacsc Date: Tue, 9 Dec 2025 15:57:32 -0300 Subject: [PATCH 41/63] botoes de visualizar form e voltar atualizados --- app/views/student/forms/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/student/forms/show.html.erb b/app/views/student/forms/show.html.erb index 64f787eabd..b46d828374 100644 --- a/app/views/student/forms/show.html.erb +++ b/app/views/student/forms/show.html.erb @@ -6,7 +6,7 @@

<%= @form.title %>

<%= @form.klass.name %>

- <%= link_to 'Voltar', student_forms_path, class: 'btn btn-secondary' %> + <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %>git
From 3781916404fd6dba21ddb06d18586bd011ec90df Mon Sep 17 00:00:00 2001 From: bg1777 Date: Tue, 9 Dec 2025 17:12:37 -0300 Subject: [PATCH 42/63] fix: rotas consertadas na tela de respostas e correcoes menores na tela de imports --- app/views/admin/dashboard/index.html.erb | 48 +++++++------ app/views/admin/forms/index.html.erb | 1 - app/views/admin/forms/show.html.erb | 87 +++++++++++++++--------- app/views/admin/imports/index.html.erb | 19 ------ app/views/admin/users/destroy.html.erb | 2 - app/views/admin/users/edit.html.erb | 2 - app/views/admin/users/index.html.erb | 36 ---------- app/views/admin/users/show.html.erb | 2 - app/views/admin/users/update.html.erb | 2 - app/views/student/forms/show.html.erb | 2 +- class_members4.json | 31 +++++++++ 11 files changed, 111 insertions(+), 121 deletions(-) delete mode 100644 app/views/admin/users/destroy.html.erb delete mode 100644 app/views/admin/users/edit.html.erb delete mode 100644 app/views/admin/users/index.html.erb delete mode 100644 app/views/admin/users/show.html.erb delete mode 100644 app/views/admin/users/update.html.erb create mode 100644 class_members4.json diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index ccb83e2db8..71c4d6f7ff 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -42,8 +42,6 @@
<%= link_to 'Acessar', admin_form_templates_path, class: 'btn btn-primary' %>
- - -
+ \ No newline at end of file diff --git a/app/views/admin/forms/index.html.erb b/app/views/admin/forms/index.html.erb index e57fe1b1b0..7cf3ebd5bd 100644 --- a/app/views/admin/forms/index.html.erb +++ b/app/views/admin/forms/index.html.erb @@ -54,7 +54,6 @@ <% if form.published? %> <%= button_to 'Fechar', close_admin_form_path(form), method: :patch, class: 'btn btn-sm btn-danger', form_class: 'd-inline' %> <% end %> - <%= link_to 'Deletar', admin_form_path(form), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-outline-danger' %> <% end %> diff --git a/app/views/admin/forms/show.html.erb b/app/views/admin/forms/show.html.erb index 1e89ded70f..c3f1a28d06 100644 --- a/app/views/admin/forms/show.html.erb +++ b/app/views/admin/forms/show.html.erb @@ -1,35 +1,60 @@ - + -

Respostas

-
- - - - - - - - - - - <% @form.form_responses.each do |response| %> +
+
+
+

<%= @form.title %>

+

+ Turma: <%= @form.klass.name %> | + Template: <%= @form.form_template.name %> +

+
+
+ <%= link_to 'Voltar', admin_forms_path, class: 'btn btn-secondary' %> + <% if @form.draft? %> + <%= button_to 'Publicar', publish_admin_form_path(@form), method: :patch, class: 'btn btn-success' %> + <% elsif @form.published? %> + <%= button_to 'Fechar', close_admin_form_path(@form), method: :patch, class: 'btn btn-danger' %> + <% end %> +
+
+ + +

Respostas

+
+
AlunoStatusRespondido emAções
+ - - - - + + + + - <% end %> - -
<%= response.user.name %> - <% if response.completed? %> - Respondido - <% else %> - Pendente - <% end %> - <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> - <% if response.completed? %> - <%= link_to 'Ver Respostas', view_response_admin_form_path(@form, response_id: response.id), class: 'btn btn-sm btn-info' %> - <% end %> - AlunoStatusRespondido emAções
+ + + <% @form.form_responses.each do |response| %> + + <%= response.user.name %> + + <% if response.completed? %> + Respondido + <% else %> + Pendente + <% end %> + + <%= response.submitted_at.present? ? l(response.submitted_at, format: :short) : '-' %> + + <% if response.completed? %> + <%= link_to 'Ver Respostas', view_response_admin_form_path(@form, response_id: response.id), class: 'btn btn-sm btn-info' %> + <% end %> + + + <% end %> + + +
+ +
+ <%= link_to 'Voltar', admin_forms_path, class: 'btn btn-secondary' %> +
diff --git a/app/views/admin/imports/index.html.erb b/app/views/admin/imports/index.html.erb index 9a17442a7f..dd63eb1960 100644 --- a/app/views/admin/imports/index.html.erb +++ b/app/views/admin/imports/index.html.erb @@ -6,25 +6,6 @@ <%= link_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> - - <% if notice.present? %> - - <% end %> - - <% if alert.present? %> - - <% end %> -
diff --git a/app/views/admin/users/destroy.html.erb b/app/views/admin/users/destroy.html.erb deleted file mode 100644 index fa3ee39e77..0000000000 --- a/app/views/admin/users/destroy.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Admin::Users#destroy

-

Find me in app/views/admin/users/destroy.html.erb

diff --git a/app/views/admin/users/edit.html.erb b/app/views/admin/users/edit.html.erb deleted file mode 100644 index fe0288b5ad..0000000000 --- a/app/views/admin/users/edit.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Admin::Users#edit

-

Find me in app/views/admin/users/edit.html.erb

diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb deleted file mode 100644 index e5159a28d9..0000000000 --- a/app/views/admin/users/index.html.erb +++ /dev/null @@ -1,36 +0,0 @@ - - -
-

Gerenciar Usuários

- - - - - - - - - - - - - - <% @users.each do |user| %> - - - - - - - - - <% end %> - -
IDNomeEmailRoleCriado emAções
<%= user.id %><%= user.name %><%= user.email %><%= user.role %><%= user.created_at.strftime("%d/%m/%Y") %> - <%= link_to 'Ver', admin_user_path(user), class: 'btn btn-sm btn-info' %> - <%= link_to 'Editar', edit_admin_user_path(user), class: 'btn btn-sm btn-warning' %> - <%= link_to 'Deletar', admin_user_path(user), method: :delete, data: { confirm: 'Tem certeza?' }, class: 'btn btn-sm btn-danger' %> -
- - <%= link_to 'Dashboard', admin_root_path, class: 'btn btn-secondary' %> -
diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb deleted file mode 100644 index 2283782643..0000000000 --- a/app/views/admin/users/show.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Admin::Users#show

-

Find me in app/views/admin/users/show.html.erb

diff --git a/app/views/admin/users/update.html.erb b/app/views/admin/users/update.html.erb deleted file mode 100644 index e9ab64e08d..0000000000 --- a/app/views/admin/users/update.html.erb +++ /dev/null @@ -1,2 +0,0 @@ -

Admin::Users#update

-

Find me in app/views/admin/users/update.html.erb

diff --git a/app/views/student/forms/show.html.erb b/app/views/student/forms/show.html.erb index b46d828374..52bd1f533e 100644 --- a/app/views/student/forms/show.html.erb +++ b/app/views/student/forms/show.html.erb @@ -6,7 +6,7 @@

<%= @form.title %>

<%= @form.klass.name %>

- <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %>git + <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %>
diff --git a/class_members4.json b/class_members4.json new file mode 100644 index 0000000000..7c0e55f38b --- /dev/null +++ b/class_members4.json @@ -0,0 +1,31 @@ +[ + { + "code": "CIC0189", + "name": "Projeto e Analise de Algoritmos", + "class": { + "classCode": "TA", + "semester": "2025.2", + "time": "24T23" + }, + "dicente": [ + { + "nome": "Bernardo Gomes Rodrigues", + "curso": "ENGENHARIA DA COMPUTAÇÃO/CIC", + "matricula": "231034191", + "usuario": "231034191", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "bgrodrigues2005@gmail.com" + }, + { + "nome": "Ana Clara Jordao Perna", + "curso": "CIÊNCIA DA COMPUTAÇÃO/CIC", + "matricula": "190084006", + "usuario": "190084006", + "formacao": "graduando", + "ocupacao": "dicente", + "email": "acjpjvjp@gmail.com" + } + ] + } +] \ No newline at end of file From 155d83290854bad0e3d096aec468f52554fb5c27 Mon Sep 17 00:00:00 2001 From: isaaacsc Date: Tue, 9 Dec 2025 21:50:29 -0300 Subject: [PATCH 43/63] student/forms/show --- app/assets/stylesheets/application.css | 235 +++++++++++++++++++++++-- app/views/student/forms/show.html.erb | 145 +++++++++------ 2 files changed, 320 insertions(+), 60 deletions(-) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 903799e18e..44658d98b5 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,10 +1,225 @@ -/* - * This is a manifest file that'll be compiled into application.css. - * - * With Propshaft, assets are served efficiently without preprocessing steps. You can still include - * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard - * cascading order, meaning styles declared later in the document or manifest will override earlier ones, - * depending on specificity. - * - * Consider organizing styles into separate files for maintainability. - */ +/* --------------------------------------------------- + GLOBAL DESIGN SYSTEM +--------------------------------------------------- */ + +:root { + /* Marine blue palette */ + --blue-900: #072b44; + --blue-800: #0a3d62; + --blue-700: #0f527f; + --blue-600: #1d6fa1; + --blue-300: #aed7f3; + --blue-100: #e8f5ff; + + /* Light green palette */ + --green-500: #78e08f; + --green-600: #62c977; + --green-100: #e8fbe9; + + /* Neutrals */ + --gray-900: #1c1f21; + --gray-700: #4b5258; + --gray-500: #7d858c; + --gray-200: #e5e9ef; + --gray-100: #f7f9fb; + + --radius-lg: 22px; + --radius-md: 14px; + --shadow-1: 0 4px 14px rgba(0, 0, 0, 0.08); + --shadow-2: 0 10px 30px rgba(0, 0, 0, 0.12); +} + +/* --------------------------------------------------- + PAGE WRAPPER +--------------------------------------------------- */ + +.page-wrapper { + background: var(--gray-100); + min-height: 100vh; +} + +/* --------------------------------------------------- + HERO +--------------------------------------------------- */ + +.hero { + padding: 80px 0; + background: linear-gradient(135deg, var(--blue-900), var(--blue-600)); + color: white; +} + +.hero-title { + font-size: 2.8rem; + font-weight: 700; +} + +.hero-subtitle { + opacity: 0.85; + font-size: 1.3rem; +} + +.hero-back-btn { + margin-top: 22px; + display: inline-block; + padding: 10px 22px; + border-radius: var(--radius-md); + background: var(--green-500); + color: var(--blue-900) !important; + font-weight: 600; + text-decoration: none; + transition: 0.2s; +} + +.hero-back-btn:hover { + background: var(--green-600); +} + +/* --------------------------------------------------- + CONTENT +--------------------------------------------------- */ + +.content-section { + margin-top: -35px; + padding-bottom: 60px; +} + +.shadow-layer { + box-shadow: var(--shadow-1); +} + +/* Cards */ +.info-card, +.responses-card, +.status-card { + border-radius: var(--radius-lg); + background: white; + padding: 32px; + margin-bottom: 40px; +} + +.section-title { + font-weight: 700; + color: var(--blue-800); + margin-bottom: 24px; + letter-spacing: -0.5px; +} + +/* --------------------------------------------------- + INFO GRID +--------------------------------------------------- */ + +.info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 24px; +} + +.info-label { + font-size: 0.9rem; + color: var(--gray-700); + font-weight: 600; + margin-bottom: 4px; +} + +.info-value { + font-size: 1.15rem; + color: var(--gray-900); +} + +/* --------------------------------------------------- + STATUS CARDS +--------------------------------------------------- */ + +.status-card { + display: flex; + align-items: center; + gap: 18px; +} + +.status-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.4rem; + font-weight: bold; +} + +.completed .status-icon { + background: var(--green-100); + color: var(--green-600); +} + +.pending .status-icon { + background: #fff3cd; + color: #d39e00; +} + +.status-title { + font-size: 1.2rem; + font-weight: 700; +} + +.status-date { + font-size: 0.95rem; + color: var(--gray-700); +} + +/* Primary action */ +.primary-action-btn { + margin-left: auto; + padding: 10px 26px; + border-radius: var(--radius-md); + background: var(--green-500); + color: var(--blue-900); + font-weight: 600; + text-decoration: none; + transition: 0.2s; +} + +.primary-action-btn:hover { + background: var(--green-600); +} + +/* --------------------------------------------------- + TABLE +--------------------------------------------------- */ + +.modern-table { + width: 100%; + border-collapse: separate; + border-spacing: 0 10px; +} + +.modern-table thead tr { + background: var(--blue-900); + color: white; +} + +.modern-table th { + padding: 14px 16px; + font-weight: 600; + letter-spacing: 0.5px; +} + +.modern-table tbody tr { + background: white; + border-radius: var(--radius-md); + box-shadow: var(--shadow-1); +} + +.modern-table td { + padding: 16px; + color: var(--gray-900); +} + +/* Rounded table rows */ +.modern-table tbody tr td:first-child { + border-radius: var(--radius-md) 0 0 var(--radius-md); +} + +.modern-table tbody tr td:last-child { + border-radius: 0 var(--radius-md) var(--radius-md) 0; +} diff --git a/app/views/student/forms/show.html.erb b/app/views/student/forms/show.html.erb index 52bd1f533e..2efc48286b 100644 --- a/app/views/student/forms/show.html.erb +++ b/app/views/student/forms/show.html.erb @@ -1,57 +1,102 @@ -
-
-
-

<%= @form.title %>

-

<%= @form.klass.name %>

-
- <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> -
- -
-
-
Informações
-

Descrição: <%= @form.description %>

-

- Prazo: - <% if @form.due_date.present? %> - <%= l(@form.due_date, format: :long) %> - <% else %> - Sem prazo - <% end %> -

-
-
+
- <% if @form_response&.completed? %> -
- ✓ Formulário respondido em: <%= l(@form_response.submitted_at, format: :long) %> -
+ +
+
+
-

Suas Respostas

-
- - - - - - - - - <% @form_response.form_answers.each do |answer| %> - - - - - <% end %> - -
PerguntaSua Resposta
<%= answer.form_template_field.label %><%= answer.answer %>
+

<%= @form.title %>

+

<%= @form.klass.name %>

+ + <%= link_to 'Voltar', root_path, class: 'hero-back-btn' %> + +
- <% else %> -
- Este formulário ainda não foi respondido. - <%= link_to 'Responder agora', answer_student_form_path(@form), class: 'btn btn-primary' %> +
+ + + +
+ + +
+

Informações

+ +
+
+

Descrição

+

<%= @form.description.presence || "—" %>

+
+ +
+

Prazo

+

+ <% if @form.due_date.present? %> + <%= l(@form.due_date, format: :long) %> + <% else %> + Sem prazo + <% end %> +

+
+
- <% end %> + + + + <% if @form_response&.completed? %> + +
+
+ +
+

Formulário respondido

+

<%= l(@form_response.submitted_at, format: :long) %>

+
+
+ +
+

Suas Respostas

+ +
+ + + + + + + + + + <% @form_response.form_answers.each do |answer| %> + + + + + <% end %> + +
PerguntaResposta
<%= answer.form_template_field.label %><%= answer.answer.presence || "—" %>
+
+
+ + <% else %> + +
+
!
+ +
+

Formulário pendente

+

Você ainda não respondeu este formulário.

+
+ + <%= link_to 'Responder agora', + answer_student_form_path(@form), + class: 'primary-action-btn' %> +
+ + <% end %> + +
+
From abfec343534ea798d73ea7afa9fc2f00d04f5f56 Mon Sep 17 00:00:00 2001 From: isaaacsc Date: Tue, 9 Dec 2025 22:00:08 -0300 Subject: [PATCH 44/63] design layouts and home --- app/views/home/index.html.erb | 215 ++++++++++++++--------- app/views/layouts/application.html.erb | 65 ++++++- app/views/layouts/mailer.html.erb | 76 ++++++++- app/views/student/forms/answer.html.erb | 218 +++++++++++++++--------- 4 files changed, 401 insertions(+), 173 deletions(-) diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 0e42e60e41..44cba478f4 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,94 +1,149 @@ -
-
-
-

Bem-vindo, <%= current_user.name %>!

-

Email: <%= current_user.email %>

+
+ +
+ + +
+
+
+

Bem-vindo, <%= current_user.name %>!

+

Email: <%= current_user.email %>

+
+ <%= button_to 'Sair', destroy_user_session_path, + method: :delete, + class: 'btn btn-light px-4 py-2 fw-semibold', + style: "border-radius: 12px;" %> +
- <%= button_to 'Sair', destroy_user_session_path, method: :delete, class: 'btn btn-danger' %> -
- -
-

📋 Formulários Pendentes

- - <% if @pending_forms.any? %> -
- <% @pending_forms.each do |form| %> -
-
-
-
<%= form.title %>
-

- Turma: <%= form.klass.name %> -

-

- <%= truncate(form.description, length: 100) %> -

- - <% if form.due_date.present? %> -

- - Prazo: - - <%= l(form.due_date, format: :short) %> - - + + +

+ +
+

📋 Formulários Pendentes

+
+ + <% if @pending_forms.any? %> + +
+ + <% @pending_forms.each do |form| %> +
+
+ +
+ + +
<%= form.title %>
+ + +

+ Turma: <%= form.klass.name %> +

+ + +

+ <%= truncate(form.description, length: 100) %>

- <% end %> -
- <%= link_to 'Responder Formulário', answer_student_form_path(form), class: 'btn btn-primary btn-sm' %> + + <% if form.due_date.present? %> +

+ + Prazo: + + <%= l(form.due_date, format: :short) %> + + +

+ <% end %> + +
+ <%= link_to 'Responder Formulário', + answer_student_form_path(form), + class: 'btn text-white fw-semibold', + style: "background:#0A3D91; border-radius: 10px;" %> +
+
-
- <% end %> -
- <% else %> -
- ✓ Parabéns! Você respondeu todos os formulários pendentes. -
- <% end %> -
+ <% end %> - -
-

✓ Formulários Respondidos

- - <% if @completed_forms.any? %> -
- - - - - - - - - - - <% @completed_forms.each do |form| %> + + + <% else %> + +
+ ✓ Parabéns! + Você respondeu todos os formulários pendentes. +
+ + <% end %> + + + + + +
+ +

✓ Formulários Respondidos

+ + <% if @completed_forms.any? %> + +
+
TítuloTurmaRespondido emAções
+ + - - - - + + + + - <% end %> - -
<%= form.title %><%= form.klass.name %> - <% response = form.form_responses.find_by(user: current_user) %> - <%= l(response.submitted_at, format: :short) if response&.submitted_at %> - - <%= link_to 'Ver', student_form_path(form), class: 'btn btn-sm btn-info' %> - TítuloTurmaRespondido emAções
-
- <% else %> -
- Nenhum formulário respondido ainda. -
- <% end %> + + + + <% @completed_forms.each do |form| %> + + <%= form.title %> + <%= form.klass.name %> + + <% response = form.form_responses.find_by(user: current_user) %> + <%= l(response.submitted_at, format: :short) if response&.submitted_at %> + + + <%= link_to 'Ver', + student_form_path(form), + class: 'btn btn-sm text-white', + style: "background:#5fcf7f; border-radius:8px;" %> + + + <% end %> + + + +
+ + <% else %> + +
+ Nenhum formulário respondido ainda. +
+ + <% end %> + + +
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index adc3733b0a..ededd90659 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -2,27 +2,78 @@ <%= content_for(:title) || "Projeto Camaar" %> + + <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= yield :head %> - <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> - <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %> - - <%# Includes all stylesheet files in app/assets/stylesheets %> <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> - - <%= yield %> + + + + + + +
+ <%= yield %> +
+ + +
+
+ Projeto Camaar • <%= Time.zone.now.year %> +
+
+ diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index e825dcc70f..1a6ce69420 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -2,12 +2,80 @@ - - - <%= yield %> + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + Projeto Camaar + +
+ <%= yield %> +
+

+ Enviado automaticamente pelo sistema Projeto Camaar. +

+

+ © <%= Time.zone.now.year %> Projeto Camaar +

+
+ +
+ + diff --git a/app/views/student/forms/answer.html.erb b/app/views/student/forms/answer.html.erb index 6b6e036ff4..ee6b92e4ae 100644 --- a/app/views/student/forms/answer.html.erb +++ b/app/views/student/forms/answer.html.erb @@ -1,38 +1,60 @@ -
-
+
+ + +
-

<%= @form.title %>

-

<%= @form.klass.name %>

+

<%= @form.title %>

+

<%= @form.klass.name %>

- <%= link_to 'Voltar', root_path, class: 'btn btn-secondary' %> + + <%= link_to '← Voltar', root_path, + class: 'btn btn-outline-secondary btn-lg rounded-pill px-4' %>
- <% if @form.description.present? %> -
- Instruções:
- <%= simple_format(@form.description) %> -
- <% end %> + +
- <% if @form.due_date.present? %> -
- ⏰ Prazo: <%= l(@form.due_date, format: :long) %> -
- <% end %> + <% if @form.description.present? %> +
+
+
📘 Instruções
+
<%= simple_format(@form.description) %>
+
+
+ <% end %> + + <% if @form.due_date.present? %> +
+
+
+ ⏰ Prazo: <%= l(@form.due_date, format: :long) %> +
+
+
+ <% end %> + +
<% if @form_response.form_answers.empty? %> -
+ +
Erro: Não há campos para preencher neste formulário.
+ <% else %> - <%= form_with(model: @form_response, local: true, url: submit_answer_student_form_path(@form), method: :post) do |form| %> - + + <%= form_with(model: @form_response, + local: true, + url: submit_answer_student_form_path(@form), + method: :post, + class: "mt-4") do |form| %> + <% if @form_response.errors.any? %> -
-

<%= pluralize(@form_response.errors.count, "erro") %> encontrado:

-