diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..03a268b8ba --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/__pycache__ +**/.venv +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md 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 100755 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 100755 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 100755 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 100755 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 100755 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 100755 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 100755 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 100755 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 100755 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..3b47f2e4f8 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.9 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..4ba3d017fd --- /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 camaar_g1 . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name camaar_g1 camaar_g1 + +# 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.3.9 +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..03a0aba365 --- /dev/null +++ b/Gemfile @@ -0,0 +1,68 @@ +source "https://rubygems.org" + +# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" +gem "rails", "~> 8.0.2", ">= 8.0.2.1" +# 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 + +# Testing gems +group :test do + # Rails default system testing + gem "capybara" + gem "selenium-webdriver" + + # Cucumber support + gem "cucumber-rails", require: false + gem "database_cleaner-active_record" +end \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..aedc81f092 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,449 @@ +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) + bcrypt_pbkdf (1.1.1-arm64-darwin) + bcrypt_pbkdf (1.1.1-x86_64-darwin) + 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-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 (5.1.3) + 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-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + 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-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-darwin) + 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-arm64-darwin) + sqlite3 (2.8.0-x86_64-darwin) + 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-arm64-darwin) + thruster (0.1.16-x86_64-darwin) + 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 + arm64-darwin + x86_64-darwin + x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + bootsnap + brakeman + capybara + cucumber-rails + database_cleaner-active_record + debug + importmap-rails + jbuilder + kamal + propshaft + puma (>= 5.0) + rails (~> 8.0.2, >= 8.0.2.1) + 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.7.1 diff --git a/README.Docker.md b/README.Docker.md new file mode 100644 index 0000000000..ffca340863 --- /dev/null +++ b/README.Docker.md @@ -0,0 +1,17 @@ +### Building and running your application + +When you're ready, start your application by running: +`docker compose up --build`. + +### Deploying your application to the cloud + +First, build your image, e.g.: `docker build -t myapp .`. +If your cloud uses a different CPU architecture than your development +machine (e.g., you are on a Mac M1 and your cloud provider is amd64), +you'll want to build the image for that platform, e.g.: +`docker build --platform=linux/amd64 -t myapp .`. + +Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. + +Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) +docs for more detail on building and pushing. \ No newline at end of file diff --git a/README.md b/README.md index 9d7fe1bf53..7058847ca8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,287 @@ -# CAMAAR -Sistema para avaliação de atividades acadêmicas remotas do CIC +# 📝 Wiki do Projeto – Sprint 1: Etapa 1 + +**Grupo 1 – Engenharia de Software** +**Integrantes:** + +| Nome | Matrícula | +| :---------- | :-------- | +| Caroline | 232050975 | +| Célio | 211010350 | +| Luís Filipe | 190091975 | +| Mário | 231035778 | + +# 📌 Nome do Projeto + +**CAMAAR – Sistema para avaliação de atividades acadêmicas remotas do CIC** + +# 📌 Escopo do Projeto + +O sistema **CAMAAR** tem como objetivo auxiliar na avaliação acadêmica de atividades, tarefas e outras atividades remotas do CIC. +O projeto contempla funcionalidades de cadastro de usuários, redefinição de senha, importação de base de dados do SIGAA, visualização de formulários, criação de formulários, criação de templates para formulários e download de resultados dos relatórios. + +# 🔰 Papéis na Sprint 1 + +● Especificar os cenários BDD das histórias de usuário usando o +Cucumber. +Responsáveis: Caroline, Célio, Luís Filipe e Mário. + +● Abrir uma Pull Request com as especificações dos testes de aceitação +(BDD) no repositório principal. +Responsável: Mário + +● Entregar arquivo .txt contendo um link para o repositório, o nome e a +matrícula dos integrantes. +Responsável: Mário + +● Criar um arquivo Markdown como Wiki, contendo +as informações sobre a Sprint 1. +Responsáveis: Caroline, Célio, Luís Filipe e Mário. + + +## 🧑‍💼 Scrum Master + +Caroline, Célio, Luís Filipe e Mário. + +## 🧑‍💻 Product Owner + +Caroline, Célio, Luís Filipe e Mário. + +# Quais funcionalidades serão desenvolvidas? + +Nesta Sprint 1: 1° Etapa os integrantes irão Especificar os cenários BDD de acordo com as histórias de usuários, cada cenário BDD deve possuir pelo menos um cenário feliz e um triste. +As US desta sprint são: +[02 - Edição e deleção de templates](https://github.com/mariosantos-05/CAMAAR-G1/issues/2) +[03 - Visualização dos templates criados](https://github.com/mariosantos-05/CAMAAR-G1/issues/3) +[04 - Importar dados do SIGAA](https://github.com/mariosantos-05/CAMAAR-G1/issues/4) +[05 - Responder formulário](https://github.com/mariosantos-05/CAMAAR-G1/issues/5) +[06 - Cadastrar usuários do sistema](https://github.com/mariosantos-05/CAMAAR-G1/issues/6) +[07 - Gerar relatório do Administrador](https://github.com/mariosantos-05/CAMAAR-G1/issues/7) +[08 - Criar template de formulário](https://github.com/mariosantos-05/CAMAAR-G1/issues/8) +[09 - Criar formulários de avaliação](https://github.com/mariosantos-05/CAMAAR-G1/issues/9) +[10 - Sistema de login](https://github.com/mariosantos-05/CAMAAR-G1/issues/10) +[11 - Sistema de definição de senha](https://github.com/mariosantos-05/CAMAAR-G1/issues/11) +[12 - Sistema de gerenciamento por departamento](https://github.com/mariosantos-05/CAMAAR-G1/issues/12) +[13 - Redefinição de senha](https://github.com/mariosantos-05/CAMAAR-G1/issues/13) +[14 - Atualizar base de dados com os dados do SIGAA](https://github.com/mariosantos-05/CAMAAR-G1/issues/14) +[15 - Visualização de formlários para responder](https://github.com/mariosantos-05/CAMAAR-G1/issues/15) +[16 - Visualização de Resultado dos Formulários](https://github.com/mariosantos-05/CAMAAR-G1/issues/16) +[17 - Criação de formulário para docentes ou discentes](https://github.com/mariosantos-05/CAMAAR-G1/issues/17) + +# Quais serão as regras de negócio para cada funcionalidade? +## Regras de Negócio - Redefinição de Senha +(Issue 13: "Quero redefinir uma senha... a partir do e-mail recebido...") + +| Código | Descrição | +|------------|---------------------------------------------------------------------------------------------------------------------| +| RN-RS-01 | O usuário deve informar um e-mail cadastrado para solicitar a redefinição de senha. | +| RN-RS-02 | Caso o e-mail **não exista** no sistema, exibir mensagem de sucesso genérica ("Se este e-mail estiver cadastrado...") para evitar enumeração de usuários. | +| RN-RS-03 | Se o e-mail existir, gerar um **token único** de redefinição e enviá-lo por e-mail ao usuário. | +| RN-RS-04 | O token de redefinição deve expirar em **60 minutos**. | +| RN-RS-05 | O token é de **uso único** – após a redefinição da senha, torna-se inválido. | +| RN-RS-06 | A nova senha deve obedecer às regras de complexidade do sistema (ver RN-DS-02). | + +## Regras de Negócio - Definição de Senha (Primeiro Acesso) +(Issue 11: "Quero definir uma senha... a partir do e-mail do sistema de solicitação de cadastro...") + +| Código | Descrição | +|------------|---------------------------------------------------------------------------------------------------------------------| +| RN-DS-01 | O link de definição de senha é de **uso único**. | +| RN-DS-02 | A senha deve ter no mínimo **8 caracteres**, contendo letras maiúsculas, minúsculas e números. | +| RN-DS-03 | Os campos **"Nova Senha"** e **"Confirmar Senha"** devem ser idênticos; caso contrário, exibir erro. | +| RN-DS-04 | A conta só muda de status **"Pendente" → "Ativo"** após a definição bem-sucedida da senha (cadastro efetivado). | + +## Regras de Negócio - Cadastro de Usuários via Importação +(Issue 06: "Quero cadastrar participantes... ao importar dados de usuarios novos...") + +| Código | Descrição | +|------------|---------------------------------------------------------------------------------------------------------------------| +| RN-C-01 | A funcionalidade de importação só está acessível para usuários com perfil **"Admin"**. | +| RN-C-02 | Aceitar **apenas** arquivos no formato **.json**. Qualquer outro formato (ex: .pdf, .csv) → "Formato de arquivo inválido". | +| RN-C-03 | O arquivo deve ser um **JSON válido** (sintaxe correta). Erros de sintaxe → "O arquivo não é um JSON válido". | +| RN-C-04 | Cada objeto de usuário deve conter **obrigatoriamente** as chaves `"matricula"` e `"email"`. Falta de qualquer uma → rejeitar importação. | +| RN-C-05 | As chaves devem ter tipos corretos (ex: `"matricula"` deve ser número/string numérica válida). | +| RN-C-06 | Se a matrícula **não existir** no banco, criar novo usuário com status **"Pendente"**. | +| RN-C-07 | **Não** disparar automaticamente o e-mail de definição de senha ao criar usuário "Pendente" via importação. | +| RN-C-08 | Se a matrícula já existir, **não criar duplicata**. | +| RN-C-09 | Se a matrícula já existir, **atualizar** os dados do usuário (ex: atualizar e-mail se diferente no JSON). | + +## Regras de Negócio - Sistema de Login +(Issue 10: "Quero acessar o sistema utilizando um e-mail ou matrícula...") + +| Código | Descrição | +|------------|---------------------------------------------------------------------------------------------------------------------| +| RN-L-01 | O usuário deve poder se autenticar usando **e-mail** ou **número de matrícula** no mesmo campo de login. | +| RN-L-02 | Os campos **"E-mail ou Matrícula"** e **"Senha"** são de preenchimento obrigatório. | +| RN-L-03 | Em caso de e-mail/matrícula ou senha incorretos, exibir mensagem genérica **"E-mail ou senha inválidos"** (nunca informar qual dos dois está errado). | +| RN-L-04 | Usuários com perfil **"Admin"** devem ter a opção **"Gerenciamento"** exibida no menu lateral. | +| RN-L-05 | Usuários com perfil diferente de "Admin" (ex: Aluno, Professor) **não devem** ver a opção "Gerenciamento". | +| RN-L-06 | O login só é permitido se o status da conta do usuário for **"Ativo"** (ou seja, após a primeira definição de senha). | + +## Regras de Negócio - Criar Formulário (Template de Questões) +(Issue 09: "Quero criar um template de formulário contendo as questões do formulário...") + +| Código | Descrição | +|------------|----------------------------------------------------------------------------------------------------| +| RN-CF-01 | **Confirmação de Exclusão**: A ação de deletar um template exige confirmação explícita (pop-up) antes de ser executada. | +| RN-CF-02 | **Tipos de Questões**: O sistema deve permitir incluir e persistir diferentes tipos de perguntas (múltipla escolha, discursiva, etc.) no mesmo template. | + +## Regras de Negócio - Criar Template de Formulário +(Issue 17: "Quero escolher criar um formulário para os docentes ou os discentes...") + +| Código | Descrição | +|-----------|----------------------------------------------------------------------------------------------------| +| RN-CTF-01 | **Obrigatoriedade de Título**: Não é permitido criar ou salvar um template com o campo "Nome/Título" vazio. | +| RN-CTF-02 | **Imutabilidade Histórica**: A edição de um template **não pode** alterar a estrutura ou os dados de formulários já respondidos (instâncias antigas permanecem inalteradas). | + +## Regras de Negócio - Visualizar Templates +(Issue 03: "Quero visualizar os templates criados") + +| Código | Descrição | +|-----------|----------------------------------------------------------------------------------------------------| +| RN-VT-01 | **Condicionalidade de Campos**: O campo "Turma" deve ser **obrigatório** quando o público-alvo for "Discentes" e **oculto** quando for "Docentes". | +| RN-VT-02 | **Segmentação de Envio**: O formulário gerado deve ser enviado **apenas** para os usuários vinculados à turma selecionada. | + +## Regras de Negócio - Editar e Deletar Template +(Issue 02: "Quero editar e/ou deletar um template que eu criei sem afetar...") + +| Código | Descrição | +|-----------|----------------------------------------------------------------------------------------------------| +| RN-ET-01 | **Estado de Lista Vazia**: Quando não houver templates cadastrados, exibir a mensagem "Nenhum template foi criado" em vez de uma lista em branco. | +| RN-ET-02 | **Ações de Gerenciamento**: Cada item da lista deve exibir botões individuais de **"Editar"** e **"Deletar"**. | + +## Regras de Negócio - Importação de Dados do SIGAA (Apenas Adicionar) +(Issue 04: Importar dados do SIGAA) +(Quero importar dados de turmas, matérias e participantes do SIGAA caso não existam na base de dados atual) + +| Código | Descrição | +|-----------|---------------------------------------------------------------------------------------------------------------------| +| RN-IDS-13 | Para cada item do JSON, verificar a chave única (ex: matrícula do aluno ou código da disciplina):
• Se **não existir** → criar o registro.
• Se **já existir** → ignorar o item (não atualizar nem duplicar). | +| RN-IDS-14 | Esta é uma operação de **"apenas adicionar"**, usada para alimentar a base sem risco de sobrescrever dados já alterados manualmente. | +| RN-IDS-15 | Aceitar somente arquivos com extensão **.json**. Qualquer outro formato deve ser rejeitado com mensagem de erro. | +| RN-IDS-16 | O arquivo .json deve ser sintaticamente válido. Erro de sintaxe → rejeição imediata com mensagem de erro clara. | + +## Regras de Negócio - Gerenciamento de Relatórios e Resultados +(Issue 07: Gerar relatório do administrador – Quero baixar um arquivo CSV contendo os resultados de um formulário) + +| Código | Descrição | +|-----------|---------------------------------------------------------------------------------------------------------------------| +| RN-GR-01 | O acesso à página **"Gerenciamento → Resultados"** e todas as suas funcionalidades é restrito exclusivamente a usuários com papel **"Administrador"**. | +| RN-GR-02 | Usuários sem perfil Administrador **não devem ver** o link da página. Caso tentem acessar diretamente a URL, devem ser bloqueados e redirecionados ao seu dashboard. | +| RN-GR-03 | Ao solicitar o download dos resultados, o sistema deve gerar e oferecer um arquivo no formato **CSV**. | + +## Regras de Negócio - Atualizar Dados Existentes (via SIGAA) +(Issue 14: Quero atualizar a base de dados já existente com os dados atuais do SIGAA) + +| Código | Descrição | +|-----------|---------------------------------------------------------------------------------------------------------------------| +| RN-ADE-01 | Se o item (aluno, turma, etc.) do JSON **não existir** no banco, o sistema deve criá-lo. | +| RN-ADE-02 | Se o item do JSON **já existir** no banco, o sistema deve atualizar o registro existente com os dados do JSON. | +| RN-ADE-03 | O sistema **nunca** deve criar duplicatas – a ação é sempre de correção/atualização do registro existente. | +| RN-ADE-04 | Aceitar apenas arquivos com extensão **.json**. Qualquer outro formato deve ser rejeitado. | +| RN-ADE-05 | O arquivo .json deve ser sintaticamente válido. Caso contrário, a importação deve ser rejeitada. | +| RN-ADE-06 | O JSON deve conter todas as chaves obrigatórias esperadas. Se algum item estiver sem chave obrigatória (ex: matrícula), a importação deve falhar. | +| RN-ADE-07 | Após importação bem-sucedida, exibir mensagem de sucesso clara ao administrador. | +| RN-ADE-08 | Após falha na importação (qualquer motivo), exibir mensagem de erro detalhando o problema. | + +## Regras de Negócio - Gerenciamento de Turmas por Departamento +(Issue 12: Quero gerenciar somente as turmas do departamento o qual eu pertenço) + +| Código | Descrição | +|-----------|---------------------------------------------------------------------------------------------------------------------| +| RN-GTD-01 | Se um usuário (administrador de departamento ou não) tentar acessar diretamente via URL os dados de turmas de outro departamento, o sistema deve redirecioná-lo imediatamente para sua página principal (Dashboard). | + +## Regras de Negócio - Criar Formulário de Avaliação +(Issue 09: "Criar um formulário de avaliação baseado em um template para turmas selecionadas") + +| Código | Descrição | +|--------|-----------| +| RN-CFA-01 | **Seleção Obrigatória de Turmas**: Não é permitido criar formulários se nenhuma turma for selecionada; o sistema deve exibir a mensagem de erro "Nenhuma turma selecionada." | +| RN-CFA-02 | **Seleção Obrigatória de Template**: O sistema deve impedir a criação de formulários caso nenhum template seja selecionado, exibindo a mensagem "Nenhum template selecionado." | +| RN-CFA-03 | **Criação em Lote por Turma**: O sistema deve gerar individualmente um formulário para cada turma selecionada quando solicitado. | +| RN-CFA-04 | **Confirmação de Sucesso**: Após criar os formulários, o sistema deve exibir a mensagem "Formulários criados com sucesso para as turmas selecionadas." | + +## Regras de Negócio - Responder Formulário +(Issue 05: "Responder o formulário de avaliação como Participante") + +| Código | Descrição | +|--------|-----------| +| RN-RF-01 | **Disponibilidade Vinculada à Turma**: O participante só pode responder formulários das turmas em que está matriculado. | +| RN-RF-02 | **Existência de Formulário Ativo**: O sistema só permite acesso e envio se houver um formulário ativo disponível para a turma. | +| RN-RF-03 | **Validação de Campos Obrigatórios**: O sistema deve impedir o envio caso campos obrigatórios não sejam preenchidos, exibindo erro. | +| RN-RF-04 | **Registro de Respostas**: O sistema deve registrar todas as respostas submetidas pelo participante. | +| RN-RF-05 | **Confirmação de Envio**: Após envio bem-sucedido, o sistema deve exibir "Seu formulário foi enviado com sucesso." | + +## Regras de Negócio - Visualização de Formulários Pendentes +(Issue 15: "Visualizar os formulários não respondidos das turmas em que o participante está matriculado") + +| Código | Descrição | +|--------|-----------| +| RN-VFP-01 | **Exibição Apenas do que Não foi Respondido**: O sistema deve listar somente formulários pendentes. | +| RN-VFP-01 | **Segmentação por Turma Matriculada**: O participante só visualiza formulários das turmas nas quais está matriculado. | +| RN-VFP-01 | **Contador de Formulários Pendentes**: O sistema deve exibir o total de formulários pendentes com mensagem informativa. | +| RN-VFP-01 | **Estado de Lista Vazia**: Na ausência de formulários pendentes, exibir "Você não possui formulários pendentes." | + +## Regras de Negócio - Visualizar Resultados dos Formulários +(Issue 16: "Visualização dos formulários criados pelo Administrador") + +| Código | Descrição | +|--------|-----------| +| RN-VRF-01 | **Lista de Formulários Criados**: O administrador deve visualizar os formulários criados organizados por turma. | +| RN-VRF-02 | **Contador de Formulários Criados**: O sistema deve exibir o total de formulários criados, como "Existem X formulários criados." | +| RN-VRF-03 | **Acesso aos Detalhes**: O administrador deve poder visualizar perguntas, respostas e estatísticas de cada formulário selecionado. | + +## Quem ficou responsável por cada cenário BDD em relação as US/Issues? + +#[02](https://github.com/mariosantos-05/CAMAAR-G1/issues/2) Luís Filipe +#[03](https://github.com/mariosantos-05/CAMAAR-G1/issues/3) Luís Filipe +#[04](https://github.com/mariosantos-05/CAMAAR-G1/issues/4) Caroline +#[05](https://github.com/mariosantos-05/CAMAAR-G1/issues/5) Mário +#[06](https://github.com/mariosantos-05/CAMAAR-G1/issues/6) Célio +#[07](https://github.com/mariosantos-05/CAMAAR-G1/issues/7) Caroline +#[08](https://github.com/mariosantos-05/CAMAAR-G1/issues/8) Luís Filipe +#[09](https://github.com/mariosantos-05/CAMAAR-G1/issues/9) Mário +#[10](https://github.com/mariosantos-05/CAMAAR-G1/issues/10) Célio +#[11](https://github.com/mariosantos-05/CAMAAR-G1/issues/11) Célio +#[12](https://github.com/mariosantos-05/CAMAAR-G1/issues/12) (Caroline ou Luís Filipe) +#[13](https://github.com/mariosantos-05/CAMAAR-G1/issues/13) Célio +#[14](https://github.com/mariosantos-05/CAMAAR-G1/issues/14) Caroline +#[15](https://github.com/mariosantos-05/CAMAAR-G1/issues/15) Mário +#[16](https://github.com/mariosantos-05/CAMAAR-G1/issues/16) Mário +#[17](https://github.com/mariosantos-05/CAMAAR-G1/issues/17) (Caroline ou Luís Filipe) + +--- + +# 📊 Métrica Velocity da Sprint 1 + +| História/Issue | Pontos | +| ---------------- | ------------------- | +| US / #02 | 2 | +| US / #03 | 1 | +| US / #04 | 3 | +| US / #05 | 2 | +| US / #06 | 3 | +| US / #07 | 2 | +| US / #08 | 3 | +| US / #09 | 3 | +| US / #10 | 2 | +| US / #11 | 2 | +| US / #12 | 3 | +| US / #13 | 3 | +| US / #14 | 3 | +| US / #15 | 2 | +| US / #16 | 3 | +| US / #17 | 3 | +| **Total** | **Story Points** | +| **16 US/Issues** | **40 Story Points** | + +--- + +# 🌿 Política de Branching Utilizada pelo Grupo + +Sprint Branching + Feature Branching (variação do GitLab Flow): + +- A equipe cria uma branch representando a sprint a partir da main. + +- Todas as feature branches da sprint nascem a partir dela. + +- No final da sprint, tudo é consolidado e mergeado para a branch da sprint. 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..1455450e14 --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,28 @@ + + + + <%= content_for(:title) || "Camaar G1" %> + + + + <%= 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..d6cf3bbd6c --- /dev/null +++ b/app/views/pwa/manifest.json.erb @@ -0,0 +1,22 @@ +{ + "name": "CamaarG1", + "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": "CamaarG1.", + "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 100755 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/cucumber b/bin/cucumber new file mode 100755 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 100755 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 100755 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 100755 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 100755 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 100755 index 0000000000..d9ba276702 --- /dev/null +++ b/bin/kamal @@ -0,0 +1,16 @@ +#!/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__) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kamal", "kamal") diff --git a/bin/rails b/bin/rails new file mode 100755 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 100755 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 100755 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 100755 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 100755 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..72b52e0f11 --- /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 CamaarG1 + 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..5e95166c07 --- /dev/null +++ b/config/credentials.yml.enc @@ -0,0 +1 @@ +dG82hj0yB5jlVXL3V13vnZl7iIWRvVPx4qMlMg5yWkOEd6QuE6HpPOjWcx8acScjxkTLiSdpoUV3dGk+R8BTa+gyX8VkVuJM4UzcLUMQ4YlFd5craaf7jlqH4oUul0VEuFpH4387REN8JdaHIZdtal4r0+5bOAnnxzCDs2twIbSAyl13Nns0QS+/kAzj4Ge8oowivPsLtax/HloUO5Ovi+U/fUjgHZFdpfVbl0M4YA5y2HUUEEw/uRbvvi8KVM+ZE4Oa3OU0d+I5N0i4Suj+CBUOeHN//wACcP1QIbHo3y5h4NSLYJpFNkwg4MIOIcJXmJf/VJLoYsXKsohF03ioYBbbb5SCAJHX8mCHzjk5KMfbSYZMJDeOGGKZ+ZfT+dSB3dFjLKtoV9VNYTQ+Ll+Cbk4iBLn5X9M4vojcM+Q+MuPPTXEw6k2+oR8wlRt49Dez1Ar2cye9/eMRoiTsQu60GlSlDpbg9zeQ9vP/igmBOOGmv9ZIlJevO5P8--tkNmt0c6GGbUYEKp--YnPJEHyaQ1KPNB4TkhYHjA== \ 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..6188baa0b2 --- /dev/null +++ b/config/deploy.yml @@ -0,0 +1,116 @@ +# Name of your application. Used to uniquely configure containers. +service: camaar_g1 + +# Name of the container image. +image: your-user/camaar_g1 + +# 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 camaar_g1-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: + - "camaar_g1_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: 3.3.9 + # 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/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..6a19e9a592 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Docker Compose reference guide at +# https://docs.docker.com/go/compose-spec-reference/ + +# Here the instructions define your application as a service called "app". +# This service is built from the Dockerfile in the current directory. +# You can add other services your application may depend on here, such as a +# database or a cache. For examples, see the Awesome Compose repository: +# https://github.com/docker/awesome-compose +services: + app: + build: + context: . + target: final + # If your application exposes a port, uncomment the following lines and change + # the port numbers as needed. The first number is the host port and the second + # is the port inside the container. + # ports: + # - 8080:8080 + + # The commented out section below is an example of how to define a PostgreSQL + # database that your application can use. `depends_on` tells Docker Compose to + # start the database before your application. The `db-data` volume persists the + # database data between container restarts. The `db-password` secret is used + # to set the database password. You must create `db/password.txt` and add + # a password of your choosing to it before running `docker compose up`. + # depends_on: + # db: + # condition: service_healthy + # db: + # image: postgres + # restart: always + # user: postgres + # secrets: + # - db-password + # volumes: + # - db-data:/var/lib/postgresql/data + # environment: + # - POSTGRES_DB=example + # - POSTGRES_PASSWORD_FILE=/run/secrets/db-password + # expose: + # - 5432 + # healthcheck: + # test: [ "CMD", "pg_isready" ] + # interval: 10s + # timeout: 5s + # retries: 5 + # volumes: + # db-data: + # secrets: + # db-password: + # file: db/password.txt diff --git a/features/ template_formularios /criar_formulario.feature b/features/ template_formularios /criar_formulario.feature new file mode 100644 index 0000000000..0ba3dd1750 --- /dev/null +++ b/features/ template_formularios /criar_formulario.feature @@ -0,0 +1,21 @@ +Funcionalidade: Criação de formulário para docentes ou dicentes + +Cenário: Enviar formulário para Discentes de uma turma específica (Feliz) + Dado que estou criando um novo formulário a partir de um template + Quando seleciono a opção "Discentes" no campo de público-alvo + E seleciono uma turma válida "Turma A - Engenharia" + E clico no botão "Enviar" + Então o formulário deve ser enviado especificamente para os alunos daquela turma + +Cenário: Enviar formulário para Docentes (Feliz) + Dado que estou criando um novo formulário + Quando seleciono a opção "Docentes" no campo de público-alvo + Então o formulário deve ser enviado especificamente para os professores + E o campo de seleção de turma deve ficar oculto ou opcional + +Cenário: Tentativa de criar sem selecionar público (Triste) + Dado que preenchi os dados básicos do formulário + E não selecionei nem "Docente" nem "Discente" + Quando tento clicar em "Enviar" + Então o sistema deve exibir o erro "Selecione o público-alvo da avaliação" + E não deve enviar o formulário diff --git a/features/ template_formularios /criar_template.feature b/features/ template_formularios /criar_template.feature new file mode 100644 index 0000000000..dac7934038 --- /dev/null +++ b/features/ template_formularios /criar_template.feature @@ -0,0 +1,25 @@ +Funcionalidade: Criar template de formulário + +Cenário: Criação de template com dados válidos (Feliz) + Dado que estou na página de criação de templates + E preencho o campo "Nome do template" com "Avaliação 2024" + E adiciono um tipo de questão de múltipla escolha + E adiciono um tipo de questão discursiva + Quando clico no botão "Salvar Template" + Então o sistema deve exibir a mensagem "Template criado com sucesso" + E o template deve ser salvo no banco de dados + +Cenário: Tentativa de criação com título vazio (Triste) + Dado que estou na página de criação de templates + E deixo o campo "Título" vazio + E adiciono tipos de questões válidas + Quando clico no botão "Salvar Template" + Então o sistema deve exibir a mensagem de erro "O campo Título é obrigatório" + E o template não deve ser salvo + +Cenário: Tentativa de criação sem questões (Triste) + Dado que preencho o título corretamente + E não adiciono nenhum tipo de questão ao template + Quando clico no botão "Salvar Template" + Então o sistema deve exibir o erro "O template deve conter pelo menos uma questão" + E o template não deve ser salvo diff --git a/features/ template_formularios /editar_template.feature b/features/ template_formularios /editar_template.feature new file mode 100644 index 0000000000..76dd378cfb --- /dev/null +++ b/features/ template_formularios /editar_template.feature @@ -0,0 +1,23 @@ +Funcionalidade: Edição e Deleção de Templates + +Cenário: Edição de nome de template usado em formulário antigo (Feliz) + Dado que tenho o template "Avaliação A" associado a um formulário já respondido + Quando eu edito o nome do template para "Avaliação A - Revisada" + E clico no botão "Salvar" + Então o sistema deve atualizar o template para "Avaliação A - Revisada" + E os formulários antigos criados com o template "Avaliação A" não devem sofrer alteração + +Cenário: Deleção de um template existente (Feliz) + Dado que seleciono um template existente na lista + Quando clico em "Deletar" + E aparece um pop-up com o texto "Você tem certeza que deseja deletar este template?" + E confirmo a ação clicando no botão "Confirmar" + Então o template deve ser removido da lista de visualização + E não devo ver ele no gerenciamento de templates + +Cenário: Edição com caracteres inválidos no título (Triste) + Dado que estou na tela de edição de um template + Quando preencho o campo "Título" com caracteres não permitidos + Então o sistema deve exibir a mensagem "Formato de título inválido" + E aparece uma mensagem de texto indicando quais caracteres não são permitidos + E as alterações não devem ser salvas diff --git a/features/ template_formularios /visualizar_template.feature b/features/ template_formularios /visualizar_template.feature new file mode 100644 index 0000000000..439840ebc0 --- /dev/null +++ b/features/ template_formularios /visualizar_template.feature @@ -0,0 +1,13 @@ +Funcionalidade: Visualização dos templates criados + +Cenário: Visualizar lista de templates existentes (Feliz) + Dado que existem templates salvos no sistema + Quando eu acesso a página "Templates" + Então devo ver uma lista contendo todos os templates criados + E devo ver as opções de "Editar" e "Deletar" para cada template da lista + +Cenário: Visualizar lista vazia (Triste) + Dado que não existe nenhum template salvo no sistema + Quando eu acesso a página "Templates" + Então o sistema deve exibir a mensagem "Nenhum template foi criado" + E a lista de visualização deve estar vazia diff --git a/features/Gerenciamento_forms/criar_formulario.feature b/features/Gerenciamento_forms/criar_formulario.feature new file mode 100644 index 0000000000..ca024eb585 --- /dev/null +++ b/features/Gerenciamento_forms/criar_formulario.feature @@ -0,0 +1,30 @@ + Funcionalidade: Criar formulário de avaliação + Eu como Administrador + Quero criar um formulário baseado em um template para as turmas que eu escolher + A fim de avaliar o desempenho das turmas no semestre atual + + Cenário: Criar formulário de avaliação para turmas selecionadas + Dado que eu estou logado como Administrador + E que eu tenho acesso ao template de formulário "Avaliação Semestral" + Quando eu seleciono as turmas "Turma A", "Turma B" e "Turma C" + E eu escolho o template "Avaliação Semestral" + E eu clico em "Criar Formulário" + Então o sistema deve criar formulários de avaliação para as turmas selecionadas + E eu devo ver uma mensagem de sucesso "Formulários criados com sucesso para as turmas selecionadas." + + Cenário: Tentativa de criação sem selecionar turmas + Dado que eu estou logado como Administrador + E que existe um template de formulário chamado "Avaliação Semestral" + Quando eu escolho o template "Avaliação Semestral" + E eu clico em "Criar Formulário" + Então eu devo ver uma mensagem de erro "Nenhuma turma selecionada." + E nenhum formulário deve ser criado + + Cenário: Tentativa de criação sem selecionar template + Dado que eu estou logado como Administrador + E que existem turmas cadastradas no sistema + Quando eu seleciono as turmas "Turma A" e "Turma B" + E eu clico em "Criar Formulário" + Então eu devo ver uma mensagem de erro "Nenhum template selecionado." + E nenhum formulário deve ser criado + diff --git a/features/Gerenciamento_forms/responder_formulario.feature b/features/Gerenciamento_forms/responder_formulario.feature new file mode 100644 index 0000000000..c4cfbe21a6 --- /dev/null +++ b/features/Gerenciamento_forms/responder_formulario.feature @@ -0,0 +1,36 @@ + Funcionalidade: Responder formulário + Eu 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 + + Cenário: Responder formulário de avaliação para uma turma + Dado que eu estou logado como Participante + E que eu estou matriculado na turma "Turma A" + E que existe um formulário de avaliação disponível para "Turma A" + Quando eu acesso o formulário de avaliação para "Turma A" + E eu preencho o formulário com as respostas necessárias + E eu clico em "Enviar Formulário" + Então o sistema deve registrar minhas respostas para o formulário de "Turma A" + E eu devo ver uma mensagem de confirmação "Seu formulário foi enviado com sucesso." + + Cenário: Tentativa de enviar formulário sem preencher campos obrigatórios + Dado que eu estou logado como Participante + E que existe um formulário de avaliação ativo para "Turma A" + Quando eu acesso o formulário de avaliação de "Turma A" + E eu envio o formulário sem preencher os campos obrigatórios + Então eu devo ver uma mensagem de erro indicando os campos faltantes + E as respostas não devem ser registradas + + Cenário: Tentativa de enviar formulário sem preencher campos obrigatórios + Dado que eu estou logado como Participante + E que existe um formulário de avaliação ativo para "Turma A" + Quando eu acesso o formulário de avaliação de "Turma A" + E eu envio o formulário sem preencher os campos obrigatórios + Então eu devo ver uma mensagem de erro indicando os campos faltantes + E as respostas não devem ser registradas + + + + + + diff --git a/features/Gerenciamento_forms/visualizar_formulario.feature b/features/Gerenciamento_forms/visualizar_formulario.feature new file mode 100644 index 0000000000..97da17fed1 --- /dev/null +++ b/features/Gerenciamento_forms/visualizar_formulario.feature @@ -0,0 +1,19 @@ +Funcionalidade: Visualização de formulários para responder + Eu 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 + + Cenário: Visualizar formulários não respondidos para turmas matriculadas + Dado que eu estou logado como Participante + E que eu estou matriculado nas turmas "Turma A" e "Turma B" + E que existem formulários não respondidos para essas turmas + Quando eu acesso a seção de formulários disponíveis + Então eu devo ver os formulários não respondidos para "Turma A" e "Turma B" + E eu devo ver uma mensagem indicando o número de formulários pendentes "Você tem 2 formulários para responder." + + Cenário: Nenhum formulário pendente + Dado que eu estou logado como Participante + E que eu estou matriculado nas turmas "Turma A" e "Turma B" + E que não existem formulários pendentes + Quando eu acesso a seção "Formulários Disponíveis" + Então eu devo ver a mensagem "Você não possui formulários pendentes." diff --git a/features/Gerenciamento_forms/visualizar_resposta_formulario.feature b/features/Gerenciamento_forms/visualizar_resposta_formulario.feature new file mode 100644 index 0000000000..1793ae6cf5 --- /dev/null +++ b/features/Gerenciamento_forms/visualizar_resposta_formulario.feature @@ -0,0 +1,17 @@ + Funcionalidade: Visualização de resultados dos formulários + Eu como Administrador + Quero visualizar os formulários criados + A fim de poder gerar um relatório a partir das respostas + + Cenário: Visualizar formulários criados para turmas selecionadas + Dado que eu estou logado como Administrador + E que eu tenho formulários criados para as turmas "Turma A" e "Turma B" + Quando eu acesso a seção de formulários criados + Então eu devo ver os formulários criados para "Turma A" e "Turma B" + E eu devo ver uma mensagem indicando o número de formulários criados "Existem 2 formulários criados." s + + Cenário: Visualizar detalhes de um formulários + Dado que eu estou logado como Administrador + E que existe um formulário criado para a turma "Turma A" + Quando eu seleciono o formulário de "Turma A" na lista + Então eu devo visualizar as perguntas, respostas e estatísticas do formulário diff --git a/features/admin/atualizar_dados.feature b/features/admin/atualizar_dados.feature new file mode 100644 index 0000000000..1e10d4ba9a --- /dev/null +++ b/features/admin/atualizar_dados.feature @@ -0,0 +1,66 @@ +# language: pt-br + +Funcionalidade: Atualização da base de dados por importação de JSON do SIGAA + + Como um administrador do sistema + Eu quero fazer o upload de um (ou mais) arquivo JSON contendo os dados das disciplinas, turmas e alunos matriculados + A fim de atualizar o sistema com as turmas do semestre e garantir que apenas os alunos corretos possam avaliar seus respectivos professores. + +Cenário: Admin importa JSON de um novo semestre + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E eu tenho os arquivos "disciplinas_2021.2.json" e "turmas_2021.2.json" + Quando eu seleciono os arquivos "disciplinas_2021.2.json" e "turmas_2021.2.json" + E importo-os para o sistema + Então eu devo ver a mensagem "Importação concluída." + E a disciplina "BANCOS DE DADOS" (CIC0097) deve existir no sistema + E a aluna "Ana Clara Jordao Perna" (190084006) deve estar matriculada na turma "TA" de "CIC0097" + + +Cenário: Admin importa JSON que atualiza o email de um aluno + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E existe uma aluna "Ana Clara Jordao Perna" (190084006) com o email "acjpjvjp@gmail.com" + E eu tenho um arquivo "turmas_correcao.json" onde o aluno "190484006" agora tem o email "ana.clara@aluno.unb.br" + Quando eu seleciono o arquivo "turmas_correcao.json" + E importo-o ao sistema + Então eu devo ver a mensagem "Importação concluída." + E a aluna (190084006) deve ter o email "ana.clara@aluno.unb.br" no banco de dados + E não deve existir um novo aluno no sistema + +Cenário: Admin tenta importar um arquivo que não é JSON + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + Quando eu seleciono um arquivo "documento.csv" para importaçao de dados + E importo-o ao sistema + Então eu devo continuar na página que contém o botão "Importar dados" + E eu devo ver a mensagem de erro "Formato de arquivo inválido. Por favor, envie um arquivo .json." + +Cenário: Admin importa JSON de turma com chave "matricula" faltando + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E eu tenho um arquivo "turmas_sem_matricula.json" onde um objeto "discente" não possui a chave "matricula" + Quando eu seleciono o arquivo "turmas_sem_matricula.json" para importação de dados + E importo-o ao sistema + Então eu devo continuar na página que contém o botão "Importar dados" + E eu devo ver a mensagem de erro "Erro na importação: O arquivo JSON é inválido." + + +Cenário: Admin importa um arquivo JSON com sintaxe quebrada + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E eu tenho um arquivo "json_errado.json" (que possui um erro de sintaxe, como uma vírgula extra) + Quando eu seleciono o arquivo "json_errado.json" para importação de dados + E importo-o ao sistema + Então eu devo continuar na página que contém o botão "Importar dados" + E eu devo ver a mensagem de erro "Erro: O arquivo não é um JSON válido." + + +Cenário: Admin importa JSON com tipo de dado inválido para matrícula + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E eu tenho um arquivo "turmas_tipo_errado.json" onde a "matricula" de um aluno é o texto "matricula" em vez de um número + Quando eu seleciono o arquivo "turmas_tipo_errado.json" para importação de dados + E importo-o ao sistema + Então eu devo continuar na página que contém o botão "Importar dados" + E eu devo ver a mensagem de erro "Erro na importação." \ No newline at end of file diff --git a/features/admin/gerenciamento_departamento.feature b/features/admin/gerenciamento_departamento.feature new file mode 100644 index 0000000000..96c1b3bec6 --- /dev/null +++ b/features/admin/gerenciamento_departamento.feature @@ -0,0 +1,34 @@ +# language: pt-br + +Funcionalidade: Visualização de resultados de avaliação restrita por departamento + + Como um administrador + Eu quero gerenciar (ver e avaliar) somente as turmas do departamento ao qual eu pertenço + A fim de avaliar o desempenho das turmas no semestre atual + +Cenário: Admin do CIC vê apenas as turmas do CIC + Dado que eu estou logado como "admin@sistema.com" + E sou professor do depatamento "CIC" + E eu navego para a página "Gerenciamento" e clico em "Resultados" + E existe uma turma "Bancos de Dados (CIC0097)" que pertence ao departamento "CIC" + E existe uma turma "CÁLCULO 1 (MAT0025)" que pertence ao departamento "MAT" + Quando eu olho a lista de turmas na tela "Gerenciamento - Resultados" + Então eu devo ver o bloco da turma "Bancos de Dados (CIC0097)" + E eu não devo ver o bloco da turma "CÁLCULO 1 (MAT0025)" + +Cenário: Admin do MAT vê apenas as turmas do MAT + Dado que eu estou logado como "admin@sistema.com" + E sou professor do depatamento "MAT" + E eu navego para a página "Gerenciamento" e clico em "Resultados" + E existe uma turma "Bancos de Dados (CIC0097)" que pertence ao departamento "CIC" + E existe uma turma "CÁLCULO 1 (MAT0025)" que pertence ao departamento "MAT" + Quando eu olho a lista de turmas na tela "Gerenciamento - Resultados" + Então eu não devo ver o bloco da turma "Bancos de Dados (CIC0097)" + E eu devo ver o bloco da turma "CÁLCULO 1 (MAT0025)" + +Cenário: Admin do CIC tenta acessar resultados do MAT diretamente pela URL + Dado que eu estou logado como "admin@sistema.com" + E sou professor do depatamento "CIC" + E existe a turma "Cálculo 1 (MAT0025)" (de id: 42) que pertence ao departamento "MAT" + Quando eu tento acessar a URL "/gerenciamento/resultados/42" diretamente no meu navegador + Então eu devo ser redirecionado para a minha página inicial (ou "Dashboard") \ No newline at end of file diff --git a/features/admin/gerenciar_relatorios.feature b/features/admin/gerenciar_relatorios.feature new file mode 100644 index 0000000000..56b2b263ff --- /dev/null +++ b/features/admin/gerenciar_relatorios.feature @@ -0,0 +1,28 @@ +# language: pt-br + +Funcionalidade: Visualização e Download de Resultados de Avaliação + Como um administrador + Eu quero baixar um arquivo CSV contendo os resultados de um formulário + A fim de avaliar o desempenho das turmas + +Cenário: Admin baixa CSV de uma turma com respostas + Dado que eu estou logado como "admin@sistema.com" + E eu navego para a página "Gerenciamento" + E eu clico em "Resultados" + E eu estou na tela "Gerenciamento - Resultados" + Quando eu clico no bloco da turma "BANCOS DE DADOS (CIC0097) - Prof. Joao" + Então eu devo baixar um arquivo chamado "resultados_CIC0097_ProfJoao.csv" + +Cenário: Admin tenta ver resultados de uma turma sem respostas + Dado que eu estou logado como "admin@sistema.com" + E eu estou na tela "Gerenciamento - Resultados" + E a turma "ENGENHARIA DE SOFTWARE (CIC0105) - Prof. Genaina" possui 0 respostas de avaliação + Quando eu clico no bloco da turma "ENGENHARIA DE SOFTWARE (CIC0105) - Prof. Genaina" + Então deve exibir a mensagem "Este formulário ainda não possui respostas" + E não efetuar nenhum download de aruivo CSV. + +Cenário: Professor (não-admin) tenta acessar a página de resultados diretamente pela URL + Dado que eu estou logado como "professor.comum@sistema.com" (que não é admin) + E eu não vejo o link para a página "Gerenciamento" no meu menu + Quando eu tento acessar a URL "/gerenciamento/resultados" diretamente no meu navegador + Então eu devo ser redirecionado para a minha página inicial (ou "Dashboard") \ No newline at end of file diff --git a/features/admin/importar_dados.feature b/features/admin/importar_dados.feature new file mode 100644 index 0000000000..a31421e252 --- /dev/null +++ b/features/admin/importar_dados.feature @@ -0,0 +1,48 @@ +# language: pt-br + +Funcionalidade: Alimentação inicial da base de dados com JSON do SIGAA + + Como um administrador + Eu quero importar dados de turmas, matérias e participantes do SIGAA (caso não existam na base de dados atual) + A fim de alimentar a base de dados do sistema. + +Cenário: Admin importa dados em um sistema vazio + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E o banco de dados não contém nenhuma disciplina ou aluno + E eu tenho um arquivo "disciplinas_2021.2.json" (com a disciplina "BANCOS DE DADOS") + E eu tenho um arquivo "turmas_2021.2.json" (com o aluno "Ana Clara Jordao Perna") + Quando eu seleciono os arquivos "disciplinas_2021.2.json" e "turmas_2021.2.json" + E importo-os ao sistema + Então eu devo ver a mensagem "Importação concluída." + E a disciplina "BANCOS DE DADOS" (CIC0097) deve existir no sistema + E o aluno "Ana Clara Jordao Perna" (190084006) deve existir no sistema + +Cenário: Admin importa dados que já existem na base + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E já existe uma disciplina "BANCOS DE DADOS" (CIC0097) no sistema + E já existe o aluno "Ana Clara Jordao Perna" (190084006) + E eu tenho um arquivo "turmas_2021.2_repetido.json" que contém os mesmos dados + Quando eu seleciono o arquivo "turmas_2021.2_repetido.json" + E impoto-o ao sistema + Então eu devo ver a mensagem "Importação concluída." + E deve haver apenas 1 aluno com a matrícula "190084006" no sistema + +Cenário: Admin tenta alimentar a base com um arquivo que não é JSON + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E o banco de dados está vazio + Quando eu seleciono um arquivo "lista_de_alunos.pdf" + E importo-o ao sistema + Então devo ver a mensagem de erro "Formato de arquivo inválido. Por favor, envie um arquivo .json." + +Cenário: Admin importa JSON de turma com a chave "matricula" faltando + Dado que eu estou logado como "admin@sistema.com" + E eu estou na página que contém o botão "Importar dados" + E eu tenho um arquivo "turmas_sem_matricula.json" onde um "discente" não possui a chave "matricula" + Quando eu seleciono o arquivo "turmas_sem_matricula.json" + E importo-o ao sistema + Então eu devo ver a mensagem de erro "Erro na importação: O arquivo JSON é inválido." + + diff --git a/features/cadastro/cadastrar_usuarios.feature b/features/cadastro/cadastrar_usuarios.feature new file mode 100644 index 0000000000..05645330a7 --- /dev/null +++ b/features/cadastro/cadastrar_usuarios.feature @@ -0,0 +1,35 @@ +Funcionalidade: Cadastro de usuários do SIGAA via importação de dados + Como um Administrador + Eu quero cadastrar participantes de turmas do SIGAA ao importar dados de usuarios novos + A fim de que eles acessem o sistema CAMAAR + +Cenário: Admin importa JSON com um novo aluno (Caminho Feliz) + Dado que eu estou logado como "admin_valido@alias" + E eu estou na página que contém o botão "Importar dados" + E o banco de dados não contém o aluno "Novo Aluno" (matrícula "200012345") + E eu tenho um arquivo "turmas_novo_aluno.json" que contém "Novo Aluno" (200012345) com o email "novo.aluno@alias" + Quando eu seleciono o arquivo "turmas_novo_aluno.json" + E importo-o ao sistema + Então eu devo ver a mensagem "Importação concluída." + E o aluno "Novo Aluno" (200012345) deve existir no sistema com status "Pendente" + E um e-mail de definição de senha deve ser enviado para "novo.aluno@alias" + +Cenário: Admin importa JSON com um novo professor (Caminho Feliz) + Dado que eu estou logado como "admin_valido@alias" + E eu estou na página que contém o botão "Importar dados" + E o banco de dados não contém o professor "Novo Professor" (matrícula "XXXXXXXXX") + E eu tenho um arquivo "departamento_novo_professor.json" que contém "Novo Professor" (XXXXXXXXX) com o email "novo.professor@alias" + Quando eu seleciono o arquivo "departamento_novo_professor.json" + E importo-o ao sistema + Então eu devo ver a mensagem "Importação concluída." + E o professor "Novo Professor" (XXXXXXXXX) deve existir no sistema com status "Pendente" + E um e-mail de definição de senha deve ser enviado para "novo.professor@alias" + +Cenário: Admin importa JSON de turma com aluno novo sem a chave "email" (Caminho Triste) + Dado que eu estou logado como "admin_valido@alias" + E eu estou na página que contém o botão "Importar dados" + E eu tenho um arquivo "turmas_sem_email.json" onde um "discente" novo não possui a chave "email" + Quando eu seleciono o arquivo "turmas_sem_email.json" + E importo-o ao sistema + Então eu devo ver a mensagem de erro "Erro na importação: O arquivo JSON é inválido." + E nenhum novo usuário deve ser criado diff --git a/features/login/definicao_senha.feature b/features/login/definicao_senha.feature new file mode 100644 index 0000000000..91cd821fd4 --- /dev/null +++ b/features/login/definicao_senha.feature @@ -0,0 +1,36 @@ +Funcionalidade: Definição inicial de senha + Como um Usuário + Eu quero definir uma senha para o meu usuário a partir do e-mail do sistema de solicitação de cadastro + A fim de acessar o sistema + +Cenário: Usuário define uma senha válida pela primeira vez (Caminho Feliz) + Dado que eu acessei a página "Definir Senha" através de um link de cadastro válido + Quando eu preencho o campo "Nova Senha" com "SenhaForte@123" + E eu preencho o campo "Confirmar Senha" com "SenhaForte@123" + E eu clico no botão "Definir Senha" + Então eu devo ser redirecionado para a página de "Login" + E eu devo ver a mensagem "Senha definida com sucesso! Você já pode fazer o login." + +Cenário: Usuário tenta definir senhas que não são idênticas (Caminho Triste) + Dado que eu acessei a página "Definir Senha" através de um link de cadastro válido + Quando eu preencho o campo "Nova Senha" com "SenhaForte@123" + E eu preencho o campo "Confirmar Senha" com "SenhaDiferente@123" + E eu clico no botão "Definir Senha" + Então eu devo continuar na página "Definir Senha" + E eu devo ver a mensagem de erro "As senhas não conferem." + +Cenário: Usuário tenta definir uma senha muito curta (Caminho Triste) + Dado que eu acessei a página "Definir Senha" através de um link de cadastro válido + Quando eu preencho o campo "Nova Senha" com "123" + E eu preencho o campo "Confirmar Senha" com "123" + E eu clico no botão "Definir Senha" + Então eu devo continuar na página "Definir Senha" + E eu devo ver a mensagem de erro "A senha deve ter no mínimo 8 caracteres." + +Cenário: Usuário tenta definir uma senha fraca (Caminho Triste) + Dado que eu acessei a página "Definir Senha" através de um link de cadastro válido + Quando eu preencho o campo "Nova Senha" com "SenhaFraca" + E eu preencho o campo "Confirmar Senha" com "SenhaFraca" + E eu clico no botão "Definir Senha" + Então eu devo continuar na página "Definir Senha" + E eu devo ver a mensagem de erro "Não atende aos requisitos mínimos: mínimo 8 caracteres, sendo obrigatório 1 caractere especial, 1 número, 1 letra maiúscula e 1 letra minúscula!" diff --git a/features/login/redefinicao_senha.feature b/features/login/redefinicao_senha.feature new file mode 100644 index 0000000000..0f9af74dc8 --- /dev/null +++ b/features/login/redefinicao_senha.feature @@ -0,0 +1,22 @@ +Funcionalidade: Redefinição de senha + Como um Usuário + Eu quero redefinir uma senha para o meu usuário a partir do e-mail recebido + A fim de recuperar o meu acesso ao sistema + +Cenário: Usuário solicita redefinição com e-mail válido (Caminho Feliz) + Dado que eu estou na página de "Login" + E existe um usuário cadastrado com o e-mail "usuario_valido@alias" + Quando eu clico no link "Esqueci minha senha" + E eu preencho o campo "E-mail" com "usuario_valido@alias" + E clico no botão "Solicitar redefinição" + Então eu devo ver a mensagem "Um e-mail de redefinição foi enviado para sua caixa de entrada." + E um e-mail com um link de redefinição deve ser enviado para "usuario_valido@alias" + +Cenário: Usuário solicita redefinição com e-mail não cadastrado (Caminho Triste) + Dado que eu estou na página de "Login" + E não existe um usuário cadastrado com o e-mail "usuario_valido@alias" + Quando eu clico no link "Esqueci minha senha" + E eu preencho o campo "E-mail" com "usuario_valido@alias" + E clico no botão "Solicitar redefinição" + Então eu devo ver a mensagem "Se este e-mail estiver cadastrado, um link será enviado." + E nenhum e-mail de redefinição deve ser enviado diff --git a/features/login/sistema_login.feature b/features/login/sistema_login.feature new file mode 100644 index 0000000000..2cc137728d --- /dev/null +++ b/features/login/sistema_login.feature @@ -0,0 +1,40 @@ +Funcionalidade: Autenticação de usuário no sistema + Como um Usuário do sistema + Eu 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 + +Cenário: Administrador faz login com sucesso (Caminho Feliz) + Dado que eu estou na página de "Login" + E existe um usuário "usuario_valido@alias" com a senha "senha123" e perfil "Admin" + Quando eu preencho "E-mail ou Matrícula" com "usuario_valido@alias" ou "Matricula" + E preencho "Senha" com "senha123" + E clico em "Entrar" + Então eu devo ser redirecionado para a página "Dashboard Admin" + E eu devo ver o link "Gerenciamento" no menu lateral + +Cenário: Aluno (usuário comum) faz login com sucesso (Caminho Feliz) + Dado que eu estou na página de "Login" + E existe um usuário "usuario_valido@alias" com a senha "senha456" e perfil "Aluno" + Quando eu preencho "E-mail ou Matrícula" com "usuario_valido@alias" ou "Matricula" + E preencho "Senha" com "senha456" + E clico em "Entrar" + Então eu devo ser redirecionado para a página "Dashboard Aluno" + E eu não devo ver o link "Gerenciamento" no menu lateral + +Cenário: Professor (usuário comum) faz login com sucesso (Caminho Feliz) + Dado que eu estou na página de "Login" + E existe um usuário "usuario_valido@alias" com a senha "senha456" e perfil "Aluno" + Quando eu preencho "E-mail ou Matrícula" com "usuario_valido@alias" ou "Matricula" + E preencho "Senha" com "senha456" + E clico em "Entrar" + Então eu devo ser redirecionado para a página "Dashboard Professor" + E eu não devo ver o link "Gerenciamento" no menu lateral + +Cenário: Usuário tenta fazer login com senha incorreta (Caminho Triste) + Dado que eu estou na página de "Login" + E existe um usuário "usuario_valido@alias" com a senha "senha456" + Quando eu preencho "E-mail ou Matrícula" com "usuario_valido@alias" ou "Matricula" + E preencho "Senha" com "senhaErrada" + E clico em "Entrar" + Então eu devo continuar na página de "Login" + E eu devo ver a mensagem de erro "E-mail ou senha inválidos." 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 0000000000..c4c9dbfbbd Binary files /dev/null and b/public/icon.png differ 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