├── .browserslistrc ├── .codeclimate.yml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── SUPPORT.md └── workflows │ └── main.yml ├── .gitignore ├── .rspec ├── .ruby-version ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── Procfile.dev ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── anon_feedback.svg │ │ ├── get_started.svg │ │ ├── icons │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── hamburger-48x48.png │ │ │ ├── if-facebook-50x50.svg │ │ │ ├── if-linkedin-50x50.svg │ │ │ ├── if-twitter-50x50.svg │ │ │ └── if-whatsapp-50x50.svg │ │ ├── logo-180x180.png │ │ ├── make_public.svg │ │ ├── moderation.svg │ │ ├── og_image.png │ │ ├── security.svg │ │ └── set_free.svg │ └── stylesheets │ │ └── application.css.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── admin │ │ ├── application_controller.rb │ │ ├── feeds_controller.rb │ │ ├── reports_controller.rb │ │ └── users_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── feeds_controller.rb │ ├── home_controller.rb │ ├── reports_controller.rb │ └── users │ │ ├── multi_factor_authentication_controller.rb │ │ └── sessions_controller.rb ├── dashboards │ ├── feed_dashboard.rb │ ├── report_dashboard.rb │ └── user_dashboard.rb ├── helpers │ └── application_helper.rb ├── javascript │ ├── assets │ │ ├── animate.min.css │ │ ├── custom.css │ │ └── desityle.js │ ├── channels │ │ ├── consumer.js │ │ └── index.js │ └── packs │ │ └── application.js ├── jobs │ ├── application_job.rb │ └── feed_save_job.rb ├── lib │ ├── encryption.rb │ └── message.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── feed.rb │ ├── report.rb │ └── user.rb └── views │ ├── application │ ├── _analytics.html.erb │ ├── _flash.html.erb │ ├── _footer.html.erb │ └── _navigation.html.erb │ ├── feeds │ ├── _export.html.erb │ ├── _form.html.erb │ ├── _guide.html.erb │ ├── feedback.html.erb │ └── index.html.erb │ ├── home │ └── index.html.erb │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── reports │ ├── _form.html.erb │ ├── report.html.erb │ └── show.html.erb │ └── users │ ├── confirmations │ └── new.html.erb │ ├── mailer │ ├── confirmation_instructions.html.erb │ ├── email_changed.html.erb │ ├── password_change.html.erb │ ├── reset_password_instructions.html.erb │ └── unlock_instructions.html.erb │ ├── passwords │ ├── edit.html.erb │ └── new.html.erb │ ├── registrations │ ├── edit.html.erb │ └── new.html.erb │ ├── sessions │ └── new.html.erb │ ├── shared │ ├── _error_messages.html.erb │ └── _links.html.erb │ └── unlocks │ └── new.html.erb ├── babel.config.js ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring ├── webpack ├── webpack-dev-server └── yarn ├── config.ru ├── config ├── application-codespaces.yml ├── application-sample.yml ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── rack_attack.rb │ ├── sidekiq.rb │ ├── smtp.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── sidekiq.yml ├── spring.rb ├── storage.yml ├── webpack │ ├── development.js │ ├── environment.js │ ├── production.js │ └── test.js └── webpacker.yml ├── db ├── migrate │ ├── 20200425073220_devise_create_users.rb │ ├── 20200425075410_add_columns_to_users.rb │ ├── 20200425102203_create_feeds.rb │ └── 20200429071335_create_reports.rb ├── schema.rb └── seeds.rb ├── docker-compose.yml ├── drabkirn-logo-120x120.png ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── postcss.config.js ├── public ├── 404.html ├── 422.html ├── 500.html └── robots.txt ├── spec ├── controllers │ ├── application_controller_spec.rb │ ├── feeds_controller_spec.rb │ ├── home_controller_spec.rb │ ├── reports_controller_spec.rb │ └── users │ │ ├── multi_factor_authentication_controller_spec.rb │ │ └── sessions_controller_spec.rb ├── factories │ ├── feeds.rb │ ├── reports.rb │ └── users.rb ├── jobs │ └── feed_save_job_spec.rb ├── models │ ├── feed_spec.rb │ ├── report_spec.rb │ └── user_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support │ └── shared_examples_spec_helper.rb ├── storage └── .keep ├── tmp └── .keep ├── vendor └── .keep └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | argument-count: 4 | enabled: true 5 | config: 6 | threshold: 4 7 | complex-logic: 8 | enabled: true 9 | config: 10 | threshold: 4 11 | file-lines: 12 | enabled: true 13 | config: 14 | threshold: 150 15 | method-complexity: 16 | enabled: true 17 | config: 18 | threshold: 5 19 | method-count: 20 | enabled: true 21 | config: 22 | threshold: 20 23 | nested-control-flow: 24 | enabled: true 25 | config: 26 | threshold: 4 27 | return-statements: 28 | enabled: true 29 | config: 30 | threshold: 4 31 | similar-code: 32 | enabled: false 33 | identical-code: 34 | enabled: false 35 | method-lines: 36 | enabled: false 37 | plugins: 38 | brakeman: 39 | enabled: true 40 | bundler-audit: 41 | enabled: true 42 | exclude_patterns: 43 | - "config/" 44 | - "db/" 45 | - "**/node_modules/" 46 | - "**/spec/" 47 | - "**/vendor/" 48 | - "babel.config.js" -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.5 2 | 3 | # Adding NodeJS and Yarn 4 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 5 | && curl -sL https://deb.nodesource.com/setup_12.x | bash \ 6 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 7 | 8 | # Install dependencies and perform clean-up 9 | RUN apt-get update -qq && apt-get install -y build-essential nodejs yarn mariadb-server mariadb-client libmariadb-dev redis-server \ 10 | && apt-get -q clean \ 11 | && rm -rf /var/lib/apt/lists 12 | 13 | RUN service mysql restart 14 | RUN service redis-server restart 15 | 16 | ENV RAILS_ENV development 17 | ENV RACK_ENV development 18 | 19 | RUN gem install bundler foreman -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Feedka codespaces", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | }, 6 | 7 | "settings": { 8 | "terminal.integrated.shell.linux": "/bin/bash", 9 | "workbench.enableExperiments": false, 10 | "workbench.settings.enableNaturalLanguageSearch": false, 11 | "telemetry.enableTelemetry": false, 12 | "npm.fetchOnlinePackageInfo": false, 13 | "workbench.colorTheme": "Monokai", 14 | "workbench.iconTheme": "vscode-icons", 15 | "files.autoSave": "afterDelay", 16 | "files.autoSaveDelay": 3000, 17 | "editor.tabSize": 2, 18 | "editor.detectIndentation": false, 19 | "editor.wordWrap": "on", 20 | "extensions.autoUpdate": false, 21 | "files.associations": { 22 | "*.html.erb": "html" 23 | }, 24 | "emmet.includeLanguages": { 25 | "javascript": "javascriptreact", 26 | "html.erb": "html" 27 | } 28 | }, 29 | 30 | "extensions": [ 31 | "vscode-icons-team.vscode-icons", 32 | "coenraads.bracket-pair-colorizer-2", 33 | "esbenp.prettier-vscode" 34 | ], 35 | 36 | "forwardPorts": [ 37 | 3000, 38 | 3002, 39 | 3004 40 | ], 41 | 42 | "postCreateCommand": "ruby --version" 43 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .dockerignore 3 | .byebug_history 4 | log/* 5 | tmp/* -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Your issue may already be reported! 4 | Please search on the [issue track](../) before creating a new one. 5 | 6 | ### I'm submitting a ... 7 | 8 | - [ ] bug report 9 | - [ ] feature request 10 | 11 | 12 | ### What is the current behavior?* 13 | 14 | 15 | 16 | 17 | ### If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem 18 | 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 4. 24 | 25 | 26 | ### What is the expected behavior? 27 | 28 | 29 | 30 | 31 | ### What is the motivation/use case for changing the behavior? 32 | 33 | 34 | 35 | ### Please tell us about your environment: 36 | - Version: 2.0.0-beta.X 37 | - Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] 38 | - Language: [all | Ruby 2.6.3 | ES6/7 | ES5] 39 | 40 | 41 | ### Other information: 42 | *Example: Detailed explanation, stack traces, related issues, suggestions on how to fix, links for us to have context* -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report for code that is not working as expected. 4 | title: '' 5 | labels: bug 6 | assignees: cdadityang 7 | 8 | --- 9 | 10 | 11 | 12 | Your bug may already be reported! 13 | Please search on the [issue track](../) before creating a new one. Delete this line to submit this issue still. 14 | 15 | 16 | ### What is the current behavior?* 17 | 18 | 19 | 20 | ### Please provide the steps to reproduce and if possible a minimal demo of the problem 21 | 22 | 23 | 1. 24 | 2. 25 | 3. 26 | 4. 27 | 28 | 29 | ### What is the expected behavior? 30 | 31 | 32 | 33 | ### What is the motivation/use case for changing the behavior? 34 | 35 | 36 | 37 | ### Please tell us about your environment: 38 | - Version: 2.0.0-beta.X 39 | - Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] 40 | - Language: [all | Ruby 2.6.3 | ES6/7 | ES5] 41 | 42 | 43 | ### Other information: 44 | *Example: Detailed explanation, stack traces, screenshots, related issues, suggestions on how to fix, links for us to have context* -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: For new features, functions, and improvements. 4 | title: '' 5 | labels: enhancement 6 | assignees: cdadityang 7 | 8 | --- 9 | 10 | 11 | 12 | ### What is the current behavior?* 13 | 14 | 15 | 16 | ### What is the expected behavior? 17 | 18 | 19 | 20 | ### What is the motivation/use case for changing the behavior? 21 | 22 | 23 | ### Other information: 24 | *Example: Detailed explanation, related issues, screnshoots, suggestions on how to improve, links for us to have context* -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What kind of change does this PR introduce? 2 | *Example: Bug fix, feature, docs update, ...* 3 | 4 | - [ ] Bug fix (non-breaking change which fixes an issue) 5 | - [ ] New feature (non-breaking change which adds functionality) 6 | - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) 7 | - [ ] Documentation update 8 | - [ ] Others 9 | 10 | 11 | ### What is the current behavior? 12 | *Note: You can also link to an open issue here. If appropriate, provide us some screenshots.* 13 | 14 | 15 | ### What is the new behavior? 16 | *Note: Use this only if this is a feature. If appropriate, provide us some screenshots.* 17 | 18 | 19 | ### Other information: 20 | *Example: Provide us some more information that we need to know* 21 | 22 | 23 | ### Please check if the PR fulfills these requirements: 24 | 25 | 26 | - [ ] My code follows the code style and contribution guidelines of this project. 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. 29 | - [ ] I have commented my code, particularly in hard-to-understand areas 30 | - [ ] My changes generate no new warnings -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Our security policy 2 | 3 | Safety and security are critical and of the utmost priority to us. If you have discovered a security vulnerability in our codebase, we would appreciate your help in disclosing it to us in a responsible manner. 4 | 5 | ## Reporting security issues: 6 | We request you **NOT** do disclose any security-related information using GitHub issues, instead, send us an email at [drabkirn@cdadityang.xyz](mailto:drabkirn@cdadityang.xyz), and we'll get back to you as soon as possible(ASAP) and keep updating you throughout the patching process. 7 | 8 | We're committed to working for open-source and free software, so we'll not be able to reward you for reporting security vulnerabilities. However, If you choose, after patching the security issue submitted by you, we'll credit you publicly on our website for your efforts. 9 | 10 | We're looking forward to accepting your contributions and make this world a better place. Please keep them coming. ❤💖 -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Our Support 2 | 3 | We're committed to providing the best support to our community. You've come to a great place. 4 | 5 | ## Documentation 6 | Please take the time to read our [documentation](https://go.cdadityang.xyz/docs) for installation and other guides. Also, more detailed information is available in our project's [README file](https://github.com/drabkirn/rails_base/blob/master/README.md). 7 | 8 | ## Bugs 9 | If you've found a bug and before submitting that bug, Check that [our issue database](https://github.com/drabkirn/rails_base/issues) 10 | doesn't already include your problem or suggestion. If no one has reported the problem, you can [Open a new issue here](https://github.com/drabkirn/rails_base/issues/new/choose) 11 | 12 | ## Security Vulnerabilities 13 | We have a dedicated section for security-related support, [check that here](https://github.com/drabkirn/rails_base/blob/master/.github/SECURITY.md). 14 | 15 | Thank you for checking out Drabkirn. You can always reach us at [drabkirn@cdadityang.xyz](mailto:drabkirn@cdadityang.xyz) for any information, and we'll respond as soon as possible. 16 | 17 | We're looking forward to accepting your contributions and make this world a better place. Please keep them coming. ❤💖 -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Feedka_CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | concurrency: 10 | group: ${ GITHUB_REF } 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checking out latest push 19 | uses: actions/checkout@v2 20 | 21 | - name: Setup Ruby 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: 2.7.5 25 | 26 | - name: Setup MYSQL 27 | uses: mirromutth/mysql-action@v1.1 28 | with: 29 | mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }} 30 | 31 | - name: Cache Ruby Gems 32 | uses: actions/cache@v2 33 | with: 34 | path: vendor/bundle 35 | key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-gems- 38 | 39 | - name: Setup Node and Yarn 40 | uses: actions/setup-node@v2 41 | with: 42 | node-version: '12.22.7' 43 | cache: 'yarn' 44 | 45 | - name: "Cache Yarn" 46 | uses: actions/cache@v2 47 | with: 48 | path: '**/node_modules' 49 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 50 | 51 | - name: Install Yarn 52 | if: steps.yarn-cache.outputs.cache-hit != 'true' 53 | run: yarn install 54 | 55 | - name: Install Dependencies 56 | run: | 57 | sudo apt install -yqq libmysqlclient-dev xvfb firefox 58 | wget https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz 59 | tar -zxvf geckodriver-v0.26.0-linux64.tar.gz 60 | sudo mv geckodriver /usr/local/bin/ 61 | export DISPLAY=':99.0' 62 | Xvfb :99.0 > /dev/null 2>&1 & 63 | gem install bundler 64 | 65 | - name: Install Gems 66 | run: | 67 | bundle config path vendor/bundle 68 | bundle install --jobs 4 --retry 3 69 | 70 | - name: Setup Code Climate 71 | run: | 72 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 73 | chmod +x ./cc-test-reporter 74 | ./cc-test-reporter before-build 75 | 76 | - name: Prepare Database and run tests 77 | env: 78 | db_hostname: ${{ secrets.MYSQL_HOSTNAME }} 79 | db_username: ${{ secrets.MYSQL_USERNAME }} 80 | db_password: ${{ secrets.MYSQL_ROOT_PASSWORD }} 81 | db_name: ${{ secrets.MYSQL_DB_NAME }} 82 | RAILS_ENV: "test" 83 | app_hostname: "localhost" 84 | app_port: "3000" 85 | devise_secret_key: ${{ secrets.DEVISE_SECRET_KEY }} 86 | devise_pepper_hash: ${{ secrets.DEVISE_PEPPER_HASH }} 87 | ci_true: true 88 | capybara_screenshots_path: tmp/capybara_screenshots 89 | content_moderation_url: "https://contentmoderation.feedka.xyz/test" 90 | content_moderation_api_key: "abcd" 91 | encrypt_key: ${{ secrets.ENCRYPT_KEY }} 92 | run: | 93 | bundle exec rails db:prepare 94 | bundle exec rspec 95 | 96 | - name: Publish Code Coverage 97 | run: | 98 | export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" 99 | ./cc-test-reporter after-build -r ${{secrets.CC_TEST_REPORTER_ID}} 100 | 101 | - name: Create Coverage Artifact 102 | uses: actions/upload-artifact@v2 103 | with: 104 | name: code-coverage 105 | path: coverage/ 106 | 107 | security: 108 | runs-on: ubuntu-latest 109 | steps: 110 | - uses: actions/checkout@v2 111 | 112 | - name: Setup Ruby 113 | uses: ruby/setup-ruby@v1 114 | with: 115 | ruby-version: 2.7.5 116 | 117 | - name: Install Brakeman 118 | run: | 119 | gem install brakeman 120 | 121 | - name: Run Brakeman 122 | run: | 123 | brakeman -f json > tmp/brakeman.json || exit 0 124 | 125 | - name: Brakeman Report 126 | uses: devmasx/brakeman-linter-action@v1.0.0 127 | env: 128 | REPORT_PATH: tmp/brakeman.json 129 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore uploaded files in development. 17 | /storage/* 18 | !/storage/.keep 19 | 20 | /public/assets 21 | .byebug_history 22 | 23 | # Ignore master key for decrypting credentials and more. 24 | /config/master.key 25 | 26 | /public/packs 27 | /public/packs-test 28 | /node_modules 29 | /yarn-error.log 30 | yarn-debug.log* 31 | .yarn-integrity 32 | 33 | # Ignore application configuration 34 | /config/application.yml 35 | 36 | # Ignore test coverage 37 | /coverage -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.5 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Drabkirn Feedka 2 | 3 | ## Before Submitting an Issue 4 | Check that [our issue database](https://github.com/drabkirn/feedka/issues) 5 | doesn't already include your problem or suggestion before submitting your issue. 6 | If you find a match, you can use the "subscribe" button to get notified on 7 | updates. Do **NOT** leave random "+1" or "I have this too" comments, as they 8 | only clutter the discussion, and don't help to resolve it. However, if you 9 | have ways to reproduce the issue or have additional information that may help 10 | to resolve the issue, please leave a comment. 11 | 12 | ## Writing Good Bug Reports and Feature Requests 13 | Please file a single issue per problem and feature request. Do not submit combo issues. 14 | 15 | The more information you can provide, the more likely someone will be successful in reproducing the issue and finding a fix. Therefore: 16 | * Provide reproducible steps, what the result of the steps was, and what you would have expected. 17 | * A detailed description of the behavior that you expect. 18 | * Animated GIFs are a tremendous help. 19 | * Version information of your ruby and rails version. 20 | * Error outputs that may exist in your browser console. 21 | 22 | ## How to Contribute 23 | 1. Please add an issue or comment on issues that are open and mention that you are working on it. Then submit a pull request! This will let others know you're working on it. 24 | 25 | 2. Install the app on your local machine. You need to [Fork](https://help.github.com/articles/fork-a-repo/) this app and then clone it on your local machine. See the Installation section of [README.md](https://github.com/drabkirn/feedka/blob/master/README.md) on how to do the installation. 26 | 27 | 3. Set the upstream remote so you can keep your copy of the app synced with the original. To do that, go to your terminal and cd into your cloned Drabkirn Feedka app directory. Then use one of the following commands: 28 | * If you have ssh set up with Git 29 | ```bash 30 | $ git remote add upstream https://github.com/drabkirn/feedka.git 31 | ``` 32 | Or 33 | ```bash 34 | git remote add upstream git@github.com:drabkirn/feedka.git 35 | ``` 36 | 37 | 4. Before you start working on your issue, create a branch, and name it as the following examples: 38 | * If its a new feature: 39 | ```bash 40 | $ git checkout -b feature/new-feature-name 41 | ``` 42 | * If its a bug fix 43 | ```bash 44 | git checkout -b fix/fixed-bug-name 45 | ``` 46 | 47 | 5. When you have finished and are ready to submit a Pull Request: 48 | * Push your branch to your fork 49 | ```bash 50 | $ git push origin 51 | ``` 52 | * Go to your fork on Github after you have pushed up your branch. A new button should be visible near the top of the page. It will allow you to create a pull request to the original Drabkirn Feedka repo. 53 | * You'll see a `PULL_REQUEST_TEMPLATE` - Try to complete this in your own words. 54 | * Please Link to the issue your pull request resolves in the body of your pull request. 55 | 56 | 6. **Important:** After you submit your pull request, Drabkirn requires that every contributor sign a Drabkirn Contributor License Agreement (CLA) to a Drabkirn open source project. This Agreement is effective upon your acknowledgment via the CLA Assistant tool. You can read and sign this Agreement at [https://cla-assistant.io/drabkirn/feedka](https://cla-assistant.io/drabkirn/feedka) or follow the link in your pull request pending checks list. Until you agree by signing this Agreement, Drabkirn will not merge your pull request. 57 | 58 | We're looking forward to accepting your contributions and make this world a better place. Please keep them coming. ❤💖 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.5 2 | 3 | MAINTAINER drabkirn@cdadityang.xyz 4 | 5 | # Adding NodeJS and Yarn 6 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 7 | && curl -sL https://deb.nodesource.com/setup_12.x | bash \ 8 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 9 | 10 | # Install dependencies and perform clean-up 11 | RUN apt-get update -qq && apt-get install -y build-essential nodejs yarn \ 12 | && apt-get -q clean \ 13 | && rm -rf /var/lib/apt/lists 14 | 15 | WORKDIR /usr/src/app 16 | 17 | ENV RAILS_ENV development 18 | ENV RACK_ENV development 19 | 20 | COPY . . 21 | 22 | RUN gem install bundler 23 | RUN bundle install 24 | RUN yarn install --check-files 25 | 26 | ENTRYPOINT ["bundle", "exec"] 27 | 28 | CMD puma -C config/puma.rb 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '2.7.5' 5 | 6 | gem 'rails', '~> 6.1.6.1' 7 | gem 'mysql2', '~> 0.5.3' 8 | gem 'puma', '~> 4.3' 9 | gem 'webpacker', '~> 5.1', '>= 5.1.1' 10 | gem 'jbuilder', '~> 2.10' 11 | gem 'bootsnap', '~> 1.4', '>= 1.4.6', require: false 12 | gem 'tzinfo-data', '~> 1.2020', '>= 1.2020.1' 13 | 14 | gem 'sass-rails', '>= 6' 15 | gem 'turbolinks', '~> 5.2', '>= 5.2.1' 16 | 17 | group :development, :test do 18 | gem 'byebug', '~> 11.1', '>= 11.1.3', platforms: [:mri, :mingw, :x64_mingw] 19 | 20 | # Custom gems: 21 | gem 'database_cleaner', '~> 1.8', '>= 1.8.4' 22 | gem 'simplecov', '~> 0.17.1' 23 | gem 'shoulda-matchers', '~> 4.3' 24 | gem 'rails-controller-testing', '~> 1.0', '>= 1.0.4' 25 | gem 'rspec-rails', '~> 4.0' 26 | gem 'factory_bot_rails', '~> 5.1', '>= 5.1.1' 27 | gem 'faker', '~> 2.11' 28 | end 29 | 30 | group :development do 31 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 32 | gem 'web-console', '~> 4.0', '>= 4.0.1' 33 | gem 'listen', '~> 3.2', '>= 3.2.1' 34 | gem 'spring', '~> 2.1' 35 | gem 'spring-watcher-listen', '~> 2.0', '>= 2.0.1' 36 | 37 | # Custom gems: 38 | gem 'brakeman', '~> 4.8', '>= 4.8.1' 39 | gem 'bundler-audit', '~> 0.9.0.1' 40 | end 41 | 42 | group :test do 43 | gem 'capybara', '~> 3.32', '>= 3.32.1' 44 | gem 'selenium-webdriver', '~> 4.0', '>= 4.0.3' 45 | gem 'webdrivers', '~> 4.3' 46 | gem 'webmock', '~> 3.8', '>= 3.8.3' 47 | end 48 | 49 | ## Custom Gems: 50 | gem 'figaro', '~> 1.1', '>= 1.1.1' 51 | gem 'devise', '~> 4.7', '>= 4.7.1' 52 | gem 'active_model_otp', :git => 'https://github.com/heapsource/active_model_otp.git' 53 | gem 'sidekiq', '~> 6.4' 54 | gem 'rack-attack', '~> 6.3' 55 | gem 'redis', '~> 4.1', '>= 4.1.3' 56 | gem 'administrate', '~> 0.16.0' -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | worker: bundle exec sidekiq -e $RACK_ENV -C config/sidekiq.yml -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | rails: rails s -b 0.0.0.0 -p 3002 2 | react: bundle exec ./bin/webpack-dev-server 3 | sidekiq: bundle exec sidekiq -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | # Drabkirn Feedka 6 | 7 | 8 | > Get authentic, kindful, and constructive feedback from your friends, family, and co-workers. 9 | 10 | 11 | [![Ruby 2.7.5](https://img.shields.io/badge/Ruby-v2.7.5-green.svg)](https://www.ruby-lang.org/en/) 12 | [![Rails 6.1.6.1](https://img.shields.io/badge/Rails-v6.1.6.1-brightgreen.svg)](https://rubyonrails.org/) 13 | [![Rspec 4.0](https://img.shields.io/badge/RSpec-v4.0-red.svg)](http://rspec.info/) 14 | [![Build Status](https://travis-ci.org/drabkirn/feedka.svg?branch=master)](https://travis-ci.org/drabkirn/feedka) 15 | [![Test Coverage](https://api.codeclimate.com/v1/badges/914eb5f6039700faec09/test_coverage)](https://codeclimate.com/github/drabkirn/feedka/test_coverage) 16 | [![Maintainability](https://api.codeclimate.com/v1/badges/914eb5f6039700faec09/maintainability)](https://codeclimate.com/github/drabkirn/feedka/maintainability) 17 | [![Technical Dept](https://img.shields.io/codeclimate/tech-debt/drabkirn/feedka)](https://codeclimate.com/github/drabkirn/feedka/trends/technical_debt) 18 | [![Issues](https://img.shields.io/github/issues/drabkirn/feedka.svg)](https://github.com/drabkirn/feedka/issues) 19 | [![Issues closed](https://img.shields.io/github/issues-closed/drabkirn/feedka.svg)](https://github.com/drabkirn/feedka/issues) 20 | [![Pulls](https://img.shields.io/github/issues-pr/drabkirn/feedka.svg)](https://github.com/drabkirn/feedka/pulls) 21 | [![Pulls](https://img.shields.io/github/issues-pr-closed/drabkirn/feedka.svg)](https://github.com/drabkirn/feedka/pulls) 22 | [![License](https://img.shields.io/github/license/drabkirn/feedka.svg)](https://choosealicense.com/licenses/agpl-3.0/) 23 | [![CLA assistant](https://cla-assistant.io/readme/badge/drabkirn/feedka)](https://cla-assistant.io/drabkirn/feedka) 24 | [![Dependabot](https://badgen.net/dependabot/drabkirn/feedka?icon=dependabot)]() 25 | [![Code size](https://img.shields.io/github/languages/code-size/drabkirn/feedka)]() 26 | 27 | 28 | Feedka is an open-source web application that can serve as a platform to get feedback from your friends, family, and co-workers. We're all on the same boat in just our own different ocean. Let us all get better together. 29 | 30 | 31 | **[Visit website here](https://go.cdadityang.xyz/feedka)** 32 | 33 | ----- 34 | ----- 35 | 36 | ## Table of Contents 37 | - [Features](#features) 38 | - [Tech Stack](#tech-stack) 39 | - [Installation](#installation) 40 | - [Contributing](#contributing) 41 | - [Connect](#connect) 42 | 43 | ----- 44 | ----- 45 | 46 | ## Features 47 | - Front-end built with [Desityle CSS](https://go.cdadityang.xyz/style) 48 | - Two Factor Authentication for users 49 | - Anonymous feedbacks 50 | - Random scheduling of feedbacks using `sidekiq` 51 | - Built-in moderation with AI API's 52 | - Server side encryption to all feedbacks 53 | 54 | ----- 55 | ----- 56 | 57 | ## Tech Stack 58 | - Ruby on Rails: Backend 59 | - Desityle CSS: Front-end 60 | - Rspec: Testing suite 61 | - Simplecov: Test coverage and maintainability 62 | - MySQL: Database engine 63 | - Sidekiq and Redis: Scheduling feedbacks for later time 64 | - Sidekiq UI: Sidekiq dashboard for admins at `/sidekiq` 65 | - Administrate: Admin UI for admins at `/admin` 66 | - Heroku: Hosting 67 | - Sendgrid: Sending emails 68 | - Azure Content Moderation: Flagging abuse and PII info and block such feedbacks 69 | - Devise: User authentication 70 | - And maybe more: See our [Gemfile](https://github.com/drabkirn/feedka/blob/master/Gemfile) 71 | 72 | ----- 73 | ----- 74 | 75 | ## Installation 76 | 77 | **We like to keep our GitHub repo clean and simple. So for usage and installation guides, please check out our [documentation here](https://go.cdadityang.xyz/FdocsD)** 78 | 79 | ----- 80 | ----- 81 | 82 | ## Contributing 83 | 84 | If you would like to contribute, please check [this contributing guide](https://github.com/drabkirn/feedka/blob/master/CONTRIBUTING.md) 85 | 86 | Please check [this Code of Conduct guide](https://github.com/drabkirn/feedka/blob/master/CODE_OF_CONDUCT.md) before contributing or having any kind of discussion(issues, pull requests etc.) with the Feedka project! 87 | 88 | ----- 89 | 90 | ## Connect: 91 | Need any help? Have any Questions? Or just say us hi! 92 | 93 | 1. [Drabkirn Website](https://go.cdadityang.xyz/drab) 94 | 2. [Our Blog](https://go.cdadityang.xyz/blog) 95 | 3. [Discord Server](https://go.cdadityang.xyz/discord) 96 | 4. [Twitter](https://go.cdadityang.xyz/DtwtK) 97 | 5. [Instagram](https://go.cdadityang.xyz/DinsK) -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link administrate/application.css 4 | //= link administrate/application.js -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/get_started.svg: -------------------------------------------------------------------------------- 1 | runner_start -------------------------------------------------------------------------------- /app/assets/images/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/assets/images/icons/favicon-16x16.png -------------------------------------------------------------------------------- /app/assets/images/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/assets/images/icons/favicon-32x32.png -------------------------------------------------------------------------------- /app/assets/images/icons/hamburger-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/assets/images/icons/hamburger-48x48.png -------------------------------------------------------------------------------- /app/assets/images/logo-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/assets/images/logo-180x180.png -------------------------------------------------------------------------------- /app/assets/images/make_public.svg: -------------------------------------------------------------------------------- 1 | online_articles -------------------------------------------------------------------------------- /app/assets/images/og_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/assets/images/og_image.png -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class ApplicationController < Administrate::ApplicationController 3 | before_action :authenticate_user! 4 | before_action :authenticate_admin 5 | 6 | def authenticate_admin 7 | redirect_to root_path, alert: Message.require_admin if !current_user.admin? 8 | end 9 | 10 | # Override this value to specify the number of elements to display at a time 11 | # on index pages. Defaults to 20. 12 | # def records_per_page 13 | # params[:per_page] || 20 14 | # end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/controllers/admin/feeds_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class FeedsController < Admin::ApplicationController 3 | # See https://administrate-prototype.herokuapp.com/customizing_controller_actions 4 | # for more information 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/admin/reports_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class ReportsController < Admin::ApplicationController 3 | # See https://administrate-prototype.herokuapp.com/customizing_controller_actions 4 | # for more information 5 | 6 | ## Before showing the report to admin, decrypt the content 7 | ## The feedback giver has authorized admin to see the content 8 | def find_resource(param) 9 | @report = Report.find(param.to_i) 10 | @report.content = Encryption.decrypt_data(@report.content) 11 | @report 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/controllers/admin/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class UsersController < Admin::ApplicationController 3 | # See https://administrate-prototype.herokuapp.com/customizing_controller_actions 4 | # for more information 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | ## Add our custom flash messages as an array 3 | add_flash_types :custom_alert 4 | 5 | before_action :configure_permitted_parameters, if: :devise_controller? 6 | 7 | protected 8 | ## Devise whitelist params for users signup and updates 9 | def configure_permitted_parameters 10 | sign_up_added_attrs = [:username, :email, :password, :password_confirmation, :remember_me] 11 | update_added_attrs = [:email, :password, :password_confirmation, :remember_me] 12 | devise_parameter_sanitizer.permit :sign_up, keys: sign_up_added_attrs 13 | devise_parameter_sanitizer.permit :account_update, keys: update_added_attrs 14 | end 15 | 16 | ## Devise - after sign_in, goto /feeds 17 | def after_sign_in_path_for(resource) 18 | feeds_path 19 | end 20 | 21 | ## Devise - after sign out, goto / 22 | def after_sign_out_path_for(resource) 23 | root_path 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/reports_controller.rb: -------------------------------------------------------------------------------- 1 | class ReportsController < ApplicationController 2 | 3 | ## GET /reports/:id 4 | def show 5 | @report = Report.find(params[:id]) 6 | end 7 | 8 | ## GET /r/:username 9 | ## GET /r/:admin 10 | ## Anyone can use this link and submit report 11 | def report 12 | ## Pick the user by username params 13 | ## If user is not found, redirect to root_path with alert message 14 | user = User.find_by(username: params[:username]) 15 | if !user 16 | redirect_to root_path, alert: Message.user_not_found 17 | return 18 | end 19 | 20 | ## If users email is not confirmed, don't show this page 21 | if !user.confirmed? 22 | redirect_to root_path, alert: Message.user_email_not_confirmed 23 | return 24 | end 25 | 26 | ## Get the `feed_content` param 27 | @feed_content = params[:feed_content] 28 | 29 | ## If `feed_content` param is not found, redirect to root path with alert 30 | if !@feed_content 31 | ## First user is always admin, so submit other reports from here 32 | if !(user.username == ENV["admin_username"]) 33 | redirect_to root_path, alert: Message.feed_content_not_found 34 | return 35 | end 36 | end 37 | 38 | @report = Report.new 39 | end 40 | 41 | ## POST /reports 42 | def create 43 | @report = Report.new(report_params) 44 | 45 | ## We cannot use `user_id` as hidden field in forms 46 | ## So this workfix, helps get the username 47 | ref_path = URI(request.referer).path 48 | username = ref_path.split("/").last 49 | user = User.find_by(username: username) 50 | 51 | ## Now set the user 52 | ## If user is somehow not set, Feed won't save and returns the error to view 53 | @report.user = user if user 54 | 55 | if @report.save 56 | redirect_to report_path(@report.id), notice: Message.report_created 57 | else 58 | redirect_to ref_path, alert: Message.feed_content_not_found 59 | end 60 | end 61 | 62 | private 63 | ## Whitelist params for reports, anything else will be rejected 64 | def report_params 65 | params.require(:report).permit(:content) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /app/controllers/users/multi_factor_authentication_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::MultiFactorAuthenticationController < ApplicationController 2 | before_action :authenticate_user! 3 | before_action :set_user 4 | 5 | ## POST /users/:id/enable_multi_factor_authentication 6 | def verify_enable 7 | if current_user == @user && current_user.authenticate_otp(params[:multi_factor_authentication][:otp_code_token], drift: 60) 8 | current_user.otp_module_enabled! 9 | redirect_to edit_user_registration_path, notice: Message.two_fa_enabled 10 | else 11 | redirect_to edit_user_registration_path, alert: Message.two_fa_not_enabled 12 | end 13 | end 14 | 15 | ## POST /users/:id/disable_multi_factor_authentication 16 | def verify_disable 17 | if current_user == @user && current_user.authenticate_otp(params[:multi_factor_authentication][:otp_code_token], drift: 60) 18 | current_user.otp_module_disabled! 19 | redirect_to edit_user_registration_path, notice: Message.two_fa_disabled 20 | else 21 | redirect_to edit_user_registration_path, alert: Message.two_fa_not_disabled 22 | end 23 | end 24 | 25 | private 26 | def set_user 27 | @user = User.find(params[:id]) 28 | end 29 | end -------------------------------------------------------------------------------- /app/controllers/users/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::SessionsController < Devise::SessionsController 2 | 3 | ## POST /users/sign_in 4 | ## This also checks for 2FA code if user has enabled 2FA 5 | def create 6 | self.resource = warden.authenticate!(auth_options) 7 | 8 | if resource && resource.otp_module_disabled? 9 | continue_sign_in(resource, resource_name) 10 | elsif resource && resource.otp_module_enabled? 11 | supplied_otp_code = params[:user][:otp_code_token] 12 | if supplied_otp_code && supplied_otp_code.size > 0 13 | if resource.authenticate_otp(supplied_otp_code, drift: 60) 14 | continue_sign_in(resource, resource_name) 15 | else 16 | sign_out resource 17 | redirect_to root_url, alert: Message.two_fa_wrong 18 | end 19 | else 20 | sign_out resource 21 | redirect_to root_url, alert: Message.two_fa_empty 22 | end 23 | end 24 | end 25 | 26 | private 27 | def continue_sign_in(resource, resource_name) 28 | set_flash_message!(:notice, :signed_in) 29 | sign_in(resource_name, resource) 30 | yield resource if block_given? 31 | respond_with resource, location: after_sign_in_path_for(resource) 32 | end 33 | end -------------------------------------------------------------------------------- /app/dashboards/feed_dashboard.rb: -------------------------------------------------------------------------------- 1 | require "administrate/base_dashboard" 2 | 3 | class FeedDashboard < Administrate::BaseDashboard 4 | # ATTRIBUTE_TYPES 5 | # a hash that describes the type of each of the model's fields. 6 | # 7 | # Each different type represents an Administrate::Field object, 8 | # which determines how the attribute is displayed 9 | # on pages throughout the dashboard. 10 | ATTRIBUTE_TYPES = { 11 | user: Field::BelongsTo, 12 | id: Field::Number 13 | }.freeze 14 | 15 | # COLLECTION_ATTRIBUTES 16 | # an array of attributes that will be displayed on the model's index page. 17 | # 18 | # By default, it's limited to four items to reduce clutter on index pages. 19 | # Feel free to add, remove, or rearrange items. 20 | COLLECTION_ATTRIBUTES = ATTRIBUTE_TYPES.keys 21 | 22 | # SHOW_PAGE_ATTRIBUTES 23 | # an array of attributes that will be displayed on the model's show page. 24 | SHOW_PAGE_ATTRIBUTES = ATTRIBUTE_TYPES.keys 25 | 26 | # FORM_ATTRIBUTES 27 | # an array of attributes that will be displayed 28 | # on the model's form (`new` and `edit`) pages. 29 | FORM_ATTRIBUTES = ATTRIBUTE_TYPES.keys 30 | 31 | # COLLECTION_FILTERS 32 | # a hash that defines filters that can be used while searching via the search 33 | # field of the dashboard. 34 | # 35 | # For example to add an option to search for open resources by typing "open:" 36 | # in the search field: 37 | # 38 | # COLLECTION_FILTERS = { 39 | # open: ->(resources) { resources.where(open: true) } 40 | # }.freeze 41 | COLLECTION_FILTERS = {}.freeze 42 | 43 | # Overwrite this method to customize how feeds are displayed 44 | # across all pages of the admin dashboard. 45 | # 46 | # def display_resource(feed) 47 | # "Feed ##{feed.id}" 48 | # end 49 | end 50 | -------------------------------------------------------------------------------- /app/dashboards/report_dashboard.rb: -------------------------------------------------------------------------------- 1 | require "administrate/base_dashboard" 2 | 3 | class ReportDashboard < Administrate::BaseDashboard 4 | # ATTRIBUTE_TYPES 5 | # a hash that describes the type of each of the model's fields. 6 | # 7 | # Each different type represents an Administrate::Field object, 8 | # which determines how the attribute is displayed 9 | # on pages throughout the dashboard. 10 | ATTRIBUTE_TYPES = { 11 | user: Field::BelongsTo, 12 | id: Field::Number, 13 | content: Field::Text, 14 | status: Field::String.with_options(searchable: false), 15 | message: Field::Text, 16 | created_at: Field::DateTime, 17 | updated_at: Field::DateTime 18 | }.freeze 19 | 20 | # COLLECTION_ATTRIBUTES 21 | # an array of attributes that will be displayed on the model's index page. 22 | # 23 | # By default, it's limited to four items to reduce clutter on index pages. 24 | # Feel free to add, remove, or rearrange items. 25 | COLLECTION_ATTRIBUTES = %i[ 26 | user 27 | id 28 | status 29 | ].freeze 30 | 31 | # SHOW_PAGE_ATTRIBUTES 32 | # an array of attributes that will be displayed on the model's show page. 33 | SHOW_PAGE_ATTRIBUTES = ATTRIBUTE_TYPES.keys 34 | 35 | # FORM_ATTRIBUTES 36 | # an array of attributes that will be displayed 37 | # on the model's form (`new` and `edit`) pages. 38 | FORM_ATTRIBUTES = %i[ 39 | content 40 | status 41 | message 42 | ].freeze 43 | 44 | # COLLECTION_FILTERS 45 | # a hash that defines filters that can be used while searching via the search 46 | # field of the dashboard. 47 | # 48 | # For example to add an option to search for open resources by typing "open:" 49 | # in the search field: 50 | # 51 | # COLLECTION_FILTERS = { 52 | # open: ->(resources) { resources.where(open: true) } 53 | # }.freeze 54 | COLLECTION_FILTERS = {}.freeze 55 | 56 | # Overwrite this method to customize how reports are displayed 57 | # across all pages of the admin dashboard. 58 | # 59 | # def display_resource(report) 60 | # "Report ##{report.id}" 61 | # end 62 | end 63 | -------------------------------------------------------------------------------- /app/dashboards/user_dashboard.rb: -------------------------------------------------------------------------------- 1 | require "administrate/base_dashboard" 2 | 3 | class UserDashboard < Administrate::BaseDashboard 4 | # ATTRIBUTE_TYPES 5 | # a hash that describes the type of each of the model's fields. 6 | # 7 | # Each different type represents an Administrate::Field object, 8 | # which determines how the attribute is displayed 9 | # on pages throughout the dashboard. 10 | ATTRIBUTE_TYPES = { 11 | feeds: Field::HasMany, 12 | reports: Field::HasMany, 13 | id: Field::Number, 14 | email: Field::String, 15 | username: Field::String, 16 | admin: Field::Boolean, 17 | otp_secret_key: Field::String, 18 | otp_module: Field::String.with_options(searchable: false) 19 | }.freeze 20 | 21 | # COLLECTION_ATTRIBUTES 22 | # an array of attributes that will be displayed on the model's index page. 23 | # 24 | # By default, it's limited to four items to reduce clutter on index pages. 25 | # Feel free to add, remove, or rearrange items. 26 | COLLECTION_ATTRIBUTES = %i[ 27 | feeds 28 | reports 29 | id 30 | email 31 | username 32 | admin 33 | ].freeze 34 | 35 | # SHOW_PAGE_ATTRIBUTES 36 | # an array of attributes that will be displayed on the model's show page. 37 | SHOW_PAGE_ATTRIBUTES = ATTRIBUTE_TYPES.keys 38 | 39 | # FORM_ATTRIBUTES 40 | # an array of attributes that will be displayed 41 | # on the model's form (`new` and `edit`) pages. 42 | FORM_ATTRIBUTES = %i[ 43 | id 44 | email 45 | username 46 | admin 47 | ].freeze 48 | 49 | # COLLECTION_FILTERS 50 | # a hash that defines filters that can be used while searching via the search 51 | # field of the dashboard. 52 | # 53 | # For example to add an option to search for open resources by typing "open:" 54 | # in the search field: 55 | # 56 | # COLLECTION_FILTERS = { 57 | # open: ->(resources) { resources.where(open: true) } 58 | # }.freeze 59 | COLLECTION_FILTERS = {}.freeze 60 | 61 | # Overwrite this method to customize how users are displayed 62 | # across all pages of the admin dashboard. 63 | # 64 | # def display_resource(user) 65 | # "User ##{user.id}" 66 | # end 67 | end 68 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/assets/custom.css: -------------------------------------------------------------------------------- 1 | html { 2 | scroll-behavior: smooth; 3 | } 4 | 5 | h2 { 6 | font-size: 3em; 7 | } 8 | 9 | img.image-illustration { 10 | background: white; 11 | padding: 5px; 12 | } -------------------------------------------------------------------------------- /app/javascript/assets/desityle.js: -------------------------------------------------------------------------------- 1 | // Drabkirn Desityle - See License at: https://go.cdadityang.xyz/DcenS 2 | 3 | // Navbar configuration START 4 | document.addEventListener('DOMContentLoaded', function() { 5 | document.addEventListener('turbolinks:load', function () { 6 | const htmlTag = document.querySelector('html'); 7 | 8 | const fixedNavbar = document.querySelector('.fixed-navbar'); 9 | 10 | if(fixedNavbar) { 11 | const fixedNavbarHamburgerMenuButton = document.querySelector('.fixed-navbar-hamburger a'); 12 | const fixedNavbarHamburgerMenuAllLinks = document.querySelector('.fixed-navbar-hamburger .fixed-navbar-hamburger-links'); 13 | const defaultNavbarHamburgerMenuButton = document.querySelector('.default-navbar-hamburger a'); 14 | const defaultNavbarHamburgerMenuAllLinks = document.querySelector('.default-navbar-hamburger .default-navbar-hamburger-links'); 15 | 16 | const fixedNavbarHamburgerMenuEachLinks = document.querySelectorAll('.fixed-navbar-hamburger .fixed-navbar-hamburger-links li a'); 17 | const defaultNavbarHamburgerMenuEachLinks = document.querySelectorAll('.default-navbar-hamburger .default-navbar-hamburger-links li a'); 18 | 19 | 20 | // Navbar Scroll 21 | document.addEventListener('scroll', function() { 22 | if(htmlTag.scrollTop > 70) { 23 | fixedNavbar.style.display = "block"; 24 | } 25 | else { 26 | fixedNavbar.style.display = "none"; 27 | } 28 | }); 29 | 30 | // Navbar Hamburgers 31 | fixedNavbarHamburgerMenuButton.addEventListener('click', function() { 32 | if (fixedNavbarHamburgerMenuAllLinks.style.display === "block") { 33 | fixedNavbarHamburgerMenuAllLinks.style.display = "none"; 34 | } else { 35 | fixedNavbarHamburgerMenuAllLinks.style.display = "block"; 36 | } 37 | }); 38 | 39 | for(let i = 0; i < fixedNavbarHamburgerMenuEachLinks.length; i++) { 40 | (function() { 41 | let temp = i; 42 | 43 | fixedNavbarHamburgerMenuEachLinks[temp].addEventListener('click', function() { 44 | fixedNavbarHamburgerMenuAllLinks.style.display = "none"; 45 | }); 46 | }()); 47 | } 48 | 49 | defaultNavbarHamburgerMenuButton.addEventListener('click', function() { 50 | if (defaultNavbarHamburgerMenuAllLinks.style.display === "block") { 51 | defaultNavbarHamburgerMenuAllLinks.style.display = "none"; 52 | } else { 53 | defaultNavbarHamburgerMenuAllLinks.style.display = "block"; 54 | } 55 | }); 56 | 57 | for(let i = 0; i < defaultNavbarHamburgerMenuEachLinks.length; i++) { 58 | (function() { 59 | let temp = i; 60 | 61 | defaultNavbarHamburgerMenuEachLinks[temp].addEventListener('click', function() { 62 | defaultNavbarHamburgerMenuAllLinks.style.display = "none"; 63 | }); 64 | }()); 65 | } 66 | } 67 | }) 68 | }); 69 | // Navbar configuration END 70 | 71 | 72 | 73 | // Content click modal START 74 | document.addEventListener('DOMContentLoaded', function() { 75 | document.addEventListener('turbolinks:load', function () { 76 | const allContentClickModals = document.querySelectorAll('.content-click-modal'); 77 | const allModals = document.querySelectorAll('.modal'); 78 | 79 | const body = document.querySelector('body'); 80 | 81 | let isModalOpened = false; 82 | let modalNumber; 83 | 84 | for (let i = 0; i < allModals.length; i++) { 85 | (function(){ 86 | let temp = i; 87 | 88 | allContentClickModals[temp].addEventListener('click', function() { 89 | body.style.overflow = "hidden"; 90 | allModals[temp].style.display = "block"; 91 | modalNumber = temp; 92 | isModalOpened = true; 93 | window.location.href = "#modal-dialog"; 94 | }); 95 | 96 | allModals[temp].addEventListener('click', function(e) { 97 | if(isModalOpened) { 98 | if(e.target.id === "modal" || e.target.id === "modal-close") { 99 | body.style.overflow = "auto"; 100 | allModals[modalNumber].style.display = "none"; 101 | isModalOpened = false; 102 | window.location.href = "#"; 103 | } 104 | } 105 | }); 106 | }()); 107 | } 108 | }); 109 | }); 110 | // Content click modal END -------------------------------------------------------------------------------- /app/javascript/channels/consumer.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | 4 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | // Load all the channels within this directory and all subdirectories. 2 | // Channel files must be named *_channel.js. 3 | 4 | const channels = require.context('.', true, /_channel\.js$/) 5 | channels.keys().forEach(channels) 6 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | require("@rails/ujs").start() 7 | require("turbolinks").start() 8 | require("@rails/activestorage").start() 9 | require("channels") 10 | 11 | // Importing Desityle CSS 12 | import 'desityle/build/css/desityle.min.css'; 13 | 14 | // Import our custom styles 15 | import '../assets/custom.css'; 16 | 17 | // Desityle JS won't work for turbolinks, so add custom with turbolinks support 18 | import '../assets/desityle'; 19 | 20 | // Import animate.css for adding animations 21 | import '../assets/animate.min.css'; 22 | 23 | // Import WOW.js - library to animate while scrolling only 24 | import WOW from 'wowjs'; 25 | 26 | // Import clipboardJS - copy the text on the go 27 | import ClipboardJS from 'clipboard'; 28 | 29 | 30 | // Add WOW.js 31 | const wow = new WOW.WOW(); 32 | 33 | document.addEventListener('DOMContentLoaded', function() { 34 | // This is right time to initialize WOW.js 35 | wow.init(); 36 | 37 | document.addEventListener('turbolinks:load', function () { 38 | // Sync WOW.js on page turbolinks reload 39 | wow.sync(); 40 | 41 | // Start clipboardJS 42 | let clipboard = new ClipboardJS('.copytoClipboard'); 43 | 44 | // On successful clipboarding 45 | clipboard.on('success', function(e) { 46 | e.trigger.innerText = "Link copied to clipboard"; 47 | 48 | e.clearSelection(); 49 | }); 50 | 51 | // On error clipboarding 52 | clipboard.on('error', function(e) { 53 | e.trigger.innerText = "Link could not be copied to clipboard."; 54 | 55 | e.clearSelection(); 56 | }); 57 | }); 58 | }); 59 | 60 | // Uncomment to copy all static images under ../images to the output folder and reference 61 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 62 | // or the `imagePath` JavaScript helper below. 63 | // 64 | // const images = require.context('../images', true) 65 | // const imagePath = (name) => images(name, true) 66 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/jobs/feed_save_job.rb: -------------------------------------------------------------------------------- 1 | class FeedSaveJob < ApplicationJob 2 | queue_as :default 3 | 4 | def perform(user_id, encrypted_content) 5 | content = Encryption.decrypt_data(encrypted_content) 6 | 7 | feed = Feed.new 8 | feed.user_id = user_id 9 | feed.content = content 10 | feed.save 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/lib/encryption.rb: -------------------------------------------------------------------------------- 1 | class Encryption 2 | def self.encrypt_data(data) 3 | crypt = ActiveSupport::MessageEncryptor.new(ENV["encrypt_key"]) 4 | crypt.encrypt_and_sign(data) 5 | end 6 | 7 | def self.decrypt_data(data) 8 | crypt = ActiveSupport::MessageEncryptor.new(ENV["encrypt_key"]) 9 | crypt.decrypt_and_verify(data) 10 | end 11 | end -------------------------------------------------------------------------------- /app/lib/message.rb: -------------------------------------------------------------------------------- 1 | class Message 2 | # Custom Messages that can be used application-wide 3 | 4 | ## Feeds messages 5 | def self.feed_created 6 | "Yayay! Your feedback was successfully sent." 7 | end 8 | 9 | def self.feed_public 10 | "Your requested feedback went into public mode, you can anytime make it back to private." 11 | end 12 | 13 | def self.feed_private 14 | "Your requested feedback went back into private mode, you can anytime make it public." 15 | end 16 | 17 | def self.feed_destroyed 18 | "Requested feedback has been destroyed." 19 | end 20 | 21 | ## Users messages 22 | def self.user_not_found 23 | "No user was found with this username. Please try again and if the problem persists, submit us a report here" 24 | end 25 | 26 | def self.user_email_not_confirmed 27 | "This account is not yet activated. Email associated is not yet confirmed. Please try again and if the problem persists, submit us a report here" 28 | end 29 | 30 | def self.require_admin 31 | "You must have admin priviledges to perform such request." 32 | end 33 | 34 | ## Content moderation messages 35 | def self.pii_info_found 36 | "You need to wait!!! You've tried to send some Personal Identifiable Information[PII] which may include your phone number or email or home address. Our goal is to keep your feedback anonymous, so we had to reject your feedback. Please try again without adding any PII data, and if the problem persists, submit us a report below!" 37 | end 38 | 39 | def self.abuse_found 40 | "That was not expected from you!!! Our system has detected that you've tried to abuse while giving your feedback. Our goal is to keep feedbacks abuse-free, so we had to reject your feedback. Please try again without abusing, and if the problem persists, submit us a report below!" 41 | end 42 | 43 | ## Report messages 44 | def self.report_created 45 | "Your report was successfully submitted, bookmark this page and keep visiting this page to see the status of your report." 46 | end 47 | 48 | def self.feed_content_not_found 49 | "You tried to submit the report with invalid. Please try again and if the problem persists, submit us a report here" 50 | end 51 | 52 | ## 2FA related messages 53 | def self.two_fa_empty 54 | "You've two factor enabled for your account, so you'll need to enter your token while signing in. If you've no access to your OTP, submit us a report here" 55 | end 56 | 57 | def self.two_fa_wrong 58 | "You've entered wrong 2FA code. Please try again and if the problem persists, submit us a report here" 59 | end 60 | 61 | def self.two_fa_enabled 62 | "Yayay! Two factor authentication has been successfully enabled for your account, now you'll need to input token to access your account. Your account is now more secure." 63 | end 64 | 65 | def self.two_fa_not_enabled 66 | "We're sorry, we couldn't enable two factor authentication to your account. Please try again and if the problem persists, submit us a report here" 67 | end 68 | 69 | def self.two_fa_disabled 70 | "Woooo! Two factor authentication has been successfully disabled for your account. This has now reduced your account security." 71 | end 72 | 73 | def self.two_fa_not_disabled 74 | "We're sorry, we couldn't disable two factor authentication to your account. Please try again and if the problem persists, submit us a report here" 75 | end 76 | 77 | ## Other messages 78 | def self.api_error 79 | "There is something wrong with our API, don't worry, this problem is from our end. Please try again and if the problem persists, submit us a report here" 80 | end 81 | 82 | def self.ip_throttled_body 83 | "You're not permitted to perform such actions frequently, please try again after some time." 84 | end 85 | end -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: ENV["mailer_from_address"] 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/feed.rb: -------------------------------------------------------------------------------- 1 | class Feed < ApplicationRecord 2 | belongs_to :user 3 | 4 | before_save :encrypt_feed 5 | 6 | ## Make content validation 7 | validates :content, presence: true, length: { minimum: 10, maximum: 500 } 8 | ## User's email must be confirmed to accept feedback 9 | ## This check is done at controller level, but for more security 10 | ## done at model level also 11 | validate :user_confirmed?, on: :create 12 | 13 | private 14 | ## Encrypt the content before saving/updating to DB 15 | def encrypt_feed 16 | self.content = Encryption.encrypt_data(self.content) 17 | end 18 | 19 | ## If someone tired to submit a feedback for a user whose email is not confirmed 20 | ## Throw an error 21 | def user_confirmed? 22 | errors.add(:user, Message.user_email_not_confirmed) unless self.user && self.user.confirmed? 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/report.rb: -------------------------------------------------------------------------------- 1 | class Report < ApplicationRecord 2 | belongs_to :user 3 | before_save :encrypt_feed 4 | 5 | enum status: { pending: 0, rejected: 1, accepted: 2 }, _prefix: true 6 | 7 | ## Make content validation 8 | ## Same as Feed content validation 9 | validates :content, presence: true, length: { minimum: 10, maximum: 500 } 10 | validates :message, length: { maximum: 500 } 11 | ## Make status validation 12 | validates :status, presence: true 13 | 14 | private 15 | ## Encrypt the content before saving/updating to DB 16 | def encrypt_feed 17 | self.content = Encryption.encrypt_data(self.content) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | # Include default devise modules. Others available are: 3 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 4 | devise :database_authenticatable, :registerable, 5 | :recoverable, :rememberable, :validatable, 6 | :confirmable, :lockable, :trackable 7 | 8 | has_many :feeds, dependent: :destroy 9 | has_many :reports 10 | 11 | # OTP Token 12 | has_one_time_password 13 | enum otp_module: { disabled: 0, enabled: 1 }, _prefix: true 14 | attr_accessor :otp_code_token 15 | 16 | # Username 17 | USERNAME_REGEX = /\A[a-z0-9\_]+\z/i 18 | 19 | validates :username, uniqueness: { case_sensitive: true }, presence: true, length: { minimum: 3, maximum: 10 }, format: { with: USERNAME_REGEX, multiline: true } 20 | validates_inclusion_of :admin, in: [true, false] 21 | 22 | before_save :downcase_username 23 | 24 | private 25 | def downcase_username 26 | self.username = self.username.downcase if self.username 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/application/_analytics.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /app/views/application/_flash.html.erb: -------------------------------------------------------------------------------- 1 | <% if notice %> 2 |
3 |
4 |

<%= notice %>

5 |
6 |
7 | <% end %> 8 | 9 | <% if alert %> 10 |
11 |
12 |

<%= alert.html_safe %>

13 |
14 |
15 | <% end %> 16 | 17 | <% if custom_alert %> 18 |
19 |
20 |

We found some errors:

21 |
    22 | <% custom_alert.each do |alert| %> 23 |
  • <%= alert %>
  • 24 | <% end %> 25 |
26 |
27 |
28 | <% end %> -------------------------------------------------------------------------------- /app/views/application/_footer.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/application/_navigation.html.erb: -------------------------------------------------------------------------------- 1 | 53 | 54 | -------------------------------------------------------------------------------- /app/views/feeds/_export.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/feeds/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: feed, local: true, html: { class: "form-tag" }) do |form| %> 2 |
3 | <%= form.label :content, class: "form-field-label" %> 4 |

Write your feedback. Keep it simple and short in less than 500 characters. Also, before writing consider looking at our short guide for more on how you can improve this feedback.

5 | <%= form.text_area :content, class: "form-field-input form-field-textarea", rows: "5", minlength: "10", maxlength: "500", required: true, placeholder: "Write your feedback here..." %> 6 |
7 | 8 |
9 | <%= form.submit "Submit", class: "btn btn-wide btn-rev" %> 10 |
11 | <% end %> 12 | 13 | <%= render 'guide' %> -------------------------------------------------------------------------------- /app/views/feeds/_guide.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/feeds/feedback.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Give feedback to #{params[:username]} - Feedka" 3 | @meta_description = "Give authentic, kindful, and constructive feedback to #{params[:username]}" 4 | %> 5 | 6 |
7 |
8 |

Give Feedback

9 | You're giving feedback to <%= params[:username] %>. Make sure you read our super short, simple guide before writing your feedback. 10 |
11 | 12 |
13 | <%= render 'form', feed: @feed %> 14 |
15 |
16 | 17 | <% if @public_feeds && !@public_feeds.empty? %> 18 |
19 | 20 |
21 |
22 |

Public feedbacks

23 | Feedbacks that are made public by <%= params[:username] %>. 24 |
25 | 26 |
27 | <% @public_feeds.each do |feed| %> 28 |

<%= Encryption.decrypt_data(feed.content) %>

29 |
30 | <% end %> 31 |
32 | 33 | <% unless user_signed_in? %> 34 |
35 |

36 | So what's the wait for? <%= link_to "Get started now", new_user_registration_path, class: "green-bg" %> 37 |

38 |
39 | <% else %> 40 |
41 |

42 | That's it, now it's time to see your feeds. <%= link_to "See your feeds", feeds_path, class: "green-bg" %> 43 |

44 |
45 | <% end %> 46 |
47 | <% end %> 48 | 49 | <%= render 'guide' %> -------------------------------------------------------------------------------- /app/views/feeds/index.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Your feeds - Feedka" 3 | 4 | c_name = ENV["c_name"] 5 | share_message = "Holla, I am looking for ways to improve, so please leave your constructive feedback for me here." 6 | feedback_url = "#{ENV['app_base_domain']}/f/#{current_user.username}" 7 | 8 | facebook_share_url = "https://www.facebook.com/sharer/sharer.php?u=#{feedback_url}"e=#{share_message}&hashtag=#feedka" 9 | twitter_share_url = "https://twitter.com/intent/tweet?text=#{share_message}%0A&hashtags=feedka,#{c_name}&url=#{feedback_url}&via=#{c_name}" 10 | whatsapp_share_url = "https://api.whatsapp.com/send?text=#{share_message}%0A#{feedback_url}" 11 | linkedin_share_url = "https://www.linkedin.com/sharing/share-offsite/?url=#{feedback_url}" 12 | %> 13 | 14 |
15 |
16 |

17 | You're now all set to receive feedback. Here is your unique URL that you can share with others: 18 | <%= link_to "#{ENV['app_base_domain']}/f/#{current_user.username}", feedback_path(username: "#{current_user.username}") %> 19 |

20 | 21 |

22 | 23 |
24 | Share on: 25 | <%= link_to "#{whatsapp_share_url}", class: "a-image ml-5", target: "_blank", rel: "noopener noreferrer" do %> 26 | <%= image_tag "icons/if-whatsapp-50x50.svg", alt: "whatsapp-share-icon" %> 27 | <% end %> 28 | <%= link_to "#{facebook_share_url}", class: "a-image ml-5", target: "_blank", rel: "noopener noreferrer" do %> 29 | <%= image_tag "icons/if-facebook-50x50.svg", alt: "fb-share-icon" %> 30 | <% end %> 31 | <%= link_to "#{twitter_share_url}", class: "a-image ml-5", target: "_blank", rel: "noopener noreferrer" do %> 32 | <%= image_tag "icons/if-twitter-50x50.svg", alt: "twtr-share-icon" %> 33 | <% end %> 34 | <%= link_to "#{linkedin_share_url}", class: "a-image ml-5", target: "_blank", rel: "noopener noreferrer" do %> 35 | <%= image_tag "icons/if-linkedin-50x50.svg", alt: "linkedin-share-icon" %> 36 | <% end %> 37 | 38 |
39 | 40 |

or

41 | 42 |
43 | 44 | <%= link_to "Click here to copy link to clipboard", "javascript:void(0);", class: "copytoClipboard", data: { clipboard_text: "#{share_message} #{ENV['app_base_domain']}/f/#{current_user.username}" } %> 45 |
46 |
47 |
48 | 49 |
50 | 51 |
52 |
53 |

Your feeds

54 | Below is all the feedback that you've received filtered from new to old. You can even choose to make your feedback public or delete em. 55 |
56 | 57 |
58 | <% @feeds.each do |feed| %> 59 | <% feed_content = Encryption.decrypt_data(feed.content) %> 60 |

<%= feed_content %>

61 | <% if feed.public? %> 62 | <%= link_to "Make private", private_feed_path(feed.id), method: :patch, data: { confirm: "Are you sure you want to make this feedback private?" } %> 63 | <% else %> 64 | <%= link_to "Make public", public_feed_path(feed.id), method: :patch, data: { confirm: "Are you sure you want to make this feedback public?" } %> 65 | <% end %> 66 | <%= link_to "Delete", feed_path(feed.id), method: :delete, data: { confirm: "Are you sure you want to delete this feedback?" } %> 67 |
68 | <% end %> 69 |
70 | 71 |
72 |
73 | 74 |
75 |
76 | 77 |
78 | 79 |
80 |

You've reached the end of your feedbacks. Comeback later and see if you get more feedbacks. You won't see feedbacks right away, to learn more <%= link_to "take a tour", root_path(anchor: "tour") %>

81 |
82 |
83 | 84 | <%= render 'export' %> -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% c_name, app_base_domain = ENV["c_name"], ENV["app_base_domain"] %> 5 | 6 | <%= @meta_title ? @meta_title : "Feedka - Get constructive feedback" %> | <%= c_name %> 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | " /> 19 | 20 | 21 | 22 | 23 | 24 | " /> 25 | 26 | 27 | 28 | 29 | " /> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | " /> 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | <%= favicon_link_tag 'logo-180x180.png', rel: 'apple-touch-icon', type: 'image/png', sizes: "180x180" %> 50 | <%= favicon_link_tag 'icons/favicon-32x32.png', type: 'image/png', sizes: "32x32" %> 51 | <%= favicon_link_tag 'icons/favicon-16x16.png', type: 'image/png', sizes: "16x16" %> 52 | 53 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 54 | <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 55 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 56 | 57 | 58 | 59 | <%= render 'navigation' %> 60 | 61 | <%= render 'flash' %> 62 | 63 | <%= yield %> 64 | 65 | <%= render 'footer' %> 66 | 67 | <%= render 'analytics' %> 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/reports/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: report, local: true, html: { class: "form-tag" }) do |form| %> 2 | <% if feed_content.empty? %> 3 |
4 | <%= form.label :content, class: "form-field-label" %> 5 |

Write the problem that you're facing while using our application. Try to keep this sweet and simple in less than 500 characters.

6 | <%= form.text_area :content, class: "form-field-input form-field-textarea", rows: "5", minlength: "10", maxlength: "500", required: true, placeholder: "Write your issue here..." %> 7 |
8 | <% else %> 9 |
10 | <%= form.label :content, class: "form-field-label" %> 11 |

It seems you're having trouble with this content. You cannot edit this part. Just submit us this report and we'll send it to respective user if everything is right.

12 | <%= form.text_area :content, class: "form-field-input form-field-textarea", rows: "5", minlength: "10", maxlength: "500", required: true, value: feed_content, readonly: true %> 13 |
14 | <% end %> 15 | 16 |
17 | <%= form.submit "Submit", class: "btn btn-wide btn-rev" %> 18 |
19 | <% end %> 20 | -------------------------------------------------------------------------------- /app/views/reports/report.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Submit a new report - Feedka" 3 | %> 4 | 5 |
6 |
7 |

<%= "Submit new report" %>

8 | <% if @feed_content %> 9 | If you're having trouble with sending your feedback because our system has blocked it, then submit us a report. When you submit us this report, we'll be able to see your feedback, if we don't find anything wrong with your feedback, we'll send it to the respective user. You can always track the status of your request by using a unique link that we give you after submitting this report. 10 | <% else %> 11 | If you're having trouble with something on our application, then submit us a new report. You can always track the status of your request by using a unique link that we give you after submitting this report. 12 | <% end %> 13 |
14 | 15 |
16 | <% if @feed_content %> 17 | <%= render 'form', report: @report, feed_content: @feed_content %> 18 | <% else %> 19 | <%= render 'form', report: @report, feed_content: "" %> 20 | <% end %> 21 |
22 |
-------------------------------------------------------------------------------- /app/views/reports/show.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Report ##{@report.id} - Feedka" 3 | %> 4 | 5 |
6 |
7 |

<%= "Report ##{@report.id}" %>

8 | Below refers to the report submitted by the anonymous person about the system. 9 |
10 | 11 |
12 |

Report Status: 13 | <% if @report.status == "pending" %> 14 | <%= @report.status %> 15 | <% elsif @report.status == "rejected" %> 16 | <%= @report.status %> 17 | <% else %> 18 | <%= @report.status %> 19 | <% end %> 20 |

21 | 22 |

Message from Admin: 23 | <% if @report.message %> 24 | <%= @report.message %> 25 | <% else %> 26 | We're still investigating your request. 27 | <% end %> 28 |

29 |
30 |
-------------------------------------------------------------------------------- /app/views/users/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Resend confirmation instructions - Feedka" 3 | @meta_description = "If you've signed up recently and still didn't get a confirmation email from us, then you can request to get a new confirmation email here." 4 | %> 5 | 6 |
7 |
8 |

Resend confirmation instructions

9 | If you've signed up recently and still didn't get a confirmation email from us, then you can request to get a new confirmation email below. Please allow up to 10 minutes before re-requesting. If the problem persists, submit us a <%= link_to "report here", submit_report_path(username: "#{ENV['admin_username']}") %>. 10 |
11 | 12 |
13 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: "form-tag" }) do |f| %> 14 | <%= render "users/shared/error_messages", resource: resource %> 15 | 16 |
17 | <%= f.label :email, class: "form-field-label" %> 18 |

Enter the same email address below that you've used to signup at feedka. We use this email address only for authentication purposes, thus no spam, no selling to any 3rd party providers. Like a pinky promise.

19 | <%= f.email_field :email, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), class: "form-field-input", required: true %> 20 |
21 | 22 |
23 | <%= f.submit "Submit", class: "btn btn-wide btn-rev" %> 24 |
25 |
26 | <%= link_to "Back", :back, class: "btn btn-wide" %> 27 |
28 | <% end %> 29 |
30 |
31 | 32 | <%= render "users/shared/links" %> 33 | -------------------------------------------------------------------------------- /app/views/users/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome to Feedka, <%= @email %>!

2 | 3 |

To reduce spam, we require you to confirm your email address before you use Feedka. You can confirm your account email through the link below:

4 | 5 |

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

6 | -------------------------------------------------------------------------------- /app/views/users/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @email %>!

2 | 3 | <% if @resource.try(:unconfirmed_email?) %> 4 |

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

5 | <% else %> 6 |

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

7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/users/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

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

4 | -------------------------------------------------------------------------------- /app/views/users/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

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

4 | 5 |

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

6 | 7 |

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

8 |

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

9 | -------------------------------------------------------------------------------- /app/views/users/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

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

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

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

8 | -------------------------------------------------------------------------------- /app/views/users/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Change your password - Feedka" 3 | @meta_description = "You can reset your password from here. We're glad we can help you with changing your password and getting access to your account." 4 | %> 5 | 6 |
7 |
8 |

Change your password

9 | Yayay! We're glad that we can help you change your password and getting access to your account. You can change the same from below, but please try to remember your password this time. 10 |
11 | 12 |
13 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: "form-tag" }) do |f| %> 14 | <%= render "users/shared/error_messages", resource: resource %> 15 | <%= f.hidden_field :reset_password_token %> 16 | 17 |
18 | <%= f.label :password, class: "form-field-label" %> 19 |

Enter your password. Your password must be a minimum of 8 characters and a maximum of 80 characters. Don't share your password with ANYONE and keep it secret in your brain.

20 | <%= f.password_field :password, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "new-password" %> 21 |
22 | 23 |
24 | <%= f.label :password_confirmation, class: "form-field-label" %> 25 |

Confirm your password that you've entered above. Your password must be a minimum of 8 characters and a maximum of 80 characters. Again, remember not to share your password with ANYONE and keep it secret in your brain.

26 | <%= f.password_field :password_confirmation, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "new-password" %> 27 |
28 | 29 |
30 | <%= f.submit "Submit", class: "btn btn-wide btn-rev" %> 31 |
32 |
33 | <%= link_to "Back", :back, class: "btn btn-wide" %> 34 |
35 | <% end %> 36 |
37 |
38 | 39 | <%= render "users/shared/links" %> 40 | -------------------------------------------------------------------------------- /app/views/users/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Forgot your password? - Feedka" 3 | @meta_description = "If you've forgotten your password, then you can request to get a reset password email link from below." 4 | %> 5 | 6 |
7 |
8 |

Forgot your password?

9 | If you've forgotten your password, then you can request to get a reset password email link from below. 10 |
11 | 12 |
13 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: "form-tag" }) do |f| %> 14 | <%= render "users/shared/error_messages", resource: resource %> 15 | 16 |
17 | <%= f.label :email, class: "form-field-label" %> 18 |

Enter the same email address below that you've used to signup at feedka. We use this email address only for authentication purposes, thus no spam, no selling to any 3rd party providers. Like a pinky promise.

19 | <%= f.email_field :email, autocomplete: "email", class: "form-field-input", required: true %> 20 |
21 | 22 |
23 | <%= f.submit "Submit", class: "btn btn-wide btn-rev" %> 24 |
25 |
26 | <%= link_to "Back", :back, class: "btn btn-wide" %> 27 |
28 | <% end %> 29 |
30 |
31 | 32 | <%= render "users/shared/links" %> -------------------------------------------------------------------------------- /app/views/users/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Account Settings - Profile - Feedka" 3 | @meta_description = "Manage your account settings like changing the password and adding two-factor authentication on this page." 4 | %> 5 | 6 |
7 |
8 |

Your Account

9 | Your Email: <%= resource.email %> 10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |

Manage 2FA

18 | You can add a security layer on your account by enabling Two Factor Authentication(2FA). Every time you log in, you'll need to input the OTP code to access your account. 19 |
20 | 21 |
22 | Current 2FA Status: <%= resource.otp_module_enabled? ? 'Enabled' : 'Disabled' %> 23 | 24 | <% url = resource.otp_module_enabled? ? disable_multi_factor_authentication_user_path(resource) : enable_multi_factor_authentication_user_path(resource) %> 25 | 26 | <%= form_for :multi_factor_authentication, url: url, html: { class: "form-tag mt-10" } do |f| %> 27 | <% unless resource.otp_module_enabled? %> 28 |

29 | Download the open-source andOTP app(<%= link_to "Android", "https://play.google.com/store/apps/details?id=org.shadowice.flocke.andotp", target: "_blank", rel: "noopener noreferrer" %>) or Google Authenticator app(<%= link_to "Android", "https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2", target: "_blank", rel: "noopener noreferrer" %> or <%= link_to "IOS", "https://apps.apple.com/us/app/google-authenticator/id388497605", target: "_blank", rel: "noopener noreferrer" %>), then add the below secret key, press SAVE. Then enter the OTP code below to enable 2FA. 30 |

31 | 32 |

33 | 34 |

Your secret key: <%= resource.otp_secret_key %>

35 | <% end %> 36 | 37 |
38 | <%= f.label :otp_code_token, class: "form-field-label" %> 39 |

Open your authenticator app, and enter the 6 digit OTP code below.

40 | <%= f.text_field :otp_code_token, class: "form-field-input", minlength: "6", maxlength: "6", pattern: "[0-9]*", autocomplete: "one-time-code" %> 41 |
42 | 43 |
44 | <%= f.submit resource.otp_module_enabled? ? 'Disable 2FA' : 'Enable 2FA', class: "btn btn-wide btn-rev" %> 45 |
46 | <% end %> 47 |
48 |
49 | 50 |
51 | 52 |
53 |
54 |

Change password

55 | If you find your password is too tough on you, then you can change your password. We still recommend you to use a strong password to keep hackers away. 56 |
57 | 58 |
59 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: "form-tag" }) do |f| %> 60 | <%= render "users/shared/error_messages", resource: resource %> 61 | 62 |
63 | <%= f.label :current_password, class: "form-field-label" %> 64 |

Before you change your password, we require you to enter your current password first.

65 | <%= f.password_field :current_password, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "current-password" %> 66 |
67 | 68 |
69 | <%= f.label :password, class: "form-field-label" %> 70 |

Leave this blank if you've nothing to change. If you're entering a new password, make sure your password has a minimum of 8 characters and a maximum of 80 characters.

71 | <%= f.password_field :password, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "new-password" %> 72 |
73 | 74 |
75 | <%= f.label :password_confirmation, class: "form-field-label" %> 76 |

Confirm your password that you've entered above. Your password must be a minimum of 8 characters and a maximum of 80 characters.

77 | <%= f.password_field :password_confirmation, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "new-password" %> 78 |
79 | 80 |
81 | <%= f.submit "Update", class: "btn btn-wide btn-rev" %> 82 |
83 | <% end %> 84 |
85 |
86 | 87 |
88 | 89 |
90 |
91 |

Delete my account

92 | This is the danger zone, your account, and all feedbacks will be deleted. However, reports related to your account will not be deleted! 93 |
94 | 95 |
96 |

I'm sure, please delete my account: <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: "dark-red-bg" %>

97 |
98 |
99 | 100 |
101 | 102 |
103 | <%= link_to "Back", :back, class: "btn btn-wide" %> 104 |
105 | -------------------------------------------------------------------------------- /app/views/users/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Create an account - Sign up - Feedka" 3 | @meta_description = "Get started getting feedback by creating an account. You've made the right decision, and you're in a good company." 4 | %> 5 | 6 |
7 |
8 |

Sign up

9 | Get started getting feedback by creating an account. You've made the right decision, and you're in a good company. 10 |
11 | 12 |
13 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "form-tag" }) do |f| %> 14 |
15 | <%= f.label :email, class: "form-field-label" %> 16 |

Enter your email. Make sure your email is valid and you have access to this email. We use this email address only for authentication purposes, thus no spam, no selling to any 3rd party providers. Like a pinky promise.

17 | <%= f.email_field :email, autocomplete: "email", class: "form-field-input", required: true %> 18 |
19 | 20 |
21 | <%= f.label :username, class: "form-field-label" %> 22 |

Enter your username. Your username must be a minimum of 3 characters and a maximum of 10 characters. Tip: Add your first/last name in your username so that people can identify that it's genuinely you to whom they're sending the feedback.

23 | <%= f.text_field :username, autocomplete: "username", class: "form-field-input", required: true, minlength: "3", maxlength: "10" %> 24 |
25 | 26 |
27 | <%= f.label :password, class: "form-field-label" %> 28 |

Enter your password. Your password must be a minimum of 8 characters and a maximum of 80 characters. Don't share your password with ANYONE and keep it secret in your brain.

29 | <%= f.password_field :password, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "new-password" %> 30 |
31 | 32 |
33 | <%= f.label :password_confirmation, class: "form-field-label" %> 34 |

Confirm your password that you've entered above. Your password must be a minimum of 8 characters and a maximum of 80 characters. Again, remember not to share your password with ANYONE and keep it secret in your brain.

35 | <%= f.password_field :password_confirmation, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "new-password" %> 36 |
37 | 38 |
39 | <%= f.submit "Sign up", class: "btn btn-wide btn-rev" %> 40 |
41 |
42 | <%= link_to "Back", :back, class: "btn btn-wide" %> 43 |
44 | <% end %> 45 |
46 |
47 | 48 | <%= render "users/shared/links" %> -------------------------------------------------------------------------------- /app/views/users/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Log in - Feedka" 3 | @meta_description = "Log in securely to your account. By logging in, you can manage your feedbacks and account." 4 | %> 5 | 6 |
7 |
8 |

Log in

9 | Log in securely to your account. By logging in, you can manage your feedbacks and account. 10 |
11 | 12 |
13 | <%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "form-tag" }) do |f| %> 14 |
15 | <%= f.label :email, class: "form-field-label" %> 16 |

Enter the email that you've used while signing up for your account. We use this email address only for authentication purposes, thus no spam, no selling to any 3rd party providers. Like a pinky promise.

17 | <%= f.email_field :email, autocomplete: "email", class: "form-field-input", required: true %> 18 |
19 | 20 |
21 | <%= f.label :password, class: "form-field-label" %> 22 |

Enter the password that you've used while signing up for your account.

23 | <%= f.password_field :password, class: "form-field-input", required: true, minlength: "8", maxlength: "80", autocomplete: "current-password" %> 24 |
25 | 26 |
27 | <%= f.label :otp_code_token, class: "form-field-label" %> 28 |

If you've enabled 2FA for your account, then enter the OTP code shown in your app below. Leave this blank if you've not done anything.

29 | <%= f.text_field :otp_code_token, class: "form-field-input", minlength: "6", maxlength: "6", pattern: "[0-9]*", autocomplete: "one-time-code" %> 30 |
31 | 32 | <% if devise_mapping.rememberable? %> 33 |
34 | 35 |

If you're using a shared computer or shared mobile, then make sure to un-tick the box below. If this is your private computer or mobile, then leave it ticked.

36 | 37 | <%= f.label :remember_me, class: "form-field-label-checkbox" do %> 38 | Remeber me 39 | <%= f.check_box :remember_me, class: "form-field-checkbox", checked: true %> 40 | 41 | <% end %> 42 |
43 | <% end %> 44 | 45 |
46 | <%= f.submit "Log in", class: "btn btn-wide btn-rev" %> 47 |
48 |
49 | <%= link_to "Back", :back, class: "btn btn-wide" %> 50 |
51 | <% end %> 52 |
53 |
54 | 55 | <%= render "users/shared/links" %> 56 | -------------------------------------------------------------------------------- /app/views/users/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

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

6 | 7 | 12 |
13 | <% end %> -------------------------------------------------------------------------------- /app/views/users/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%- if controller_name != 'sessions' %> 4 | <%= link_to "Log in", new_session_path(resource_name) %>

5 | <% end %> 6 | 7 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 8 | <%= link_to "Sign up", new_registration_path(resource_name) %>

9 | <% end %> 10 | 11 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 12 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>

13 | <% end %> 14 | 15 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 16 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>

17 | <% end %> 18 | 19 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 20 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %> 21 | <% end %> 22 | 23 | <%- if devise_mapping.omniauthable? %> 24 | <%- resource_class.omniauth_providers.each do |provider| %> 25 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
26 | <% end %> 27 | <% end %> 28 |
29 |
-------------------------------------------------------------------------------- /app/views/users/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | @meta_title = "Resend unlock instructions - Feedka" 3 | @meta_description = "If your account was locked due to some reason, then you can use the below form to send the unlock instructions to your email." 4 | %> 5 | 6 |
7 |
8 |

Resend unlock instructions

9 | If your account was locked due to some reason, then you can use the below form to send the unlock instructions to your email. 10 |
11 | 12 |
13 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: "form-tag" }) do |f| %> 14 | <%= render "users/shared/error_messages", resource: resource %> 15 | 16 |
17 | <%= f.label :email, class: "form-field-label" %> 18 |

Enter the email that you've used while signing up for your account. We use this email address only for authentication purposes, thus no spam, no selling to any 3rd party providers. Like a pinky promise.

19 | <%= f.email_field :email, autocomplete: "email", class: "form-field-input", required: true %> 20 |
21 | 22 |
23 | <%= f.submit "Submit", class: "btn btn-wide btn-rev" %> 24 |
25 |
26 | <%= link_to "Back", :back, class: "btn btn-wide" %> 27 |
28 | <% end %> 29 |
30 |
31 | 32 | <%= render "users/shared/links" %> 33 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ['development', 'test', 'production'] 3 | var currentEnv = api.env() 4 | var isDevelopmentEnv = api.env('development') 5 | var isProductionEnv = api.env('production') 6 | var isTestEnv = api.env('test') 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | 'Please specify a valid `NODE_ENV` or ' + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | '.' 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | '@babel/preset-env', 22 | { 23 | targets: { 24 | node: 'current' 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | '@babel/preset-env', 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: 'entry', 33 | corejs: 3, 34 | modules: false, 35 | exclude: ['transform-typeof-symbol'] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | 'babel-plugin-macros', 41 | '@babel/plugin-syntax-dynamic-import', 42 | isTestEnv && 'babel-plugin-dynamic-import-node', 43 | '@babel/plugin-transform-destructuring', 44 | [ 45 | '@babel/plugin-proposal-class-properties', 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | '@babel/plugin-proposal-object-rest-spread', 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | '@babel/plugin-transform-runtime', 58 | { 59 | helpers: false, 60 | regenerator: true, 61 | corejs: false 62 | } 63 | ], 64 | [ 65 | '@babel/plugin-transform-regenerator', 66 | { 67 | async: false 68 | } 69 | ] 70 | ].filter(Boolean) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 || ">= 0.a" 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= begin 65 | env_var_version || cli_arg_version || 66 | lockfile_version || "#{Gem::Requirement.default}.a" 67 | end 68 | end 69 | 70 | def load_bundler! 71 | ENV["BUNDLE_GEMFILE"] ||= gemfile 72 | 73 | # must dup string for RG < 1.8 compatibility 74 | activate_bundler(bundler_version.dup) 75 | end 76 | 77 | def activate_bundler(bundler_version) 78 | if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0") 79 | bundler_version = "< 2" 80 | end 81 | gem_error = activation_error_handling do 82 | gem "bundler", bundler_version 83 | end 84 | return if gem_error.nil? 85 | require_error = activation_error_handling do 86 | require "bundler/version" 87 | end 88 | return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 89 | warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" 90 | exit 42 91 | end 92 | 93 | def activation_error_handling 94 | yield 95 | nil 96 | rescue StandardError, LoadError => e 97 | e 98 | end 99 | end 100 | 101 | m.load_bundler! 102 | 103 | if m.invoked_as_script? 104 | load Gem.bin_path("bundler", "bundle") 105 | end 106 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:prepare' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads Spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application-codespaces.yml: -------------------------------------------------------------------------------- 1 | test: 2 | db_hostname: "localhost" 3 | db_username: "root" 4 | db_password: "1234" 5 | db_name: "feedka_test" 6 | 7 | app_hostname: 8 | app_portname: 9 | app_base_domain: 10 | c_name: "Drabkirn" 11 | admin_username: 12 | 13 | mailer_from_address: "test@feedka.xyz" 14 | 15 | devise_secret_key: "9ecf0de808ee1f6aea6bdee27371c9ff31af7165983704dcf5b4020b73e45bbe1d5bb5853eaac910881ac1612bf3e84b44670f5467b85aa4efb3b7920ffdb29d" 16 | devise_pepper_hash: "56fa8e124eaba498a9220251c23d1a1b6332d615512a7e452f2637926452a6f8d6ed4316a1cb60da055b05b79f6708adeee60bdfd6f371f7afd1f2090af72f61" 17 | 18 | encrypt_key: "13f5f2efb73a2f0912f1b3c93f38d9d8" 19 | 20 | REDIS_URL: "redis://localhost:6379/1" 21 | 22 | content_moderation_url: "https://contentmoderation.feedka.xyz/test" 23 | content_moderation_api_key: "abcd" 24 | 25 | ci_true: 26 | 27 | 28 | development: 29 | db_hostname: "localhost" 30 | db_username: "root" 31 | db_password: "1234" 32 | db_name: "feedka_development" 33 | 34 | app_hostname: 35 | app_port: 36 | app_base_domain: 37 | c_name: "Drabkirn" 38 | admin_username: "abcd" 39 | 40 | mailer_from_address: "dev@feedka.xyz" 41 | 42 | devise_secret_key: "e3016338752a2822fea428aac7acae4bb327b86743a1b48d40506d8ee9d4a0cbd31254804c634901478bd1ca1cc64585a588c0e627bbd518e8caf3e91c550fdb" 43 | devise_pepper_hash: "4d1ffb39c3719e9278d3f4b8481d85ec47ca69fcf75815a80b7795e822922400f18ed48ccfe8d0bf5e9b1962c7e5e438de469b3b10f3ce2e09c47c8e9012ebf3" 44 | 45 | encrypt_key: "48edfc5c8b3ac7192a806b2e9630317b" 46 | 47 | REDIS_URL: "redis://localhost:6379/1" 48 | 49 | content_moderation_url: "https://contentmoderation.feedka.xyz/test" 50 | content_moderation_api_key: "abcd" 51 | 52 | 53 | production: 54 | db_hostname: 55 | db_username: 56 | db_password: 57 | db_name: 58 | 59 | app_hostname: 60 | app_port: 61 | app_base_domain: 62 | c_name: 63 | admin_username: 64 | 65 | mailer_from_address: 66 | 67 | sendgrid_api_key: 68 | sendgrid_domain: 69 | 70 | devise_secret_key: 71 | devise_pepper_hash: 72 | 73 | encrypt_key: 74 | 75 | REDIS_URL: 76 | 77 | content_moderation_url: 78 | content_moderation_api_key: -------------------------------------------------------------------------------- /config/application-sample.yml: -------------------------------------------------------------------------------- 1 | test: 2 | db_hostname: 3 | db_username: 4 | db_password: 5 | db_name: 6 | 7 | app_hostname: 8 | app_port: 9 | app_base_domain: 10 | c_name: 11 | admin_username: 12 | 13 | mailer_from_address: 14 | 15 | devise_secret_key: 16 | devise_pepper_hash: 17 | 18 | encrypt_key: 19 | 20 | REDIS_URL: 21 | 22 | content_moderation_url: 23 | content_moderation_api_key: 24 | 25 | ci_true: 26 | 27 | 28 | development: 29 | db_hostname: 30 | db_username: 31 | db_password: 32 | db_name: 33 | 34 | app_hostname: 35 | app_port: 36 | app_base_domain: 37 | c_name: 38 | admin_username: 39 | 40 | mailer_from_address: 41 | 42 | devise_secret_key: 43 | devise_pepper_hash: 44 | 45 | encrypt_key: 46 | 47 | REDIS_URL: 48 | 49 | content_moderation_url: 50 | content_moderation_api_key: 51 | 52 | 53 | production: 54 | db_hostname: 55 | db_username: 56 | db_password: 57 | db_name: 58 | 59 | app_hostname: 60 | app_port: 61 | app_base_domain: 62 | c_name: 63 | admin_username: 64 | 65 | mailer_from_address: 66 | 67 | sendgrid_api_key: 68 | sendgrid_domain: 69 | 70 | devise_secret_key: 71 | devise_pepper_hash: 72 | 73 | encrypt_key: 74 | 75 | REDIS_URL: 76 | 77 | content_moderation_url: 78 | content_moderation_api_key: -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Feedka 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 6.0 13 | 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration can go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded after loading 17 | # the framework and any gems in your application. 18 | 19 | ## Custom 20 | # Don't run un-required generations of files 21 | config.generators do |g| 22 | g.test_framework :rspec, 23 | view_specs: false, 24 | helper_specs: false, 25 | routing_specs: false, 26 | controller_specs: true, 27 | request_specs: false 28 | g.helper false 29 | # Uncomment if using React/Others for View 30 | # g.assets false 31 | # g.template_engine false 32 | 33 | # Set sidekiq as default AJ queue adapter 34 | config.active_job.queue_adapter = :sidekiq 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | redis: &redis 2 | adapter: redis 3 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 4 | channel_prefix: feedka_production 5 | 6 | development: *redis 7 | test: *redis 8 | production: *redis -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | BQS+HaSlIi+nRjzVuFwW3LoC8ncZndCew+4d7dfIhALoB4ADzeS3jIxlImtpdn3Bvr6brTZgmRVaKRaMEXku7JUn2eLOC0YJZ3n0lqce+O9g882wXrjyvEnvr1GAza+/8FspUurSTfOzCGow4rkBCk3BpPeC8ZAX0mZLFtJdDAuVzUNwCsiZYgXTbYUpFLWpzFmUGiEKHMZFIs3JgiCArheHpuHlR9XfSGoC0SHF5hnicXQzZTUVdgl1SmZLXByD3n1LNBvNTSfCUokD+JeDLrEraHPcA7jcb7lcUnSv+30KK5tR/cP+7vUXP6a+Qznma19gTyAY+SQMJLJ/DhOq8cUdSE/LK5PwCMuRVBgmdcxb17F5RhqAtEHDcqWeVL6FSpI7xvj1OmtfK2MJQlQ0oe3r8d18XfsupwD+--jqtnSqJSwbz6/f9p--b5reDp9mGfP8IYGogBGXBw== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # MySQL Versions 5.5.8 and up are supported. 2 | 3 | default: &default 4 | adapter: mysql2 5 | encoding: utf8mb4 6 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 7 | socket: /var/run/mysqld/mysqld.sock 8 | host: <%= ENV["db_hostname"] %> 9 | username: <%= ENV["db_username"] %> 10 | password: <%= ENV["db_password"] %> 11 | 12 | development: 13 | <<: *default 14 | socket: <%= ENV["db_socket_path"] %> 15 | database: <%= ENV["db_name"] %> 16 | 17 | # Do not set this db to the same as development or production. 18 | test: 19 | <<: *default 20 | socket: <%= ENV["db_socket_path"] %> 21 | database: <%= ENV["db_name"] %> 22 | 23 | # You can use this database configuration with: 24 | # production: 25 | # url: <%= ENV['DATABASE_URL'] %> 26 | production: 27 | <<: *default 28 | database: <%= ENV["db_name"] %> -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 18 | config.action_controller.perform_caching = true 19 | config.action_controller.enable_fragment_cache_logging = true 20 | 21 | config.cache_store = :memory_store 22 | config.public_file_server.headers = { 23 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 24 | } 25 | else 26 | config.action_controller.perform_caching = false 27 | 28 | config.cache_store = :null_store 29 | end 30 | 31 | # Store uploaded files on the local file system (see config/storage.yml for options). 32 | config.active_storage.service = :local 33 | 34 | # Don't care if the mailer can't send. 35 | config.action_mailer.raise_delivery_errors = false 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Print deprecation notices to the Rails logger. 40 | config.active_support.deprecation = :log 41 | 42 | # Raise an error on page load if there are pending migrations. 43 | config.active_record.migration_error = :page_load 44 | 45 | # Highlight code that triggered database queries in logs. 46 | config.active_record.verbose_query_logs = true 47 | 48 | # Debug mode disables concatenation and preprocessing of assets. 49 | # This option may cause significant delays in view rendering with a large 50 | # number of complex assets. 51 | config.assets.debug = true 52 | 53 | # Suppress logger output for asset requests. 54 | config.assets.quiet = true 55 | 56 | # Raises error for missing translations. 57 | # config.action_view.raise_on_missing_translations = true 58 | 59 | # Use an evented file watcher to asynchronously detect changes in source code, 60 | # routes, locales, etc. This feature depends on the listen gem. 61 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 62 | 63 | ## Custom 64 | # Devise Mailer 65 | config.action_mailer.default_url_options = { host: ENV["app_hostname"], port: ENV["app_port"].to_i } 66 | end 67 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # The test environment is used exclusively to run your application's 2 | # test suite. You never need to work with it otherwise. Remember that 3 | # your test database is "scratch space" for the test suite and is wiped 4 | # and recreated between test runs. Don't rely on the data there! 5 | 6 | # Silence depreciation warings for Ruby 2.7 7 | $VERBOSE = nil if ENV["ci_true"] 8 | 9 | Rails.application.configure do 10 | # Settings specified here will take precedence over those in config/application.rb. 11 | 12 | config.cache_classes = false 13 | 14 | # Do not eager load code on boot. This avoids loading your whole application 15 | # just for the purpose of running a single test. If you are using a tool that 16 | # preloads Rails for running tests, you may have to set it to true. 17 | config.eager_load = false 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Tell Action Mailer not to deliver emails to the real world. 42 | # The :test delivery method accumulates sent emails in the 43 | # ActionMailer::Base.deliveries array. 44 | config.action_mailer.delivery_method = :test 45 | 46 | # Print deprecation notices to the stderr. 47 | config.active_support.deprecation = :stderr 48 | 49 | # Raises error for missing translations. 50 | # config.action_view.raise_on_missing_translations = true 51 | 52 | ## Custom 53 | # Devise mailer 54 | config.action_mailer.default_url_options = { host: ENV["app_hostname"], port: ENV["app_portname"].to_i } 55 | end 56 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | # # If you are using webpack-dev-server then specify webpack-dev-server host 15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 16 | 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | 21 | # If you are using UJS then enable automatic nonce generation 22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 23 | 24 | # Set the nonce only to specific directives 25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 26 | 27 | # Report CSP violations to a specified URI 28 | # For further information see the following documentation: 29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 30 | # Rails.application.config.content_security_policy_report_only = true 31 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/rack_attack.rb: -------------------------------------------------------------------------------- 1 | class Rack::Attack 2 | ### Configure Cache ### 3 | ## Defaults to `Rails.cache`, but we'll use REDIS 4 | redis_client = Redis.new(url: ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" }) 5 | Rack::Attack.cache.store = Rack::Attack::StoreProxy::RedisStoreProxy.new(redis_client) 6 | 7 | ### Throttle Spammy Clients ### 8 | ## We're setting a limit of 30rpm/IP for enpoints_array paths 9 | ## If using CDN, this will count it as that also 10 | endpoints_array = ["/", "/feeds" ] 11 | throttle("req/ip", :limit => 30, :period => 1.minute) do |req| 12 | req.ip if endpoints_array.include?(req.path) 13 | end 14 | 15 | ## Exponential backoff for all requests to root path 16 | ## Allows 160 requests in ~8 minutes 17 | ## 320 requests in ~1 hour 18 | ## 640 requests in ~8 hours (~1920 requests/day) 19 | (3..5).each do |level| 20 | throttle("req/ip/#{level}", :limit => (20 * (2 ** level)), :period => (0.9 * (8 ** level)).to_i.seconds) do |req| 21 | req.ip if endpoints_array.include?(req.path) 22 | end 23 | end 24 | 25 | ### Prevent Brute-Force Login, Signup, Feeds create Attacks ### 26 | ## Don't allow if user signs in/signs up 3 times in less than 15 mins 27 | throttle("users/sign_in", limit: 3, period: 15.minutes) do |req| 28 | req.ip if req.path == "/users/sign_in" && req.post? 29 | end 30 | 31 | throttle("users/sign_up", limit: 3, period: 15.minutes) do |req| 32 | req.ip if req.path == "/users" && req.post? 33 | end 34 | 35 | throttle("/feeds/create", limit: 3, period: 15.minutes) do |req| 36 | req.ip if req.path == "/feeds" && req.post? 37 | end 38 | 39 | ## Custom Throttled response for scrappers/hackers to see 40 | self.throttled_response = lambda do |env| 41 | [ 42 | 503, # status 43 | {}, # headers 44 | [Message.ip_throttled_body] # body 45 | ] 46 | end 47 | end if Rails.env.production? 48 | 49 | 50 | ## Send the notification when a request is throttled 51 | ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, payload| 52 | req = payload[:request] 53 | if %i[throttle].include?(req.env['rack.attack.match_type']) 54 | Rails.logger.info "[Rack::Attack][Blocked] - remote_ip: #{req.ip} - path: #{req.path}" 55 | end 56 | end -------------------------------------------------------------------------------- /config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.production? 2 | 3 | Sidekiq.configure_client do |config| 4 | config.redis = { url: ENV['REDIS_URL'] } 5 | end 6 | 7 | Sidekiq.configure_server do |config| 8 | config.redis = { url: ENV['REDIS_URL'] } 9 | end 10 | end -------------------------------------------------------------------------------- /config/initializers/smtp.rb: -------------------------------------------------------------------------------- 1 | if Rails.env.production? 2 | ActionMailer::Base.smtp_settings = { 3 | domain: ENV["sendgrid_domain"], 4 | address: "smtp.sendgrid.net", 5 | port: 587, 6 | authentication: :plain, 7 | user_name: "apikey", 8 | password: ENV["sendgrid_api_key"] 9 | } 10 | end -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed. You can now signin and access Feedka." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Feedka - Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Feedka - Reset password instructions" 24 | unlock_instructions: 25 | subject: "Feedka - Unlock instructions" 26 | email_changed: 27 | subject: "Feedka - Email Changed" 28 | password_change: 29 | subject: "Feedka - Password Changed" 30 | omniauth_callbacks: 31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 32 | success: "Successfully authenticated from %{kind} account." 33 | passwords: 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 37 | updated: "Your password has been changed successfully. You are now signed in." 38 | updated_not_active: "Your password has been changed successfully." 39 | registrations: 40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 41 | signed_up: "Welcome! You have signed up successfully." 42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account. To reduce spam, we require you to confirm your email address before you use Feedka." 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again" 48 | sessions: 49 | signed_in: "Signed in successfully." 50 | signed_out: "Signed out successfully." 51 | already_signed_out: "Signed out successfully." 52 | unlocks: 53 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 54 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 55 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 56 | errors: 57 | messages: 58 | already_confirmed: "was already confirmed, please try signing in" 59 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 60 | expired: "has expired, please request a new one" 61 | not_found: "not found" 62 | not_locked: "was not locked" 63 | not_saved: 64 | one: "1 error prohibited this %{resource} from being saved:" 65 | other: "%{count} errors prohibited this %{resource} from being saved:" 66 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | 12 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 13 | # 14 | port ENV.fetch("PORT") { 3000 } 15 | 16 | 17 | # Specifies the `environment` that Puma will run in. 18 | # 19 | environment ENV.fetch("RAILS_ENV") { "development" } 20 | 21 | # Specifies the `pidfile` that Puma will use. 22 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 23 | 24 | # Specifies the number of `workers` to boot in clustered mode. 25 | # Workers are forked web server processes. If using threads and workers together 26 | # the concurrency of the application would be max `threads` * `workers`. 27 | # Workers do not work on JRuby or Windows (both of which do not support 28 | # processes). 29 | # 30 | workers ENV.fetch("WEB_CONCURRENCY") { 2 } 31 | 32 | # Use the `preload_app!` method when specifying a `workers` number. 33 | # This directive tells Puma to first boot the application and load code 34 | # before forking the application. This takes advantage of Copy On Write 35 | # process behavior so workers use less memory. 36 | # 37 | preload_app! 38 | 39 | # Allow puma to be restarted by `rails restart` command. 40 | plugin :tmp_restart 41 | 42 | ## Avoid connection leakage 43 | before_fork do 44 | puts "Puma master process about to fork. Closing existing Active record connections." 45 | ActiveRecord::Base.connection.disconnect! 46 | end 47 | 48 | on_worker_boot do 49 | ActiveRecord::Base.establish_connection 50 | end -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root "home#index" 3 | 4 | ## Use devise for users, except sessions - We add 2FA here 5 | devise_for :users, controllers: { sessions: 'users/sessions' } 6 | 7 | ## Empty [] because unrequired `users` routes are not created 8 | ## Add 2FA endpoints 9 | resources :users, only: [] do 10 | member do 11 | post :enable_multi_factor_authentication, to: 'users/multi_factor_authentication#verify_enable' 12 | post :disable_multi_factor_authentication, to: 'users/multi_factor_authentication#verify_disable' 13 | end 14 | end 15 | 16 | ## Feeds endpoint 17 | resources :feeds, only: [:index, :destroy, :create] do 18 | member do 19 | patch :public 20 | patch :private 21 | end 22 | end 23 | 24 | ## Non-login users can give feedback to users on this endpoint 25 | get '/f/:username', as: "feedback", to: "feeds#feedback" 26 | 27 | ## Reports 28 | resources :reports, only: [:show, :create] 29 | get '/r/:username', as: "submit_report", to: "reports#report" 30 | 31 | ## Export feedbacks 32 | get '/feeds/export', as: "export_feedbacks", to: "feeds#export" 33 | 34 | ## Administrate for admins 35 | namespace :admin do 36 | resources :users 37 | resources :reports 38 | resources :feeds 39 | 40 | root to: "reports#index" 41 | end 42 | 43 | ## Redis Endpoint - Only for admins 44 | authenticate :user, lambda { |u| u.admin? } do 45 | require 'sidekiq/web' 46 | mount Sidekiq::Web => '/sidekiq' 47 | end 48 | 49 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 50 | end 51 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | development: 2 | :concurrency: 5 3 | production: 4 | :concurrency: 1 5 | :queues: 6 | - default 7 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | check_yarn_integrity: false 10 | webpack_compile_output: true 11 | 12 | # Additional paths webpack should lookup modules 13 | # ['app/assets', 'engine/foo/app/assets'] 14 | resolved_paths: [] 15 | 16 | # Reload manifest.json on all requests so we reload latest compiled packs 17 | cache_manifest: false 18 | 19 | # Extract and emit a css file 20 | extract_css: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | - .eot 31 | - .otf 32 | - .ttf 33 | - .woff 34 | - .woff2 35 | 36 | extensions: 37 | - .mjs 38 | - .js 39 | - .sass 40 | - .scss 41 | - .css 42 | - .module.sass 43 | - .module.scss 44 | - .module.css 45 | - .png 46 | - .svg 47 | - .gif 48 | - .jpeg 49 | - .jpg 50 | 51 | development: 52 | <<: *default 53 | compile: true 54 | 55 | # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules 56 | check_yarn_integrity: true 57 | 58 | # Reference: https://webpack.js.org/configuration/dev-server/ 59 | dev_server: 60 | https: false 61 | host: 0.0.0.0 62 | port: 3003 63 | public: 0.0.0.0:3003 64 | hmr: false 65 | # Inline should be set to true if using HMR 66 | inline: true 67 | overlay: true 68 | compress: true 69 | disable_host_check: true 70 | use_local_ip: false 71 | quiet: false 72 | pretty: false 73 | headers: 74 | 'Access-Control-Allow-Origin': '*' 75 | watch_options: 76 | ignored: '**/node_modules/**' 77 | 78 | 79 | test: 80 | <<: *default 81 | compile: true 82 | 83 | # Compile test packs to a separate directory 84 | public_output_path: packs-test 85 | 86 | production: 87 | <<: *default 88 | 89 | # Production depends on precompilation of packs prior to booting for performance. 90 | compile: false 91 | 92 | # Extract and emit a css file 93 | extract_css: true 94 | 95 | # Cache manifest.json for performance 96 | cache_manifest: true 97 | -------------------------------------------------------------------------------- /db/migrate/20200425073220_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :users do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | t.integer :sign_in_count, default: 0, null: false 19 | t.datetime :current_sign_in_at 20 | t.datetime :last_sign_in_at 21 | t.string :current_sign_in_ip 22 | t.string :last_sign_in_ip 23 | 24 | ## Confirmable 25 | t.string :confirmation_token 26 | t.datetime :confirmed_at 27 | t.datetime :confirmation_sent_at 28 | t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | t.string :unlock_token # Only if unlock strategy is :email or :both 33 | t.datetime :locked_at 34 | 35 | 36 | t.timestamps null: false 37 | end 38 | 39 | add_index :users, :email, unique: true 40 | add_index :users, :reset_password_token, unique: true 41 | add_index :users, :confirmation_token, unique: true 42 | add_index :users, :unlock_token, unique: true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /db/migrate/20200425075410_add_columns_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddColumnsToUsers < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :users, :username, :string 4 | add_column :users, :admin, :boolean, default: false 5 | 6 | add_column :users, :otp_secret_key, :string 7 | add_column :users, :otp_module, :integer, default: 0 8 | 9 | add_index :users, :username, unique: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200425102203_create_feeds.rb: -------------------------------------------------------------------------------- 1 | class CreateFeeds < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :feeds do |t| 4 | t.text :content, null: false 5 | t.boolean :public, default: false 6 | t.references :user, null: false, foreign_key: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200429071335_create_reports.rb: -------------------------------------------------------------------------------- 1 | class CreateReports < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :reports do |t| 4 | t.text :content, null: false 5 | t.integer :status, default: 0 6 | t.text :message 7 | t.references :user, null: false, foreign_key: true 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2020_04_29_071335) do 14 | 15 | create_table "feeds", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| 16 | t.text "content", null: false 17 | t.boolean "public", default: false 18 | t.bigint "user_id", null: false 19 | t.datetime "created_at", precision: 6, null: false 20 | t.datetime "updated_at", precision: 6, null: false 21 | t.index ["user_id"], name: "index_feeds_on_user_id" 22 | end 23 | 24 | create_table "reports", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| 25 | t.text "content", null: false 26 | t.integer "status", default: 0 27 | t.text "message" 28 | t.bigint "user_id", null: false 29 | t.datetime "created_at", precision: 6, null: false 30 | t.datetime "updated_at", precision: 6, null: false 31 | t.index ["user_id"], name: "index_reports_on_user_id" 32 | end 33 | 34 | create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| 35 | t.string "email", default: "", null: false 36 | t.string "encrypted_password", default: "", null: false 37 | t.string "reset_password_token" 38 | t.datetime "reset_password_sent_at" 39 | t.datetime "remember_created_at" 40 | t.integer "sign_in_count", default: 0, null: false 41 | t.datetime "current_sign_in_at" 42 | t.datetime "last_sign_in_at" 43 | t.string "current_sign_in_ip" 44 | t.string "last_sign_in_ip" 45 | t.string "confirmation_token" 46 | t.datetime "confirmed_at" 47 | t.datetime "confirmation_sent_at" 48 | t.string "unconfirmed_email" 49 | t.integer "failed_attempts", default: 0, null: false 50 | t.string "unlock_token" 51 | t.datetime "locked_at" 52 | t.datetime "created_at", precision: 6, null: false 53 | t.datetime "updated_at", precision: 6, null: false 54 | t.string "username" 55 | t.boolean "admin", default: false 56 | t.string "otp_secret_key" 57 | t.integer "otp_module", default: 0 58 | t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true 59 | t.index ["email"], name: "index_users_on_email", unique: true 60 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 61 | t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true 62 | t.index ["username"], name: "index_users_on_username", unique: true 63 | end 64 | 65 | add_foreign_key "feeds", "users" 66 | add_foreign_key "reports", "users" 67 | end 68 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | web: 5 | build: 6 | dockerfile: Dockerfile 7 | context: . 8 | volumes: 9 | - ./:/usr/src/app 10 | working_dir: /usr/src/app 11 | ports: 12 | - "3000:3000" 13 | command: rails s -b 0.0.0.0 -p 3000 14 | depends_on: 15 | - mysql 16 | - redis 17 | links: 18 | - mysql 19 | 20 | redis: 21 | image: 'redis:5.0.8' 22 | command: redis-server 23 | volumes: 24 | - 'redis:/data' 25 | 26 | mysql: 27 | image: mysql:5.7.18 28 | environment: 29 | - MYSQL_ROOT_PASSWORD=root 30 | volumes: 31 | - 'mysql:/var/lib/mysql' 32 | ports: 33 | - "3306:3306" 34 | 35 | sidekiq: 36 | depends_on: 37 | - 'mysql' 38 | - 'redis' 39 | build: . 40 | command: bundle exec sidekiq -C config/sidekiq.yml 41 | volumes: 42 | - '.:/usr/src/app' 43 | 44 | webpack: 45 | build: . 46 | command: bundle exec ./bin/webpack-dev-server 47 | volumes: 48 | - '.:/usr/src/app' 49 | 50 | volumes: 51 | mysql: 52 | redis: -------------------------------------------------------------------------------- /drabkirn-logo-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/drabkirn-logo-120x120.png -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feedka", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/actioncable": "^6.1.5-1", 6 | "@rails/activestorage": "^6.1.5-1", 7 | "@rails/ujs": "^6.1.5-1", 8 | "@rails/webpacker": "5.4.3", 9 | "clipboard": "^2.0.6", 10 | "desityle": "^1.2.0", 11 | "turbolinks": "^5.2.0", 12 | "wowjs": "^1.1.3" 13 | }, 14 | "version": "0.1.0", 15 | "devDependencies": { 16 | "webpack-dev-server": "^3.11.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | User-agent: * 3 | Disallow: /r/ 4 | Disallow: /feeds 5 | Disallow: /admin -------------------------------------------------------------------------------- /spec/controllers/application_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ApplicationController, type: :controller do 4 | 5 | # Additional test for testing sign out path 6 | describe "after sign_out path test" do 7 | before(:each) do 8 | @user = create(:confirmed_user) 9 | sign_in @user 10 | end 11 | 12 | it 'redirects user to root_path' do 13 | expect(controller.instance_eval { after_sign_out_path_for(@user) } ).to eq root_path 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/controllers/home_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe HomeController, type: :controller do 4 | 5 | describe "GET #index" do 6 | it "returns http success" do 7 | get :index 8 | expect(response).to have_http_status(:success) 9 | end 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /spec/controllers/reports_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ReportsController, type: :controller do 4 | let(:user) { create(:confirmed_user) } 5 | let(:report) { create(:report, user: user) } 6 | 7 | ## GET /reports/:id 8 | describe "GET #show" do 9 | context "show /reports/:id" do 10 | before(:each) do 11 | get :show, params: { id: report.id } 12 | end 13 | 14 | it "renders show page" do 15 | expect(response).to render_template(:show) 16 | end 17 | 18 | it "assigns report" do 19 | expect(assigns(:report)).to eq report 20 | end 21 | end 22 | end 23 | 24 | ## GET /r/:username?feed_content=abcdefghijkl 25 | ## GET /r/:admin 26 | describe "GET #report" do 27 | context "show /r/:username" do 28 | context "for feed_content reports" do 29 | before(:each) do 30 | get :report, params: { username: user.username, feed_content: "abcdefghijkl" } 31 | end 32 | 33 | it "renders report page" do 34 | expect(response).to render_template(:report) 35 | end 36 | 37 | it "assigns feed_content from params" do 38 | expect(assigns(:feed_content)).to eq "abcdefghijkl" 39 | end 40 | end 41 | 42 | context "for other reports in name of admin" do 43 | before(:each) do 44 | @user1 = create(:confirmed_user) 45 | @user1.admin = true 46 | @user1.save 47 | @user1.reload 48 | ENV["admin_username"] = @user1.username 49 | get :report, params: { username: @user1.username } 50 | end 51 | 52 | after(:each) do 53 | ENV["admin_username"] = "" 54 | end 55 | 56 | it "renders report page" do 57 | expect(response).to render_template(:report) 58 | end 59 | end 60 | end 61 | 62 | context "cannot /r/:username" do 63 | context "if feed_content param is not present" do 64 | before(:each) do 65 | get :report, params: { username: user.username } 66 | end 67 | 68 | it "renders root_path" do 69 | expect(response).to redirect_to root_path 70 | end 71 | 72 | it "renders alert message" do 73 | expect(flash[:alert]).to eq Message.feed_content_not_found 74 | end 75 | end 76 | 77 | context "if username is not in the database" do 78 | before(:each) do 79 | get :report, params: { username: 'xxxxxx' } 80 | end 81 | 82 | it "redirects to root_path" do 83 | expect(response).to redirect_to root_path 84 | end 85 | 86 | it "shows user not found message" do 87 | expect(flash[:alert]).to eq Message.user_not_found 88 | end 89 | end 90 | 91 | context "if user has not confirmed the email" do 92 | before(:each) do 93 | @user1 = create(:user) 94 | get :report, params: { username: @user1.username } 95 | end 96 | 97 | it "redirects to root_path" do 98 | expect(response).to redirect_to root_path 99 | end 100 | 101 | it "shows user email not confirmed message" do 102 | expect(flash[:alert]).to eq Message.user_email_not_confirmed 103 | end 104 | end 105 | end 106 | end 107 | 108 | ## POST /reports 109 | describe "POST #create" do 110 | context "creates report successfully" do 111 | before(:each) do 112 | @report1 = build(:report, user: user) 113 | @report1.content = "1234567891011" 114 | request.env['HTTP_REFERER'] = "/r/#{@report1.user.username}" 115 | end 116 | 117 | it "create report and adds to the database" do 118 | expect { post :create, params: { report: { content: @report1.content } } }.to change(Report, :count).by(1) 119 | end 120 | 121 | it "redirects to root_path" do 122 | post :create, params: { report: { content: @report1.content } } 123 | expect(response).to redirect_to report_path(Report.last.id) 124 | end 125 | 126 | it "renders successful report created message" do 127 | post :create, params: { report: { content: @report1.content } } 128 | expect(flash[:notice]).to eq Message.report_created 129 | end 130 | end 131 | 132 | context "cannot create report" do 133 | context "if content length is invalid" do 134 | before(:each) do 135 | @report1 = build(:report, user: user) 136 | @report1.content = "1234" 137 | request.env['HTTP_REFERER'] = "/r/#{@report1.user.username}" 138 | post :create, params: { report: { content: @report1.content } } 139 | end 140 | 141 | it "redirects to ref_path" do 142 | expect(response).to redirect_to "/r/#{@report1.user.username}" 143 | end 144 | 145 | it "renders content problem message" do 146 | expect(flash[:alert]).to eq Message.feed_content_not_found 147 | end 148 | end 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /spec/controllers/users/multi_factor_authentication_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Users::MultiFactorAuthenticationController, type: :controller do 4 | let(:user) { create(:confirmed_user) } 5 | 6 | describe "POST #verify_enable" do 7 | context "when successfully enabled" do 8 | before(:each) do 9 | sign_in user 10 | post :verify_enable, params: { id: user.id, multi_factor_authentication: { otp_code_token: user.otp_code } } 11 | end 12 | 13 | it "redirects to edit_user_registration_path" do 14 | expect(response).to redirect_to edit_user_registration_path 15 | end 16 | 17 | it "shows 2fa enabled message" do 18 | expect(flash[:notice]).to eq Message.two_fa_enabled 19 | end 20 | end 21 | 22 | context "when could not be enabled" do 23 | context "when otp_code is empty" do 24 | before(:each) do 25 | sign_in user 26 | post :verify_enable, params: { id: user.id, multi_factor_authentication: { otp_code_token: '' } } 27 | end 28 | 29 | it "redirects to edit_user_registration_path" do 30 | expect(response).to redirect_to edit_user_registration_path 31 | end 32 | 33 | it "shows 2fa not enabled message" do 34 | expect(flash[:alert]).to eq Message.two_fa_not_enabled 35 | end 36 | end 37 | 38 | context "when otp_code is wrong" do 39 | before(:each) do 40 | sign_in user 41 | post :verify_enable, params: { id: user.id, multi_factor_authentication: { otp_code_token: '123456' } } 42 | end 43 | 44 | it "redirects to edit_user_registration_path" do 45 | expect(response).to redirect_to edit_user_registration_path 46 | end 47 | 48 | it "shows 2fa not enabled message" do 49 | expect(flash[:alert]).to eq Message.two_fa_not_enabled 50 | end 51 | end 52 | 53 | context "cannot access/manage 2fa settings" do 54 | it_behaves_like 'when user is not logged in', '/users/verify_enable' 55 | end 56 | end 57 | end 58 | 59 | describe "POST #verify_disabled" do 60 | context "when successfully disabled" do 61 | before(:each) do 62 | sign_in user 63 | post :verify_disable, params: { id: user.id, multi_factor_authentication: { otp_code_token: user.otp_code } } 64 | end 65 | 66 | it "redirects to edit_user_registration_path" do 67 | expect(response).to redirect_to edit_user_registration_path 68 | end 69 | 70 | it "shows 2fa enabled message" do 71 | expect(flash[:notice]).to eq Message.two_fa_disabled 72 | end 73 | end 74 | 75 | context "when could not be disabled" do 76 | context "when otp_code is empty" do 77 | before(:each) do 78 | sign_in user 79 | post :verify_disable, params: { id: user.id, multi_factor_authentication: { otp_code_token: '' } } 80 | end 81 | 82 | it "redirects to edit_user_registration_path" do 83 | expect(response).to redirect_to edit_user_registration_path 84 | end 85 | 86 | it "shows 2fa not disabled message" do 87 | expect(flash[:alert]).to eq Message.two_fa_not_disabled 88 | end 89 | end 90 | 91 | context "when otp_code is wrong" do 92 | before(:each) do 93 | sign_in user 94 | post :verify_disable, params: { id: user.id, multi_factor_authentication: { otp_code_token: '123456' } } 95 | end 96 | 97 | it "redirects to edit_user_registration_path" do 98 | expect(response).to redirect_to edit_user_registration_path 99 | end 100 | 101 | it "shows 2fa not disabled message" do 102 | expect(flash[:alert]).to eq Message.two_fa_not_disabled 103 | end 104 | end 105 | 106 | context "cannot access/manage 2fa settings" do 107 | it_behaves_like 'when user is not logged in', '/users/verify_disable' 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/controllers/users/sessions_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Users::SessionsController, type: :controller do 4 | describe "POST #create" do 5 | context "when user successfully signs in" do 6 | context "with 2FA not enabled" do 7 | before(:each) do 8 | @request.env["devise.mapping"] = Devise.mappings[:user] 9 | @user = create(:confirmed_user) 10 | post :create, params: { user: { email: @user.email, password: "12345678" } } 11 | end 12 | 13 | it "redirects to feeds_path" do 14 | expect(response).to redirect_to feeds_path 15 | end 16 | 17 | it "shows successfully signed in message" do 18 | expect(flash[:notice]).to eq "Signed in successfully." 19 | end 20 | end 21 | 22 | context "with 2FA enabled" do 23 | before(:each) do 24 | @request.env["devise.mapping"] = Devise.mappings[:user] 25 | @user = create(:confirmed_user) 26 | @user.otp_module = "enabled" 27 | @user.save 28 | post :create, params: { user: { email: @user.email, password: "12345678", otp_code_token: @user.otp_code } } 29 | end 30 | 31 | it "redirects to feeds_path" do 32 | expect(response).to redirect_to feeds_path 33 | end 34 | 35 | it "shows successfully signed in message" do 36 | expect(flash[:notice]).to eq "Signed in successfully." 37 | end 38 | end 39 | end 40 | 41 | context "when user is not signed in successfully" do 42 | context "when email is wrong" do 43 | before(:each) do 44 | @request.env["devise.mapping"] = Devise.mappings[:user] 45 | @user = create(:confirmed_user) 46 | post :create, params: { user: { email: 'a@b.com', password: "12345678" } } 47 | end 48 | 49 | it "renders the new template with errors" do 50 | expect(response).to render_template(:new) 51 | end 52 | 53 | it "shows invalid credentials in message" do 54 | expect(flash[:alert]).to eq "Invalid Email or password." 55 | end 56 | end 57 | 58 | context "when password is wrong" do 59 | before(:each) do 60 | @request.env["devise.mapping"] = Devise.mappings[:user] 61 | @user = create(:confirmed_user) 62 | post :create, params: { user: { email: @user.email, password: "12345678910" } } 63 | end 64 | 65 | it "renders the new template with errors" do 66 | expect(response).to render_template(:new) 67 | end 68 | 69 | it "shows invalid credentials message" do 70 | expect(flash[:alert]).to eq "Invalid Email or password." 71 | end 72 | end 73 | 74 | context "when OTP Module is enabled with no token" do 75 | before(:each) do 76 | @request.env["devise.mapping"] = Devise.mappings[:user] 77 | @user = create(:confirmed_user) 78 | @user.otp_module = "enabled" 79 | @user.save 80 | post :create, params: { user: { email: @user.email, password: "12345678", otp_code_token: "" } } 81 | end 82 | 83 | it "redirects to root_path" do 84 | expect(response).to redirect_to root_path 85 | end 86 | 87 | it "shows invalid 2FA code message" do 88 | expect(flash[:alert]).to eq Message.two_fa_empty 89 | end 90 | end 91 | 92 | context "when OTP Module is enabled with no token" do 93 | before(:each) do 94 | @request.env["devise.mapping"] = Devise.mappings[:user] 95 | @user = create(:confirmed_user) 96 | @user.otp_module = "enabled" 97 | @user.save 98 | post :create, params: { user: { email: @user.email, password: "12345678", otp_code_token: '1234' } } 99 | end 100 | 101 | it "redirects to root_path" do 102 | expect(response).to redirect_to root_path 103 | end 104 | 105 | it "shows invalid 2FA code message" do 106 | expect(flash[:alert]).to eq Message.two_fa_wrong 107 | end 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/factories/feeds.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :feed do 3 | association :user, factory: :confirmed_user 4 | 5 | content { Faker::Lorem.paragraph_by_chars(number: 256) } 6 | public { false } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/reports.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :report do 3 | association :user, factory: :confirmed_user 4 | 5 | content { Faker::Lorem.paragraph_by_chars(number: 256) } 6 | status { 0 } 7 | message { "" } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | email { Faker::Internet.email } 4 | username { Faker::Internet.username(specifier: 3..10, separators: %w(_)) } 5 | 6 | password { "12345678" } 7 | password_confirmation { "12345678" } 8 | admin { false } 9 | 10 | factory :confirmed_user do 11 | after(:create) do |user| 12 | user.confirm 13 | end 14 | end 15 | 16 | factory :admin_user do 17 | before(:create) do |user| 18 | user.admin = true 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/jobs/feed_save_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe FeedSaveJob, type: :job do 4 | include ActiveJob::TestHelper 5 | 6 | describe "Perform Job" do 7 | context "#perform later" do 8 | before(:each) do 9 | ActiveJob::Base.queue_adapter = :test 10 | @feed = build(:feed, user: create(:confirmed_user)) 11 | @feed.content = Encryption.encrypt_data(@feed.content) 12 | end 13 | 14 | it 'queues the job' do 15 | expect { FeedSaveJob.set(wait: 10.minutes).perform_later(@feed.user.id, @feed.content) } 16 | .to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1) 17 | end 18 | 19 | it 'is in default queue' do 20 | expect(FeedSaveJob.new.queue_name).to eq('default') 21 | end 22 | 23 | it "reponds to #perform" do 24 | expect(FeedSaveJob.new).to respond_to(:perform) 25 | end 26 | 27 | it "have_been_enqueued" do 28 | FeedSaveJob.set(wait: 10.minutes).perform_later(@feed.user.id, @feed.content) 29 | expect(FeedSaveJob).to have_been_enqueued.with(@feed.user.id, @feed.content) 30 | end 31 | 32 | it "saves the feed to the DB" do 33 | expect { FeedSaveJob.perform_now(@feed.user.id, @feed.content) }.to change(Feed, :count).by(1) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/models/feed_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Feed, type: :model do 4 | let(:feed) { create(:feed) } 5 | let(:user) { create(:user) } 6 | 7 | it "is valid with default valid attributes" do 8 | expect(feed).to be_valid 9 | end 10 | 11 | it { should belong_to(:user) } 12 | 13 | describe "content validation" do 14 | it { should validate_presence_of(:content) } 15 | 16 | it "should respond to content" do 17 | expect(feed).to respond_to(:content) 18 | end 19 | 20 | it "is invalid with a blank or no content" do 21 | feed.content = " " 22 | feed.valid? 23 | expect(feed.errors[:content]).to include("can't be blank") 24 | end 25 | 26 | it "is invalid if length is < 10" do 27 | feed.content = "ab" 28 | feed.valid? 29 | expect(feed.errors[:content]).to include("is too short (minimum is 10 characters)") 30 | end 31 | 32 | it "is invalid if length is > 500" do 33 | feed.content = Faker::Lorem.paragraph_by_chars(number: 501) 34 | feed.valid? 35 | expect(feed.errors[:content]).to include("is too long (maximum is 500 characters)") 36 | end 37 | 38 | it "encrypts the content after creating" do 39 | @feed1 = build(:feed, user: create(:confirmed_user)) 40 | local_feed1_content = @feed1.content 41 | @feed1.save 42 | expect(@feed1.content).to_not eq local_feed1_content 43 | end 44 | 45 | it "encrypts the content after updating" do 46 | @feed1 = create(:feed, user: create(:confirmed_user)) 47 | local_feed1_content = Encryption.decrypt_data(@feed1.content) 48 | @feed1.content = "This is a new content over here" 49 | @feed1.save 50 | expect(@feed1.content).to_not eq local_feed1_content 51 | end 52 | end 53 | 54 | describe "Invalid" do 55 | context "is invalid if user associated is not confirmed" do 56 | before(:each) do 57 | @feed1 = build(:feed, user: user) 58 | end 59 | 60 | it "is invalid" do 61 | expect(@feed1).to_not be_valid 62 | end 63 | 64 | it "shows invalid message" do 65 | @feed1.valid? 66 | expect(@feed1.errors[:user][0]).to include Message.user_email_not_confirmed 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/models/report_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Report, type: :model do 4 | let(:report) { create(:report) } 5 | 6 | it { should belong_to(:user) } 7 | 8 | it "is valid with default valid attributes" do 9 | expect(report).to be_valid 10 | end 11 | 12 | describe "content validation" do 13 | it { should validate_presence_of(:content) } 14 | 15 | it "should respond to content" do 16 | expect(report).to respond_to(:content) 17 | end 18 | 19 | it "is invalid with a blank or no content" do 20 | report.content = " " 21 | report.valid? 22 | expect(report.errors[:content]).to include("can't be blank") 23 | end 24 | 25 | it "is invalid if length is < 10" do 26 | report.content = "ab" 27 | report.valid? 28 | expect(report.errors[:content]).to include("is too short (minimum is 10 characters)") 29 | end 30 | 31 | it "is invalid if length is > 500" do 32 | report.content = Faker::Lorem.paragraph_by_chars(number: 501) 33 | report.valid? 34 | expect(report.errors[:content]).to include("is too long (maximum is 500 characters)") 35 | end 36 | 37 | it "encrypts the content after creating" do 38 | @report1 = build(:report, user: create(:confirmed_user)) 39 | local_report1_content = @report1.content 40 | @report1.save 41 | expect(@report1.content).to_not eq local_report1_content 42 | end 43 | 44 | it "encrypts the content after updating" do 45 | @report1 = create(:report, user: create(:confirmed_user)) 46 | local_report1_content = Encryption.decrypt_data(@report1.content) 47 | @report1.content = "This is a new content over here" 48 | @report1.save 49 | expect(@report1.content).to_not eq local_report1_content 50 | end 51 | end 52 | 53 | describe "message validation" do 54 | it "should respond to message" do 55 | expect(report).to respond_to(:message) 56 | end 57 | 58 | it "is invalid if length is > 500" do 59 | report.message = Faker::Lorem.paragraph_by_chars(number: 501) 60 | report.valid? 61 | expect(report.errors[:message]).to include("is too long (maximum is 500 characters)") 62 | end 63 | end 64 | 65 | describe "status validation" do 66 | it { should validate_presence_of(:status) } 67 | 68 | it { should define_enum_for(:status).with_values(pending: 0, rejected: 1, accepted: 2).with_prefix(true) } 69 | 70 | it "should respond to status" do 71 | expect(report).to respond_to(:status) 72 | end 73 | 74 | it "is invalid with a blank or no status" do 75 | report.status = " " 76 | report.valid? 77 | expect(report.errors[:status]).to include("can't be blank") 78 | end 79 | 80 | it "changes the states from pending to rejected" do 81 | report.status = 1 82 | report.save 83 | report.reload 84 | expect(report.status_rejected?).to eq true 85 | end 86 | 87 | it "changes the states from pending to accepted" do 88 | report.status = 2 89 | report.save 90 | report.reload 91 | expect(report.status_accepted?).to eq true 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | let(:user) { create(:user) } 5 | 6 | it "is valid with default valid attributes" do 7 | expect(user).to be_valid 8 | end 9 | 10 | it { should have_many(:feeds) } 11 | it { should have_many(:reports) } 12 | 13 | describe "email validations" do 14 | it { should validate_presence_of(:email) } 15 | 16 | it "should respond to email" do 17 | expect(user).to respond_to(:email) 18 | end 19 | 20 | it "is invalid with a blank or no email" do 21 | user.email = " " 22 | user.valid? 23 | expect(user.errors[:email]).to include("can't be blank") 24 | end 25 | 26 | it "is invalid without a proper email" do 27 | user.email = "a" 28 | user.valid? 29 | expect(user.errors[:email]).to include("is invalid") 30 | end 31 | 32 | it "is invalid with a duplicate email" do 33 | user.save 34 | @otheruser = FactoryBot.build(:user, email: user.email) 35 | @otheruser.valid? 36 | expect(@otheruser.errors[:email]).to include("has already been taken") 37 | end 38 | end 39 | 40 | describe "password validations" do 41 | it { should validate_presence_of(:password) } 42 | 43 | it "should respond to password" do 44 | expect(user).to respond_to(:password) 45 | end 46 | 47 | it "should respond to password_confirmation" do 48 | expect(user).to respond_to(:password_confirmation) 49 | end 50 | 51 | it "is invalid without same password for password_confirmation" do 52 | user.password_confirmation = "xxxxx" 53 | user.valid? 54 | expect(user.errors[:password_confirmation]).to include("doesn't match Password") 55 | end 56 | 57 | it "is invalid with password length < 8" do 58 | user.password = "123456" 59 | user.password_confirmation = "123456" 60 | user.valid? 61 | expect(user.errors[:password][0]).to include("is too short") 62 | end 63 | 64 | it "is invalid with password length > 80" do 65 | user.password = Faker::Lorem.characters(number: 81) 66 | user.password_confirmation = user.password 67 | user.valid? 68 | expect(user.errors[:password][0]).to include("is too long") 69 | end 70 | 71 | it "should have encrypted password + not eq to password" do 72 | expect(user.password).not_to eq user.encrypted_password 73 | end 74 | end 75 | 76 | describe "username validations" do 77 | it { should validate_presence_of(:username) } 78 | 79 | it { should validate_uniqueness_of(:username) } 80 | 81 | it "should respond to username" do 82 | expect(user).to respond_to(:username) 83 | end 84 | 85 | it "is invalid with a blank username" do 86 | user.username = " " 87 | user.valid? 88 | expect(user.errors[:username]).to include("can't be blank") 89 | end 90 | 91 | it "is invalid with no username" do 92 | user.username = nil 93 | user.valid? 94 | expect(user.errors[:username]).to include("can't be blank") 95 | end 96 | 97 | it "is invalid with a duplicate username" do 98 | user.save 99 | @otheruser = FactoryBot.build(:user, username: user.username) 100 | @otheruser.valid? 101 | expect(@otheruser.errors[:username]).to include("has already been taken") 102 | end 103 | 104 | it "is invalid if length is < 3" do 105 | user.username = "ab" 106 | user.valid? 107 | expect(user.errors[:username]).to include("is too short (minimum is 3 characters)") 108 | end 109 | 110 | it "is invalid if length is > 10" do 111 | user.username = "abcdefghijk" 112 | user.valid? 113 | expect(user.errors[:username]).to include("is too long (maximum is 10 characters)") 114 | end 115 | 116 | it "downcases username" do 117 | user.username = "ABCdeF" 118 | user.save 119 | expect(user.username).to eq "abcdef" 120 | end 121 | 122 | it "is invalid for bad username" do 123 | user.username = "ABC_E." 124 | user.valid? 125 | expect(user.errors[:username]).to include("is invalid") 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | =begin 50 | # This allows you to limit a spec run to individual examples or groups 51 | # you care about by tagging them with `:focus` metadata. When nothing 52 | # is tagged with `:focus`, all examples get run. RSpec also provides 53 | # aliases for `it`, `describe`, and `context` that include `:focus` 54 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 55 | config.filter_run_when_matching :focus 56 | 57 | # Allows RSpec to persist some state between runs in order to support 58 | # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # you configure your source control system to ignore this file. 60 | config.example_status_persistence_file_path = "spec/examples.txt" 61 | 62 | # Limits the available syntax to the non-monkey patched syntax that is 63 | # recommended. For more details, see: 64 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 65 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 67 | config.disable_monkey_patching! 68 | 69 | # Many RSpec users commonly either run the entire suite or an individual 70 | # file, and it's useful to allow more verbose output when running an 71 | # individual spec file. 72 | if config.files_to_run.one? 73 | # Use the documentation formatter for detailed output, 74 | # unless a formatter has already been configured 75 | # (e.g. via a command-line flag). 76 | config.default_formatter = "doc" 77 | end 78 | 79 | # Print the 10 slowest examples and example groups at the 80 | # end of the spec run, to help surface which specs are running 81 | # particularly slow. 82 | config.profile_examples = 10 83 | 84 | # Run specs in random order to surface order dependencies. If you find an 85 | # order dependency and want to debug it, you can fix the order by providing 86 | # the seed, which is printed after each run. 87 | # --seed 1234 88 | config.order = :random 89 | 90 | # Seed global randomization in this process using the `--seed` CLI option. 91 | # Setting this allows you to use `--seed` to deterministically reproduce 92 | # test failures related to randomization by passing the same `--seed` value 93 | # as the one that triggered the failure. 94 | Kernel.srand config.seed 95 | =end 96 | end 97 | -------------------------------------------------------------------------------- /spec/support/shared_examples_spec_helper.rb: -------------------------------------------------------------------------------- 1 | shared_examples 'when user is not logged in' do |action| 2 | before(:each) do 3 | if action == '/feeds' 4 | get :index 5 | elsif action == '/feed/:id/delete' 6 | @feed1 = create(:feed, user: user) 7 | delete :destroy, params: { id: @feed1.id } 8 | elsif action == '/feed/:id/public' 9 | @feed1 = create(:feed, user: user) 10 | patch :public, params: { id: @feed1.id } 11 | elsif action == '/feed/:id/private' 12 | @feed1 = create(:feed, user: user, public: true) 13 | patch :private, params: { id: @feed1.id } 14 | elsif action == '/users/verify_enable' 15 | @user1 = create(:confirmed_user) 16 | post :verify_enable, params: { id: @user1.id, multi_factor_authentication: { otp_code_token: @user1.otp_code } } 17 | elsif 18 | @user1 = create(:confirmed_user) 19 | post :verify_disable, params: { id: @user1.id, multi_factor_authentication: { otp_code_token: @user1.otp_code } } 20 | end 21 | end 22 | 23 | it "redirects to new_user_session_path" do 24 | expect(response).to redirect_to new_user_session_path 25 | end 26 | 27 | it "shows login message" do 28 | expect(flash[:alert]).to eq "You need to sign in or sign up before continuing." 29 | end 30 | end -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/storage/.keep -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabkirn/feedka/33ded938317dd3ba25bc6f79936c5eb9b0254624/vendor/.keep --------------------------------------------------------------------------------