# Technical Documentation: TheOdinProject/theodinproject

> ℹ️ **Provenance:** In-Tree Documentation (36 Chapters) · [CodeWiki Reference](https://codewiki.google/github.com/TheOdinProject/theodinproject) · Recency: Active (< 180 days)

## File: README.md

# The Odin Project

[The Odin Project](https://www.theodinproject.com/) (TOP) is an open-source curriculum for learning full-stack web development. Our mission is to provide a comprehensive curriculum to learn web development for free. We help users learn the skills and build the impressive portfolio of projects they need to get hired as a web developer.

Some of the topics we cover include:

-   Ruby
-   Ruby on Rails
-   SQL
-   HTML and CSS
-   Javascript
-   Node
-   React
-   How to go about getting a job in the industry

This repo contains the TOP app which pulls in lesson content as well as front-end and back-end code. To see the actual lesson content that gets pulled in, please go to the [TOP curriculum repo](https://github.com/TheOdinProject/curriculum).

Our community can be found on the [TOP Discord server](https://discord.gg/fbFCkYabZB).

## Contributing



The Odin Project depends on open-source contributions to improve, grow, and thrive. We welcome contributors of all experience levels and backgrounds to help maintain this awesome curriculum and community. If you would like to contribute to our curriculum, be sure to thoroughly read our [contributing guide](https://github.com/TheOdinProject/theodinproject/blob/main/CONTRIBUTING.md).

---

## File: AGENTS.md

# AGENTS.md

## Project Overview

The Odin Project is a Rails 8 application that provides a free, open-source coding curriculum. Users follow learning paths composed of courses and lessons, submit projects, and track their progress.

## References

- **Git commit & PR format:** See [.github/PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md). Commit titles must follow `keyword: brief description` using one of: `Feature`, `Chore`, or `Fix`.

## Tech Stack

- **Backend:** Rails 8.0, Ruby 3.4, PostgreSQL
- **Frontend:** Tailwind CSS 4, Stimulus, Turbo Rails, esbuild
- **Components:** ViewComponent (app/components/)
- **Background Jobs:** Sidekiq with sidekiq-cron
- **Auth:** Devise with OmniAuth (GitHub, Google) and two-factor support
- **Testing:** RSpec, Capybara with Cuprite, FactoryBot, WebMock/VCR
- **Error Tracking:** Sentry
- **Feature Flags:** Flipper

## Common Commands

### Development

```bash
bin/setup          # Initial setup (bundle, yarn, db:prepare, etc.)
bin/dev            # Start dev server (Puma + Sidekiq + CSS/JS watchers)
```

### Testing

```bash
bin/test                              # Full suite: all linters + RSpec
bin/rspec                             # RSpec only (parallel execution)
bin/rspec spec/models/user_spec.rb    # Single file
bin/rspec spec/models/user_spec.rb:42 # Single example by line number
```

### Linting

```bash
bin/rubocop              # Ruby linting
bin/erb_lint --lint-all  # ERB template linting
yarn lint                # JavaScript (Standard.js)
yarn stylelint           # CSS/SCSS
```

### Assets

```bash
yarn build       # JavaScript bundle (esbuild)
yarn build:css   # Tailwind CSS build
```

## Architecture

### Key Patterns

- **Routes** are split into modular files under `config/routes/` (admin.rb, redirects.rb) and drawn from the main `config/routes.rb` via `draw(:admin)`.
- **ViewComponent** is used extensively for UI (40+ components in `app/components/`). In development, Lookbook is mounted at `/lookbook` for component previews.
- **Service objects** in `app/services/` encapsulate business logic.
- **Policies** in `app/policies/` handle authorization.
- **Adaptors** in `app/adaptors/` wrap external service integrations.
- **Builders** in `app/builders/` handle complex object creation.

### Core Domain Models

- **Path** > **Course** > **Lesson** - the curriculum hierarchy
- **User** - learners with Devise authentication
- **LessonCompletion** - tracks user progress through lessons
- **ProjectSubmission** - student project submissions (likeable, flaggable, soft-deletable via Discard)
- **Flag** - content moderation on submissions
- **AdminUser** - separate admin accounts (devise_invitable)

### Testing Conventions

- Tests use `rails_helper` (no separate `spec_helper`)
- FactoryBot factories in `spec/factories/`
- System tests use Capybara + Cuprite (headless Chrome)
- Shared examples in `spec/support/shared_examples/`
- Test order is randomized; persistence file at `spec/examples.txt`
- CI splits tests across 4 parallel containers via `bin/ci_spec`

### Code Style

- JavaScript follows Standard.js
- CSS uses Tailwind with stylelint

---

## File: app.json

{
  "environments": {
    "review": {
      "formation": [
        { "process": "web", "quantity": 1 },
        { "process": "worker", "quantity": 1 }
      ],
      "buildpacks": [
        { "url": "https://github.com/gaffneyc/heroku-buildpack-jemalloc.git" },
        { "url": "heroku/nodejs" },
        { "url": "heroku/ruby" },
        { "url": "https://github.com/devforce/heroku-buildpack-cleanup" }
      ],
      "addons": [
        "heroku-postgresql:mini"
      ],
      "env": {
        "STAGING": {
          "description": "This is a staging environment",
          "value": "true"
        }
      },
      "scripts": {
        "postdeploy": "bundle exec rake heroku:postdeploy"
      }
    }
  }
}

---

## File: CLAUDE.md

@AGENTS.md

---

## File: CONTRIBUTING.md

# The Odin Project Web App Contributing Guide

Thank you for expressing interest in contributing to The Odin Project (TOP) web app! Before continuing through this guide, be sure you've read our [general contributing guide](https://github.com/TheOdinProject/.github/blob/main/CONTRIBUTING.md), as it contains information that is important for all of our repos.

This contributing guide assumes you have followed the instructions in our general contributing guide to fork and clone our web app repo.

## Table of Contents

- [How to Contribute](#how-to-contribute)
  - [Running The Odin Project Locally](#running-the-odin-project-locally)
  - [Adding Curriculum Content](#adding-curriculum-content)
  - [Testing Changes Locally](#testing-changes-locally)

## How to Contribute

Due to the complexity of our web app repo compared to some of our other repos, contributing requires you to be able to run our web app locally and to run tests to ensure any changes being made look and work as expected.

### Running The Odin Project Locally

Follow our [instructions on running The Odin Project locally](https://github.com/TheOdinProject/theodinproject/wiki/Running-The-Odin-Project-Locally). These instructions include all installations you will need as well as optional instructions for setting up authentication.

### Adding Curriculum Content

When new content is ready to be or has already been merged in the TOP curriculum repo, the web app must be updated to render that new content. The following instructions cover how to add new curriculum content to the web app, but you can also refer to them when editing existing content:

- [Adding a lesson](https://github.com/TheOdinProject/theodinproject/wiki/Adding-a-Lesson)
- [Adding a section](https://github.com/TheOdinProject/theodinproject/wiki/Adding-a-Section)
- [Adding a course](https://github.com/TheOdinProject/theodinproject/wiki/Adding-a-Course)
- [Adding a path](https://github.com/TheOdinProject/theodinproject/wiki/Adding-a-Path)

### Testing Changes Locally

Before submitting a pull request (PR) to our web app repo, you should run the following commands locally to test that any changes made don't cause any failures. This can be quicker than waiting for your PR to finish running its checks to see whether there are any failures, and it can also help prevent you from making an unnecessary amount of commits that all fail to resolve a test failure.

```bash
bin/ci
```

---

## File: license.txt

MIT License

Copyright (c) 2020 The Odin Project

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

## File: package.json

{
  "license": "MIT",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "lint": "standard",
    "stylelint": "stylelint 'app/assets/stylesheets'",
    "build": "node esbuild.config.mjs",
    "build:css": "npx @tailwindcss/cli -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css"
  },
  "dependencies": {
    "@hotwired/stimulus": "^3.2.2",
    "@hotwired/turbo-rails": "^8.0.23",
    "@rails/request.js": "^0.0.13",
    "@sentry/browser": "^10.75.0",
    "@sentry/esbuild-plugin": "^5.3.0",
    "@tailwindcss/cli": "^4.3.3",
    "@tailwindcss/forms": "^0.5.11",
    "@tailwindcss/typography": "^0.5.20",
    "chart.js": "^4.5.1",
    "chartjs-plugin-crosshair": "^2.0.0",
    "chokidar": "^5.0.0",
    "debounce": "^3.0.0",
    "el-transition": "^0.0.7",
    "esbuild": "^0.28.2",
    "esbuild-plugin-prismjs": "^1.0.8",
    "esbuild-rails": "^1.0.8",
    "flatpickr": "^4.6.13",
    "mermaid": "^12.0.0",
    "prismjs": "^1.30.0",
    "sortablejs": "^1.15.7",
    "stimulus-use": "^0.53.1",
    "tailwindcss": "4.3.3",
    "tippy.js": "^6.3.7"
  },
  "devDependencies": {
    "standard": "^17.1.2",
    "stylelint": "^17.15.0",
    "stylelint-config-standard": "^40.0.0"
  },
  "packageManager": "yarn@4.4.0"
}

---

## File: .better-html.yml

---
allow_single_quoted_attributes: false
allow_unquoted_attributes: false

---

## File: .erb_lint.yml

---
glob: "**/app/**/*.{html,turbo_stream,js}{+*,}.erb"
exclude:
  - '*vendor/*'
  - '*app/views/svg/*'
linters:
  AllowedScriptType:
    enabled: true
  ClosingErbTagIndent:
    enabled: true
  DeprecatedClasses: # See https://github.com/Shopify/erb-lint#deprecatedclasses
    enabled: true
  ErbSafety: # Need to fix the remaining issues before this can be enabled
    enabled: false
    better_html_config: .better-html.yml
  ExtraNewline:
    enabled: true
  FinalNewline:
    enabled: true
  HardCodedString:
    enabled: false
  NoJavascriptTagHelper:
    enabled: true
  ParserErrors:
    enabled: true
  RightTrim: # Disable closing tag trim
    enabled: false
  RequireInputAutocomplete:
    enabled: false
  RubocopText:
    enabled: true
  Rubocop:
    enabled: true
    rubocop_config:
      inherit_from:
        - .rubocop.yml
      Layout/InitialIndentation:
        Enabled: false
      Layout/TrailingEmptyLines:
        Enabled: false
      Layout/TrailingWhitespace:
        Enabled: false
      Naming/FileName:
        Enabled: false
      Style/FrozenStringLiteralComment:
        Enabled: false
      Layout/LineLength: # This should be enabled eventually
        Enabled: false
      Lint/UselessAssignment:
        Enabled: false
      Rails/OutputSafety:
        Enabled: false
      Rails/Presence:
        Enabled: false
      Layout/LeadingEmptyLines:
        Enabled: false
      Style/RedundantCondition:
        Enabled: false
      Layout/HashAlignment:
        Enabled: false
      Rails/LinkToBlank:
        Enabled: false
      Layout/SpaceInsideHashLiteralBraces:
        Enabled: false
      Style/StringLiteralsInInterpolation:
        Enabled: false
      Layout/ArgumentAlignment:
        Enabled: false
      Style/SafeNavigation:
        Enabled: false
      Style/TrailingCommaInHashLiteral:
        Enabled: false
      Style/RedundantParentheses:
        Enabled: false
      Lint/FloatComparison:
        Enabled: false
      Style/TrailingCommaInArguments:
        Enabled: false
      Layout/EndAlignment:
        Enabled: false
      Rails/DurationArithmetic:
        Enabled: false
      Style/HashConversion:
        Enabled: false
      Layout/ElseAlignment:
        Enabled: false
      Layout/IndentationWidth:
        Enabled: false
      Style/TrailingCommaInArrayLiteral:
        Enabled: false
      Style/IfWithBooleanLiteralBranches:
        Enabled: false

  SelfClosingTag:
    enabled: false
  SpaceAroundErbTag:
    enabled: true
  SpaceInHtmlTag:
    enabled: true
  SpaceIndentation:
    enabled: true
  TrailingWhitespace:
    enabled: true

---

## File: .rubocop.yml

plugins:
- rubocop-performance
- rubocop-rails
- rubocop-rspec
- rubocop-rspec_rails
- rubocop-capybara
- rubocop-factory_bot

AllCops:
  TargetRubyVersion: 3.4
  TargetRailsVersion: 8.1
  ExtraDetails: true
  DisplayStyleGuide: true
  Include:
  - Gemfile
  - Rakefile
  - config.ru
  - lib/**/*.rake
  - '**/*.rb'
  Exclude:
  - 'vendor/**/*'
  - 'bin/*'
  - 'node_modules/**/*'
  - db/schema.rb
  NewCops: enable

Layout/LineLength:
  Max: 120
  Exclude:
  - 'db/**/*'

Style/Documentation:
  Enabled: false

Style/GuardClause:
  Enabled: false

Style/IfUnlessModifier:
  Enabled: false

Style/FrozenStringLiteralComment:
  Enabled: false

Style/TrailingCommaInArguments:
  Enabled: false

Style/TrailingCommaInHashLiteral:
  Enabled: false

Style/SafeNavigation:
  Enabled: false

Style/ClassAndModuleChildren:
  Enabled: false

Layout/EmptyLinesAroundAttributeAccessor:
  Enabled: true

Layout/SpaceAroundMethodCallOperator:
  Enabled: true

Layout/MultilineMethodCallIndentation:
  Enabled: true
  EnforcedStyle: indented

Lint/AmbiguousBlockAssociation:
  Enabled: false

Lint/BinaryOperatorWithIdenticalOperands:
  Enabled: true

Lint/DeprecatedOpenSSLConstant:
  Enabled: true

Lint/DuplicateElsifCondition:
  Enabled: true

Lint/DuplicateRescueException:
  Enabled: true

Lint/EmptyConditionalBody:
  Enabled: true

Lint/FloatComparison:
  Enabled: true

Lint/MissingSuper:
  Exclude:
  - 'app/components/**/*'
  Enabled: true

Lint/MixedRegexpCaptureTypes:
  Enabled: true

Lint/OutOfRangeRegexpRef:
  Enabled: true

Lint/RaiseException:
  Enabled: true

Lint/SelfAssignment:
  Enabled: true

Lint/ShadowingOuterLocalVariable:
  Exclude:
  - 'db/**/*'

Lint/StructNewOverride:
  Enabled: true

Lint/TopLevelReturnWithArgument:
  Enabled: true

Lint/UnreachableLoop:
  Enabled: true

Lint/UselessAssignment:
  Exclude:
  - 'spec/**/*'

Style/AccessorGrouping:
  Enabled: true

Style/ArrayCoercion:
  Enabled: true

Style/BisectedAttrAccessor:
  Enabled: true

Style/CaseLikeIf:
  Enabled: true

Style/ExplicitBlockArgument:
  Enabled: true

Style/ExponentialNotation:
  Enabled: true

Style/GlobalStdStream:
  Enabled: false

Style/HashAsLastArrayItem:
  Enabled: false

Style/HashEachMethods:
  Enabled: true

Style/HashLikeCase:
  Enabled: true

Style/HashTransformKeys:
  Enabled: true

Style/HashTransformValues:
  Enabled: true

Style/OptionalBooleanParameter:
  Enabled: true

Style/RedundantAssignment:
  Enabled: true

Style/RedundantFetchBlock:
  Enabled: false

Style/RedundantFileExtensionInRequire:
  Enabled: true

Style/RedundantRegexpCharacterClass:
  Enabled: true

Style/RedundantRegexpEscape:
  Enabled: true

Style/SingleArgumentDig:
  Enabled: true

Style/SlicingWithRange:
  Enabled: false

Style/StringConcatenation:
  Enabled: true

Style/FetchEnvVar:
  Enabled: false

Performance/AncestorsInclude:
  Enabled: true

Performance/BigDecimalWithNumericArgument:
  Enabled: true

Performance/RedundantSortBlock:
  Enabled: true

Performance/RedundantStringChars:
  Enabled: true

Performance/ReverseFirst:
  Enabled: true

Performance/SortReverse:
  Enabled: true

Performance/Squeeze:
  Enabled: true

Performance/StringInclude:
  Enabled: true

Style/FormatStringToken:
  Enabled: false

Rails/EnvironmentVariableAccess:
  Enabled: false

Rails/I18nLocaleTexts:
  Enabled: false

Metrics/BlockLength:
  Exclude:
  - 'config/environments/*'
  - 'config/routes.rb'
  - 'spec/**/*'
  Enabled: true

Metrics/MethodLength:
  Exclude:
  - 'db/**/*'

RSpec/ExampleLength:
  Enabled: false

RSpec/ExpectChange:
  EnforcedStyle: block

RSpec/MultipleExpectations:
  Max: 5
  Exclude:
  - 'spec/system/**/*'

RSpec/DescribeClass:
  Exclude:
  - 'spec/requests/**/*'
  - 'spec/system/**/*'
  - 'spec/tasks/**/*'
  - 'spec/seeds/**/*'

---

## File: .stylelintrc.json

{
  "extends": ["stylelint-config-standard"],
  "rules": {
    "selector-class-pattern": null,
    "import-notation": "string",
    "nesting-selector-no-missing-scoping-root": null,
    "no-invalid-position-declaration": null,
    "at-rule-no-unknown": [
      true,
      {
        "ignoreAtRules": [
          "utility",
          "theme",
          "config",
          "source",
          "custom-variant",
          "plugin"
        ]
      }
    ],
    "at-rule-no-deprecated": [
      true,
      {
        "ignoreAtRules": ["apply"]
      }
    ],
    "at-rule-prelude-no-invalid": [
      true,
      {
        "ignoreAtRules": ["apply"]
      }
    ]
  }
}

---

## File: .yarnrc.yml

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.4.0.cjs

---

## File: config/cable.yml

development:
  adapter: redis
  url: redis://localhost:6379/1

test:
  adapter: test

production:
  adapter: redis
  url: <%= ENV.fetch('REDIS_URL') { 'redis://localhost:6379/1' } %>
  channel_prefix: theodinproject_production

---

## File: config/database.yml

# You may need to provide a username, in which case you'd use
# your computer's user account (on a mac type $ echo $USER to
# get it).

default: &default
  adapter: postgresql
  username: <%= ENV['POSTGRES_USERNAME'] %>
  password: <%= ENV['POSTGRES_PASSWORD'] %>
  host: <%= ENV['POSTGRES_HOST'] || 'localhost' %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development: &development
  <<: *default
  database: theodinproject_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
# DO NOT PUT A REAL USERNAME AND PASSWORD IN THIS FILE
test: &test
  <<: *default
  database: theodinproject_test<%= ENV['TEST_ENV_NUMBER'] %>

production:
  adapter: postgresql
  encoding: unicode
  database: db/production
  pool: 10
  username:
  password:

cucumber:
  <<: *test

---

## File: config/newrelic.yml

#
# This file configures the New Relic Agent.  New Relic monitors
# Ruby, Java, .NET, PHP, and Python applications with deep visibility and low overhead.
# For more information, visit www.newrelic.com.
#
# Generated May 18, 2013
#
# This configuration file is custom generated for app15704677@heroku.com


# Here are the settings that are common to all environments
common: &default_settings
  # ============================== LICENSE KEY ===============================

  # You must specify the license key associated with your New Relic
  # account.  This key binds your Agent's data to your account in the
  # New Relic service.
  license_key: '432d83ba49a66440624279a08c53817533d79a8d'

  # Agent Enabled (Ruby/Rails Only)
  # Use this setting to force the agent to run or not run.
  # Default is 'auto' which means the agent will install and run only
  # if a valid dispatcher such as Mongrel is running.  This prevents
  # it from running with Rake or the console.  Set to false to
  # completely turn the agent off regardless of the other settings.
  # Valid values are true, false and auto.
  #
  # agent_enabled: auto

  # Application Name Set this to be the name of your application as
  # you'd like it show up in New Relic. The service will then auto-map
  # instances of your application into an "application" on your
  # dashboard page. If you want to map this instance into multiple
  # apps, like "AJAX Requests" and "All UI" then specify a semicolon
  # separated list of up to three distinct names, or a yaml list.
  # Defaults to the capitalized RAILS_ENV or RACK_ENV (i.e.,
  # Production, Staging, etc)
  #
  # Example:
  #
  #   app_name:
  #       - Ajax Service
  #       - All Services
  #
  app_name: My Application

  # When "true", the agent collects performance data about your
  # application and reports this data to the New Relic service at
  # newrelic.com. This global switch is normally overridden for each
  # environment below. (formerly called 'enabled')
  monitor_mode: true

  # Developer mode should be off in every environment but
  # development as it has very high overhead in memory.
  developer_mode: false

  # The newrelic agent generates its own log file to keep its logging
  # information separate from that of your application. Specify its
  # log level here.
  log_level: info

  # Optionally set the path to the log file This is expanded from the
  # root directory (may be relative or absolute, e.g. 'log/' or
  # '/var/log/') The agent will attempt to create this directory if it
  # does not exist.
  # log_file_path: 'log'

  # Optionally set the name of the log file, defaults to 'newrelic_agent.log'
  # log_file_name: 'newrelic_agent.log'

  # The newrelic agent communicates with the service via https by default.  This
  # prevents eavesdropping on the performance metrics transmitted by the agent.
  # The encryption required by SSL introduces a nominal amount of CPU overhead,
  # which is performed asynchronously in a background thread.  If you'd prefer
  # to send your metrics over http uncomment the following line.
  # ssl: false

  #============================== Browser Monitoring ===============================
  # New Relic Real User Monitoring gives you insight into the performance real users are
  # experiencing with your website. This is accomplished by measuring the time it takes for
  # your users' browsers to download and render your web pages by injecting a small amount
  # of JavaScript code into the header and footer of each page.
  browser_monitoring:
      # By default the agent automatically injects the monitoring JavaScript
      # into web pages. Set this attribute to false to turn off this behavior.
      auto_instrument: true

  # Proxy settings for connecting to the New Relic server.
  #
  # If a proxy is used, the host setting is required.  Other settings
  # are optional. Default port is 8080.
  #
  # proxy_host: hostname
  # proxy_port: 8080
  # proxy_user:
  # proxy_pass:

  # The agent can optionally log all data it sends to New Relic servers to a
  # separate log file for human inspection and auditing purposes. To enable this
  # feature, change 'enabled' below to true.
  # See: https://newrelic.com/docs/ruby/audit-log
  audit_log:
    enabled: false

  # Tells transaction tracer and error collector (when enabled)
  # whether or not to capture HTTP params.  When true, frameworks can
  # exclude HTTP parameters from being captured.
  # Rails: the RoR filter_parameter_logging excludes parameters
  # Java: create a config setting called "ignored_params" and set it to
  #     a comma separated list of HTTP parameter names.
  #     ex: ignored_params: credit_card, ssn, password
  capture_params: true

  # Transaction tracer captures deep information about slow
  # transactions and sends this to the New Relic service once a
  # minute. Included in the transaction is the exact call sequence of
  # the transactions including any SQL statements issued.
  transaction_tracer:

    # Transaction tracer is enabled by default. Set this to false to
    # turn it off. This feature is only available at the Professional
    # and above product levels.
    enabled: true

    # Threshold in seconds for when to collect a transaction
    # trace. When the response time of a controller action exceeds
    # this threshold, a transaction trace will be recorded and sent to
    # New Relic. Valid values are any float value, or (default) "apdex_f",
    # which will use the threshold for an dissatisfying Apdex
    # controller action - four times the Apdex T value.
    transaction_threshold: apdex_f

    # When transaction tracer is on, SQL statements can optionally be
    # recorded. The recorder has three modes, "off" which sends no
    # SQL, "raw" which sends the SQL statement in its original form,
    # and "obfuscated", which strips out numeric and string literals.
    record_sql: obfuscated

    # Threshold in seconds for when to collect stack trace for a SQL
    # call. In other words, when SQL statements exceed this threshold,
    # then capture and send to New Relic the current stack trace. This is
    # helpful for pinpointing where long SQL calls originate from.
    stack_trace_threshold: 0.500

    # Determines whether the agent will capture query plans for slow
    # SQL queries.  Only supported in mysql and postgres.  Should be
    # set to false when using other adapters.
    # explain_enabled: true

    # Threshold for query execution time below which query plans will
    # not be captured.  Relevant only when `explain_enabled` is true.
    # explain_threshold: 0.5

  # Error collector captures information about uncaught exceptions and
  # sends them to New Relic for viewing
  error_collector:

    # Error collector is enabled by default. Set this to false to turn
    # it off. This feature is only available at the Professional and above
    # product levels.
    enabled: true

    # Rails Only - tells error collector whether or not to capture a
    # source snippet around the place of the error when errors are View
    # related.
    capture_source: true

    # To stop specific errors from reporting to New Relic, set this property
    # to comma-separated values.  Default is to ignore routing errors,
    # which are how 404's get triggered.
    ignore_errors: "ActionController::RoutingError,Sinatra::NotFound"

  # If you're interested in capturing memcache keys as though they
  # were SQL uncomment this flag. Note that this does increase
  # overhead slightly on every memcached call, and can have security
  # implications if your memcached keys are sensitive
  # capture_memcache_keys: true

# Application Environments
# ------------------------------------------
# Environment-specific settings are in this section.
# For Rails applications, RAILS_ENV is used to determine the environment.
# For Java applications, pass -Dnewrelic.environment <environment> to set
# the environment.

# NOTE if your application has other named environments, you should
# provide newrelic configuration settings for these environments here.

development:
  <<: *default_settings
  # Turn off communication to New Relic service in development mode (also
  # 'enabled').
  # NOTE: for initial evaluation purposes, you may want to temporarily
  # turn the agent on in development mode.
  monitor_mode: false

  # Rails Only - when running in Developer Mode, the New Relic Agent will
  # present performance information on the last 100 transactions you have
  # executed since starting the mongrel.
  # NOTE: There is substantial overhead when running in developer mode.
  # Do not use for production or load testing.
  developer_mode: true

  # Enable textmate links
  # textmate: true

test:
  <<: *default_settings
  # It almost never makes sense to turn on the agent when running
  # unit, functional or integration tests or the like.
  monitor_mode: false

# Turn on the agent in production for 24x7 monitoring. NewRelic
# testing shows an average performance impact of < 5 ms per
# transaction, you can leave this on all the time without
# incurring any user-visible performance degradation.
production:
  <<: *default_settings
  monitor_mode: true

# Many applications have a staging environment which behaves
# identically to production. Support for that environment is provided
# here.  By default, the staging environment has the agent turned on.
staging:
  <<: *default_settings
  monitor_mode: true
  # app_name: My Application (Staging)

---

## File: config/schedule.yml

refresh_materialized_views:
  cron: "every 5 minutes"
  class: 'RefreshMaterializedViewsJob'
  queue: default

---

## File: config/storage.yml

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
#   service: S3
#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
#   region: us-east-1
#   bucket: your_own_bucket

# Remember not to checkin your GCS keyfile to a repository
# google:
#   service: GCS
#   project: your_project
#   credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
#   bucket: your_own_bucket

# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
#   service: AzureStorage
#   storage_account_name: your_account_name
#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
#   container: your_container_name

# mirror:
#   service: Mirror
#   primary: local
#   mirrors: [ amazon, google, microsoft ]

---

## File: config/locales/devise.en.yml

# Additional translations at https://github.com/plataformatec/devise/wiki/I18n

en:
  devise:
    confirmations:
      confirmed: "Your account was successfully confirmed. You are now signed in."
      send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes."
      send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes."
    failure:
      already_authenticated: "You are already signed in."
      inactive: "Your account was not activated yet."
      invalid: "Invalid email or password."
      invalid_token: "Invalid authentication token."
      locked: "Your account is locked."
      not_found_in_database: "Invalid email or password."
      timeout: "Your session expired, please sign in again to continue."
      unauthenticated: "You need to sign in or sign up before continuing."
      unconfirmed: "You have to confirm your account before continuing.[Didn't receive instructions or need them again?](/users/confirmation/new)"
      banned: "Your user account has been banned"
      deactivated: "Your account is deactivated"
    mailer:
      confirmation_instructions:
        subject: "Confirmation instructions"
      reset_password_instructions:
        subject: "Reset password instructions"
      unlock_instructions:
        subject: "Unlock Instructions"
    omniauth_callbacks:
      failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
      success: "Successfully authenticated from %{kind} account."
    passwords:
      no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
      send_instructions: "You will receive an email with instructions about how to reset your password in a few minutes."
      send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
      updated: "Your password was changed successfully. You are now signed in."
      updated_not_active: "Your password was changed successfully."
    registrations:
      destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon."
      signed_up: "Welcome! You have signed up successfully."
      signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
      signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
      signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account."
      update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address."
      updated: "You updated your account successfully."
    sessions:
      signed_in: "" #"Signed in successfully."
      signed_out: "" #"Signed out successfully."
    unlocks:
      send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes."
      send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes."
      unlocked: "Your account has been unlocked successfully. Please sign in to continue."
  errors:
    messages:
      already_confirmed: "was already confirmed, please try signing in"
      confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
      expired: "has expired, please request a new one"
      not_found: "not found"
      not_locked: "was not locked"
      not_saved:
        one: "1 error prohibited this %{resource} from being saved:"
        other: "%{count} errors prohibited this %{resource} from being saved:"

---

## File: config/locales/devise_invitable.en.yml

en:
  devise:
    failure:
      invited: "You have a pending invitation, accept it to finish creating your account."
    invitations:
      send_instructions: "An invitation email has been sent to %{email}."
      invitation_token_invalid: "The invitation token provided is not valid!"
      updated: "Your password was set successfully. You are now signed in."
      updated_not_active: "Your password was set successfully."
      no_invitations_remaining: "No invitations remaining"
      invitation_removed: "Your invitation was removed."
      new:
        header: "Send invitation"
        submit_button: "Send an invitation"
      edit:
        header: "Set your password"
        submit_button: "Set my password"
    mailer:
      invitation_instructions:
        subject: "Joining The Odin Project Admin Team"
        hello: "Hi %{name}"
        someone_invited_you: "We'd like to invite you to join The Odin Project admin team, you can use the link below to set up your account."
        accept: "Join the team"
        accept_until: "This invitation will be due in %{due_date}."
        ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password."
  time:
    formats:
      devise:
        mailer:
          invitation_instructions:
            accept_until_format: "%B %d, %Y %I:%M %p"

---

## File: config/locales/en.yml

# Files in the config/locales directory are used for internationalization
# and are automatically loaded by Rails. If you want to use locales other
# than English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
#     I18n.t 'hello'
#
# In views, this is aliased to just `t`:
#
#     <%= t('hello') %>
#
# To use a different locale, set it with `I18n.locale`:
#
#     I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# The following keys must be escaped otherwise they will not be retrieved by
# the default I18n backend:
#
# true, false, on, off, yes, no
#
# Instead, surround them with single quotes.
#
# en:
#   'true': 'foo'
#
# To learn more, please read the Rails Internationalization guide
# available at https://guides.rubyonrails.org/i18n.html.

en:
  activerecord:
    errors:
      models:
        user:
          attributes:
            reset_password_token:
              blank: "The password reset link you have used is no longer active or invalid. Password reset links only work one time. <a class='underline text-blue-600 dark:text-blue-400 hover:no-underline' href='/users/password_reset/new'>Please reset your password again.</a>"
              invalid: "The password reset link you have used is no longer active or invalid. Password reset links only work one time. <a class='underline text-blue-600 dark:text-blue-400 hover:no-underline' href='/users/password_reset/new'>Please reset your password again.</a>"
        admin_user:
          attributes:
            current_password:
              invalid: "Incorrect password"
            password_confirmation:
              confirmation: "Password and password confirmation must be the same"
            reset_password_token:
              blank: "The password reset link you have used is no longer active or invalid. Password reset links only work one time Please ask a team member to reset your password again."
              invalid: "The password reset link you have used is no longer active or invalid. Password reset links only work one time. Please ask a team member to reset your password again."

  static_pages:
    about:
        beliefs:
        - title: A full roadmap to becoming a developer
          image_path: about_page/img-education.svg
        - title: Learn by doing
          image_path: about_page/img-build.svg
        - title: Receive support from others
          image_path: about_page/img-people.svg
        - title: Open source and community driven
          image_path: about_page/img-opensource.svg
    contributing:
      contributing_options:
        ways_to_contribute:
          - title: Curriculum
              The curriculum consists of the lessons and projects on this site.
              We are constantly expanding and updating the curriculum to keep it current and
              ensure it meets our students' needs in the ever changing world of web development.
              Our goal is to create the best free web development curriculum in the world, anyone
              is welcome to join in and help us in striving for this goal.
            path: curriculum
          - title: Main Site
              The site you are on right now is a Ruby on Rails application. We are
              always working on new features that improve the platform and help students in
              their learning journey. As with any Open Source project, there is also a constant
              backlog of outstanding issues and bugs that need to be fixed. Anyone is welcome
              to join in and start working on any of the issues we currently have posted in
              the Github repository for the site.
            path: theodinproject
      benefits:
        - title: Flexibility
          image_path: 'contributing/img-clock.svg'
        - title: Experience
          image_path: 'contributing/img-job.svg'
        - title: Impact
          image_path: 'contributing/img-community.svg'
    faq:
      items:
        - question: 'Who is The Odin Project for?'
          answer: |
            The Odin Project is for absolute beginners in the world of web development and those who have tried other resources without success.  In reality, The Odin Project is for anyone who wants to work as a web developer, build a startup, or just hack together a homepage for yourself.  All the tools you need are here. **You should consider yourself fairly competent with personal computers and open to using a non-Windows based Operating System.**
            We've built an in-depth curriculum designed to take you 100% of the way to becoming a web developer. Many other resources out there teach you just the basics of programming. With The Odin Project, you'll create your own websites, games, and social media sites.
        - question: 'What is the goal of The Odin Project?'
          answer:  The goal of The Odin Project is to provide a complete path for students to go from zero programming knowledge to employed as web developers while working with other students and picking up the skills to become self sufficient along the way.
        - question: 'How long does The Odin Project take?'
          answer: |
            We won't have any of this 'Learn it all in two days!' BS.  You can't start from scratch and learn true web development in such a short period of time and you know it.  It's highly unlikely that you'll be employable after a weekend workshop or a one-month part-time course.
            On the other hand, you don't necessarily need to put in 4 years getting a computer science degree.  80% of what they cover won't be used during a typical web developer's early career and it's not necessary to get hired.  So why not learn the 20% and learn the rest while you're getting paid on the job?
            We've essentially distilled down what you most need to learn to hit that employable level, but it's still a healthy dose of learning.  There is no good way to estimate how long it will take you to reach that point, and we don't have any good way of getting data on how long it takes on average anyways.  If you're naturally more technical or come from a technical background, it may be a bit faster.  If you're less technical or brand new to all this, it will take longer.  Don't despair!  When you think about it, that's pretty much the same learning curve you had to climb to learn anything worthwhile so far in life.
        - question: 'Which technologies can one expect to learn from The Odin Project?'
          answer: |
            All students are initially enrolled into the [Foundations Course](paths/foundations/courses/foundations). This will give you a chance to explore several of the languages and technologies you'll need to know on your journey to becoming a web developer. Foundations introduces HTML, CSS, JavaScript, Git and more. We've also created mini-projects that give you a chance to test your new knowledge by building your own applications and websites! Once Foundations is complete, students have two paths to choose from: [Fullstack Ruby on Rails](paths/full-stack-ruby-on-rails) and [Fullstack JavaScript](paths/full-stack-javascript)
            The Full Stack Ruby on Rails path takes a back-to-front approach to fullstack development. We start with a [strong foundation in Ruby](/paths/full-stack-ruby-on-rails/courses/ruby) and an understanding of databases before migrating into [Ruby on Rails](paths/full-stack-ruby-on-rails/courses/ruby-on-rails) (which is a framework built using Ruby). Then you'll polish your front-end skills with a journey into [HTML and CSS](paths/full-stack-ruby-on-rails/courses/advanced-html-and-css) before learning to make your websites flow, load quickly and look great with [JavaScript](paths/full-stack-ruby-on-rails/courses/javascript).
            The Full Stack Javascript path maintains our front-end momentum from Foundations by delving into [Javascript.](paths/full-stack-javascript/courses/javascript) Once you've got a solid foundation here, you'll take a deeper look at front-end design with [HTML and CSS](paths/full-stack-javascript/courses/advanced-html-and-css) before taking your JavaScript skills to the server-side with Express and PostgreSQL in [NodeJS](paths/full-stack-javascript/courses/nodejs).
            Along the way in either path you'll learn Git and deployment so you can build your own portfolio, collaborate with others, and see your work on the web. Finally, The Odin Project will provide you with opportunities to work with legacy codebases and open-source projects. We'll also cover areas specifically intended to help when you're [looking for employment](paths/full-stack-ruby-on-rails/courses/getting-hired).
        - question: 'How is The Odin Project different from other programs?'
          answer: |
            The Odin Project is different from other online programs in that it pulls the best resources from around the web and compiles them to create a complete curriculum. In many other programs, the material is formatted entirely to meet their specific curriculum. This can be nice but often limits the learner's ability to explore and discover other resources, especially technical documentation. The Odin Project sidesteps this downfall by pulling various free resources from around the web and combining them with our own in-house content, not just as "check out these interesting reads" but as intentional additions supplementing our own material. While this often involves a lot of reading, learners get a lot more practice with the resources used and skills needed for professional work.
            The Odin Project also attempts to develop your problem solving skills when it requires you to install programs on your local computer. As developers, programming errors frequently arise and knowing how to decipher error messages is critical, especially within the same environment developers use in the real world. These problem solving skills are frequently ignored by other programs.
            Lastly, The Odin Project is project based. Other programs often require you to simply input the correct answer, but it is difficult to retain any knowledge by just learning the bare minimum syntax. The Odin Project is designed with projects built from the ground up, focusing on building strong fundamentals while giving you real-world experience that you can use to build your portfolio and get hired as a web developer!
        - question: 'Who is "Odin"?'
          answer: |
            Odin is a figure from Norse mythology, the allfather of the gods. He is portrayed as powerful and fickle (as they often are) but also wise and cunning. His search for knowledge is a thing of legends - he [gave his eye for the wisdom of ages](http://norse-mythology.org/tales/why-odin-is-one-eyed) and hung from the world tree, pierced by his own spear, for nine days and nights to [gain the knowledge of runes](http://norse-mythology.org/tales/odins-discovery-of-the-runes/).
            Learning web development is not an easy task and the tools laid out here are merely the path; you must walk it yourself. Should you possess a thirst for knowledge and persistence of spirit akin to that storied in Odin's mythos, you will find success on your journey.
        - question: 'Where do I sign up?'
          answer: |
            You don't have to!  Dive right in, the content is free and available.  It'd be best if you started at the beginning and went through in order but everyone's goals are different so we made the full curriculum available if you want to cherry-pick lessons or projects.
            However, by [signing up](/sign_up) you are able to check off completed projects and lessons, easily keeping track of your progress.  We don't collect any user data and keep emails to a minimum.
        - question: 'Is The Odin Project free?'
          answer: |
            Yes! The Odin Project is an open-source project created and maintained by [hard working volunteers](/team). To find out how you can help us, please visit our how to [contribute page](/contributing).
        - question: 'Will I get a certificate on completion of the course?'
          answer: |
            We don't provide a certificate for course completion. Employers will be much more impressed with your amazing personal portfolio of projects, many of which you'll hopefully have built with inspiration from The Odin Project.
        - question: 'Do you have a Code of Conduct?'
          answer: |
            You can read our [Code of Conduct on GitHub](https://github.com/TheOdinProject/.github/blob/main/CODE_OF_CONDUCT.md).
        - question: 'Can I use this curriculum to teach?'
          answer: |
            The Odin Project is licensed under two pieces: the curriculum and the main website.  The main website is a fully open-source project [under an MIT license](https://github.com/TheOdinProject/theodinproject/blob/main/license.txt), so you can use the code for whatever you want.  The curriculum is currently licensed under a [Creative Commons license](https://github.com/TheOdinProject/curriculum/blob/main/license.md) which restricts it to noncommercial use without prior authorization.
            This means you can use The Odin Project's curriculum to teach at your club, meetup, or with your friends (In fact, we'd love it if you did that and told us how it went!). You can't, however, use it to start a bootcamp without having a conversation first.  Please reach out with any questions to [contact@theodinproject.com](mailto: contact@theodinproject.com)
        - question: 'How can I get in touch?'
          answer: If you have questions about The Odin Project itself, feel free to [contact us](mailto:contact@theodinproject.com). There is also a friendly Odin community in our <a target='_blank' rel='noreferrer' href='https://discord.gg/fbFCkYabZB'>Discord chat rooms.</a>
    home:
      how_it_works:
        items:
          - image: 'img-learn.svg'
            subtitle: 'Learn'
          - image: 'img-build.svg'
            subtitle: 'Build'
          - image: 'img-connect.svg'
            subtitle: 'Connect'

---

## File: config/locales/flag_actions.yml

en:
  flag_actions:
    - value: dismiss
      name: Dismiss
      past_tense: Dismissed

    - value: ban
      name: Ban User
      past_tense: Banned

    - value: removed_project_submission
      name: Remove Project Submission
      past_tense: Project submission removed

    - value: notified_user
      name: Send Broken Link Notification
      past_tense: Notified

---

## File: config/locales/team.yml

# -- Active team member attributes --
# name - required
# image - required
# location - required
# joined - required
# github - optional
# twitter - optional
# linkedin - optional
# website - optional

# -- Alumni attributes --
# name - required
# image - required
# url - required

en:
  team_members:
    core:
      members:
        - name: "Kevin Mulhern"
          image: "https://avatars.githubusercontent.com/u/7963776?v=4"
          location: "Ireland"
          joined: "2015"
          socials:
            - name: "twitter"
              url: "https://twitter.com/KevinMulhern_"
            - name: "github"
              url: "https://github.com/KevinMulhern"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/kevin-mulhern-89532779/"

        - name: "Cody Loyd"
          image: "https://avatars.githubusercontent.com/u/18574792"
          location: "USA"
          joined: "2016"
          socials:
            - name: "website"
              url: "https://codyloyd.com/"
            - name: "github"
              url: "https://github.com/codyloyd"

        - name: "Carlos Diaz"
          image: "https://avatars.githubusercontent.com/u/34174677"
          location: "Southern California, USA"
          joined: "2019"
          socials:
            - name: "twitter"
              url: "https://twitter.com/bycdiaz"
            - name: "github"
              url: "https://github.com/bycdiaz"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/bycdiaz/"

        - name: "Austin Mason"
          image: "https://avatars.githubusercontent.com/u/12649577"
          location: "United Kingdom"
          joined: "2016"
          socials:
            - name: "twitter"
              url: "https://twitter.com/Austin_Mason"
            - name: "github"
              url: "https://github.com/CouchofTomato"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/austinmason/"

        - name: "Rachel Moser"
          image: "https://avatars.githubusercontent.com/u/33227096"
          location: "Illinois, USA"
          joined: "2020"
          socials:
            - name: "github"
              url: "https://github.com/rlmoser99"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/rlmoser99/"

        - name: "Austin Sullivan"
          image: "https://avatars.githubusercontent.com/u/7115053"
          location: "North Carolina, USA"
          joined: "2021"
          socials:
            - name: "github"
              url: "https://github.com/wise-king-sullyman"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/austin-m-sullivan/"

    maintainers:
      members:
        - name: "Manon Voorbrood"
          image: "https://avatars.githubusercontent.com/u/81025586"
          location: "The Netherlands"
          joined: "2022"
          socials:
            - name: "twitter"
              url: "https://twitter.com/Manon_Lef"
            - name: "github"
              url: "https://github.com/ManonLef"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/manon-vb/"

        - name: "Zachary Baird"
          image: "https://avatars.githubusercontent.com/u/30800863"
          location: "The United States"
          joined: "2019"
          socials:
            - name: "github"
              url: "https://github.com/ZachBaird"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/zach-baird/"

        - name: "Zach Meyer"
          image: "https://avatars.githubusercontent.com/u/34407561?v=4"
          location: "Florida, USA"
          joined: "2021"
          socials:
            - name: "twitter"
              url: "https://twitter.com/hateburn"
            - name: "github"
              url: "https://github.com/zachmmeyer"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/zachmmeyer/"

        - name: "Alex Younger"
          image: "https://avatars.githubusercontent.com/u/61510135?v=4"
          location: "USA"
          joined: "2021"
          socials:
            - name: "github"
              url: "https://github.com/fortypercenttitanium"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/aywebdev"
            - name: "website"
              url: "https://ayweb.dev/"

        - name: "Eric Olkowski"
          image: "https://avatars.githubusercontent.com/u/70952936?v=4"
          location: "New Jersey, USA"
          joined: "2021"
          socials:
            - name: "github"
              url: "https://github.com/thatblindgeye"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/ericolkowski/"

        - name: "Josh Smith"
          image: "https://avatars.githubusercontent.com/u/88392688?v=4"
          location: "USA"
          joined: "2023"
          socials:
            - name: "github"
              url: "https://github.com/JoshDevHub"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/joshua-m-smith-dev/"

        - name: "Chook"
          image: "https://avatars.githubusercontent.com/u/58506115?v=4"
          location: "Australia"
          joined: "2022"
          socials:
            - name: "github"
              url: "https://github.com/ChargrilledChook"

        - name: "Mao"
          image: "https://avatars.githubusercontent.com/u/122839503?v=4"
          location: "UK"
          joined: "2024"
          socials:
            - name: "github"
              url: "https://github.com/mao-sz"

        - name: "Fred Love"
          image: "https://avatars.githubusercontent.com/u/48169481?v=4"
          location: "Pennsylvania, USA"
          joined: "2023"
          socials:
            - name: "github"
              url: "https://github.com/justwavethings"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/fredlove000/"

        - name: "Goran Jović"
          image: "https://avatars.githubusercontent.com/u/25707370?v=4"
          location: "Banja Luka, Bosnia and Herzegovina"
          joined: "2026"
          socials:
            - name: "github"
              url: "https://github.com/goran1010"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/goran-jovic-dev/"

    moderators:
      members:
        - name: "Bryan Fines"
          image: "https://avatars.githubusercontent.com/u/49371751?v=4"
          location: "Virginia, USA"
          joined: "2023"
          socials:
            - name: "github"
              url: "https://github.com/BryanF1nes"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/bryan-fines-a44384270/"

        - name: "Emad Ali"
          image: "https://avatars.githubusercontent.com/u/70924991?v=4"
          location: "Germany"
          joined: "2022"
          socials:
            - name: "github"
              url: "https://github.com/Mclilzee"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/mclilzee"

        - name: "Elemeandor"
          image: "https://avatars.githubusercontent.com/u/22915284?s=96&v=4"
          location: "New York, USA"
          joined: "2024"
          socials:
            - name: "github"
              url: "https://github.com/DrantDumani"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/darnell-james/"

        - name: "Rick N"
          image: "https://avatars.githubusercontent.com/u/138608018?v=4"
          location: "Canada"
          joined: "2026"
          socials:
            - name: "github"
              url: "https://github.com/ric-projects"

        - name: "Zack Hoang"
          image: "https://avatars.githubusercontent.com/u/130317776?v=4"
          location: "Canada"
          joined: "2026"
          socials:
            - name: "github"
              url: "https://github.com/ZackHoang"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/viet-anh-hoang/"

        - name: "Lucy G"
          image: "https://avatars.githubusercontent.com/u/112640408?v=4"
          location: "Brazil"
          joined: "2026"
          socials:
            - name: "github"
              url: "https://github.com/LuGodi"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/lulu-g/"

        - name: "Sam"
          image: "https://avatars.githubusercontent.com/u/1546488?v=4"
          location: "Australia"
          joined: "2026"
          socials:
            - name: "github"
              url: "https://github.com/sam-omalley"

        - name: "Zebra🎅"
          image: "https://avatars.githubusercontent.com/u/2159742?v=4"
          location: "Oregon, USA"
          joined: "2026"
          socials:
            - name: "github"
              url: "https://github.com/Falciron"
            - name: "linkedin"
              url: "https://www.linkedin.com/in/aaronmattson"

    alumni:
      members:
        - name: "Erik Trautman (Founder)"
          image: "https://avatars.githubusercontent.com/u/2977438"
          url: "https://github.com/eriktrautman"

        - name: "Arun Kumar Mohan"
          image: "https://avatars.githubusercontent.com/u/7039523?v=4"
          url: "https://github.com/arku"

        - name: "Javier Machín"
          image: "https://avatars.githubusercontent.com/u/13190712"
          url: "https://www.javiermachin.com/"

        - name: "Cornelius Soon"
          image: "https://avatars.githubusercontent.com/u/16495574?v=4"
          url: "https://github.com/csrail"

        - name: "Ada Chiu"
          image: "https://avatars.githubusercontent.com/u/13413986?v=4"
          url: "https://x.com/adachiu_design?s=21&t=7GZYEoXo0zA059kKCNhTdA"

        - name: "Antoni Sobolewski"
          image: "https://avatars.githubusercontent.com/u/95084627?v=4"
          url: "https://github.com/scheals"

        - name: "Valera Kushnir"
          image: "https://avatars.githubusercontent.com/u/12993486?v=4"
          url: "https://github.com/kashura"

        - name: "MacDaniel Maisel"
          image: "https://avatars.githubusercontent.com/u/44100935"
          url: "https://github.com/linkonsat"

        - name: "Benjamin Aguirre"
          image: "https://avatars.githubusercontent.com/u/114883353?v=4"
          url: "https://www.linkedin.com/in/ben-aguirre/"

        - name: "Damon Marlow"
          image: "https://avatars.githubusercontent.com/u/126731021?v=4"
          url: "https://github.com/damon314159"

        - name: "Tosh"
          image: "https://avatars.githubusercontent.com/u/128783853?v=4"
          url: "https://github.com/ItsTosh"

        - name: "Lofty"
          image: "https://avatars.githubusercontent.com/u/100270817?v=4"
          url: "https://github.com/Lofty-Brambles"

---

## File: public/code.txt

<link rel="apple-touch-icon-precomposed" sizes="57x57" href="apple-touch-icon-57x57.png" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="apple-touch-icon-114x114.png" />
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="apple-touch-icon-72x72.png" />
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="apple-touch-icon-144x144.png" />
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="apple-touch-icon-60x60.png" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="apple-touch-icon-120x120.png" />
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="apple-touch-icon-76x76.png" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="apple-touch-icon-152x152.png" />
<link rel="icon" type="image/png" href="favicon-196x196.png" sizes="196x196" />
<link rel="icon" type="image/png" href="favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<link rel="icon" type="image/png" href="favicon-128.png" sizes="128x128" />
<meta name="application-name" content="&nbsp;"/>
<meta name="msapplication-TileColor" content="#FFFFFF" />
<meta name="msapplication-TileImage" content="mstile-144x144.png" />
<meta name="msapplication-square70x70logo" content="mstile-70x70.png" />
<meta name="msapplication-square150x150logo" content="mstile-150x150.png" />
<meta name="msapplication-wide310x150logo" content="mstile-310x150.png" />
<meta name="msapplication-square310x310logo" content="mstile-310x310.png" />

---

## File: public/manifest.json

{
    "name": "The Odin Project",
    "short_name": "The Odin Project",
    "icons": [
        {
            "src": "/android-chrome-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/android-chrome-384x384.png",
            "sizes": "384x384",
            "type": "image/png"
        }
    ],
    "theme_color": "#ffffff",
    "background_color": "#ffffff",
    "display": "standalone"
}

---

## File: public/robots.txt

# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file

Sitemap: http://www.theodinproject.com/sitemap.xml

---

## File: spec/vcr/discord_notification.yml

---
http_interactions:
- request:
    method: post
    uri: https://discordapp.com/api/webhooks/
    body:
      encoding: UTF-8
      string: '{"content":"A MESSAGE","username":null,"avatar_url":null,"tts":false,"embeds":[]}'
    headers:
      Accept:
      - "*/*"
      User-Agent:
      - rest-client/2.1.0 (darwin18.6.0 x86_64) ruby/2.6.1p33
      Content-Type:
      - application/json
      Content-Length:
      - '81'
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
      Host:
      - discordapp.com
  response:
    status:
      code: 204
      message: No Content
    headers:
      Date:
      - Wed, 29 Apr 2020 18:46:25 GMT
      Content-Type:
      - text/html; charset=utf-8
      Content-Length:
      - '0'
      Connection:
      - keep-alive
      Set-Cookie:
      - __cfduid=d3cfd6a0575d48d33d595a984b6e8dff71588185985; expires=Fri, 29-May-20
        18:46:25 GMT; path=/; domain=.discordapp.com; HttpOnly; SameSite=Lax
      - __cfruid=3b09753b5b6f9f153855a35f71c9979fa867f885-1588185985; path=/; domain=.discordapp.com;
        HttpOnly; Secure; SameSite=None
      Strict-Transport-Security:
      - max-age=31536000; includeSubDomains
      X-Ratelimit-Bucket:
      - 3cd1f278bd0ecaf11e0d2391374c011d
      X-Ratelimit-Limit:
      - '5'
      X-Ratelimit-Remaining:
      - '4'
      X-Ratelimit-Reset:
      - '1588185988'
      X-Ratelimit-Reset-After:
      - '2'
      X-Envoy-Upstream-Service-Time:
      - '29'
      Via:
      - 1.1 google
      Cf-Cache-Status:
      - DYNAMIC
      Expect-Ct:
      - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
      Server:
      - cloudflare
      Cf-Ray:
      - 58bb2f89b95bbfed-MAN
      Cf-Request-Id:
      - '0268dc0a0f0000bfed36905200000001'
    body:
      encoding: UTF-8
      string: ''
    http_version: null
  recorded_at: Wed, 29 Apr 2020 18:46:25 GMT
recorded_with: VCR 5.1.0

---

## File: spec/vcr/lesson_content.yml

---
http_interactions:
- request:
    method: get
    uri: https://api.github.com/repos/theodinproject/curriculum/contents/README.md
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - application/vnd.github.v3+json
      User-Agent:
      - Octokit Ruby Gem 6.1.1
      Content-Type:
      - application/json
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 200
      message: OK
    headers:
      Server:
      - GitHub.com
      Date:
      - Tue, 27 Jun 2023 12:18:15 GMT
      Content-Type:
      - application/json; charset=utf-8
      Cache-Control:
      - public, max-age=60, s-maxage=60
      Vary:
      - Accept, Accept-Encoding, Accept, X-Requested-With
      Etag:
      - W/"17f7e8d897a46a85d28a6def793ae67f7f841744"
      Last-Modified:
      - Mon, 26 Jun 2023 14:03:20 GMT
      X-Github-Media-Type:
      - github.v3; format=json
      X-Github-Api-Version-Selected:
      - '2022-11-28'
      Access-Control-Expose-Headers:
      - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
        X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes,
        X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO,
        X-GitHub-Request-Id, Deprecation, Sunset
      Access-Control-Allow-Origin:
      - "*"
      Strict-Transport-Security:
      - max-age=31536000; includeSubdomains; preload
      X-Frame-Options:
      - deny
      X-Content-Type-Options:
      - nosniff
      X-Xss-Protection:
      - '0'
      Referrer-Policy:
      - origin-when-cross-origin, strict-origin-when-cross-origin
      Content-Security-Policy:
      - default-src 'none'
      X-Ratelimit-Limit:
      - '60'
      X-Ratelimit-Remaining:
      - '57'
      X-Ratelimit-Reset:
      - '1687870400'
      X-Ratelimit-Resource:
      - core
      X-Ratelimit-Used:
      - '3'
      Accept-Ranges:
      - bytes
      Content-Length:
      - '3617'
      X-Github-Request-Id:
      - E397:C0CB:1EBD489:1F0DD23:649AD387
    body:
      encoding: ASCII-8BIT
      string: '{"name":"README.md","path":"README.md","sha":"17f7e8d897a46a85d28a6def793ae67f7f841744","size":2031,"url":"https://api.github.com/repos/TheOdinProject/curriculum/contents/README.md?ref=main","html_url":"https://github.com/TheOdinProject/curriculum/blob/main/README.md","git_url":"https://api.github.com/repos/TheOdinProject/curriculum/git/blobs/17f7e8d897a46a85d28a6def793ae67f7f841744","download_url":"https://raw.githubusercontent.com/TheOdinProject/curriculum/main/README.md","type":"file","content":"IyBUaGUgT2RpbiBQcm9qZWN0IEN1cnJpY3VsdW0KCltUaGUgT2RpbiBQcm9q\nZWN0XShodHRwczovL3d3dy50aGVvZGlucHJvamVjdC5jb20vKSAoVE9QKSBp\ncyBhbiBvcGVuLXNvdXJjZSBjdXJyaWN1bHVtIGZvciBsZWFybmluZyBmdWxs\nLXN0YWNrIHdlYiBkZXZlbG9wbWVudC4gT3VyIGN1cnJpY3VsdW0gaXMgZGl2\naWRlZCBpbnRvIGRpc3RpbmN0IGNvdXJzZXMsIGVhY2ggY292ZXJpbmcgdGhl\nIHN1YmplY3QgbGFuZ3VhZ2UgaW4gZGVwdGguIEVhY2ggY291cnNlIGNvbnRh\naW5zIGEgbGlzdGluZyBvZiBsZXNzb25zIGludGVyc3BlcnNlZCB3aXRoIG11\nbHRpcGxlIHByb2plY3RzLiBUaGVzZSBwcm9qZWN0cyBnaXZlIHVzZXJzIHRo\nZSBvcHBvcnR1bml0eSB0byBwcmFjdGljZSB3aGF0IHRoZXkgYXJlIGxlYXJu\naW5nLCB0aGVyZWJ5IHJlaW5mb3JjaW5nIGFuZCBzb2xpZGlmeWluZyB0aGUg\ndGhlb3JldGljYWwga25vd2xlZGdlIGxlYXJuZWQgaW4gdGhlIGxlc3NvbnMu\nIENvbXBsZXRlZCBwcm9qZWN0cyBtYXkgdGhlbiBiZSBpbmNsdWRlZCBpbiB0\naGUgdXNlcidzIHBvcnRmb2xpby4KCkxlc3NvbnMgYXJlIHN0cnVjdHVyZWQg\ndGhyb3VnaCBhIGNvbWJpbmF0aW9uIG9mIG9yaWdpbmFsIHdyaXR0ZW4gY29u\ndGVudCBhbmQgYSBjb21waWxhdGlvbiBvZiBjYXJlZnVsbHkgY3VyYXRlZCBy\nZXNvdXJjZXMgZnJvbSB0aGUgd2ViLiBUaGlzIGlzIHdoZXJlIHRoZSBjb250\ncmlidXRpbmcgaGFwcGVucyEKClRoaXMgcmVwbyBjb250YWlucyB0aGUgYWN0\ndWFsIGxlc3NvbiBmaWxlcyB1c2VkIG9uIG91ciB3ZWJzaXRlLiBGb3IgdGhl\nIGFjdHVhbCBUT1AgYXBwIHRoYXQgcHVsbHMgaW4gdGhpcyBsZXNzb24gY29u\ndGVudCBhbmQgY29udGFpbnMgb3VyIGZyb250LWVuZCBhbmQgYmFjay1lbmQg\nY29kZSwgcGxlYXNlIGdvIHRvIHRoZSBbbWFpbiBUT1AgcmVwb10oaHR0cHM6\nLy9naXRodWIuY29tL1RoZU9kaW5Qcm9qZWN0L3RoZW9kaW5wcm9qZWN0KS4K\nCk91ciBjb21tdW5pdHkgY2FuIGJlIGZvdW5kIG9uIHRoZSBbVE9QIERpc2Nv\ncmQgc2VydmVyXShodHRwczovL2Rpc2NvcmQuZ2cvZmJGQ2tZYWJaQikuCgoj\nIyBDb250cmlidXRpbmcKClRoZSBPZGluIFByb2plY3QgZGVwZW5kcyBvbiBv\ncGVuLXNvdXJjZSBjb250cmlidXRpb25zIHRvIGltcHJvdmUsIGdyb3csIGFu\nZCB0aHJpdmUuIFdlIHdlbGNvbWUgY29udHJpYnV0b3JzIG9mIGFsbCBleHBl\ncmllbmNlIGxldmVscyBhbmQgYmFja2dyb3VuZHMgdG8gaGVscCBtYWludGFp\nbiB0aGlzIGF3ZXNvbWUgY3VycmljdWx1bSBhbmQgY29tbXVuaXR5LiBJZiB5\nb3Ugd291bGQgbGlrZSB0byBjb250cmlidXRlIHRvIG91ciBjdXJyaWN1bHVt\nLCBiZSBzdXJlIHRvIHRob3JvdWdobHkgcmVhZCBvdXIgW2NvbnRyaWJ1dGlu\nZyBndWlkZV0oaHR0cHM6Ly9naXRodWIuY29tL1RoZU9kaW5Qcm9qZWN0L3Ro\nZW9kaW5wcm9qZWN0L2Jsb2IvbWFpbi9DT05UUklCVVRJTkcubWQpIGluIG91\nciBtYWluIFRPUCByZXBvLgoKU29tZSBvZiB0aGUgdGhpbmdzIHlvdSBjYW4g\nZG8gdG8gY29udHJpYnV0ZSB0byBvdXIgY3VycmljdWx1bSBpbmNsdWRlOgoq\nIENvcnJlY3QgdHlwb3MgYW5kIG90aGVyIGdyYW1tYXIgZXJyb3JzLgoqIFJl\nd3JpdGUgcGFydHMgb2YgZXhpc3RpbmcgbGVzc29ucyB0byBtYWtlIHRoZW0g\nY2xlYXJlciBhbmQgZWFzaWVyIHRvIHVuZGVyc3RhbmQuCiogRml4IGJyb2tl\nbiBsaW5rcy4KKiBBZGQgbmV3IHJlc291cmNlIGxpbmtzIHlvdSB0aGluayB3\nb3VsZCBtYWtlIGEgbGVzc29uIGJldHRlci4KKiBXb3JrIG9uIGVudGlyZWx5\nIG5ldyBsZXNzb25zIGFmdGVyIGdldHRpbmcgYXBwcm92YWwuCgoqKkhhcHB5\nIENvZGluZyEqKgoKXCogU2VlIFtsaWNlbnNlLm1kXShodHRwczovL2dpdGh1\nYi5jb20vVGhlT2RpblByb2plY3QvY3VycmljdWx1bS9ibG9iL21haW4vbGlj\nZW5zZS5tZCkgZm9yIHVzYWdlIGRldGFpbHMuCgpfX18KQ3JlYXRlZCBieSBb\nRXJpayBUcmF1dG1hbl0oaHR0cDovL3d3dy5naXRodWIuY29tL2VyaWt0cmF1\ndG1hbikK\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/TheOdinProject/curriculum/contents/README.md?ref=main","git":"https://api.github.com/repos/TheOdinProject/curriculum/git/blobs/17f7e8d897a46a85d28a6def793ae67f7f841744","html":"https://github.com/TheOdinProject/curriculum/blob/main/README.md"}}'
  recorded_at: Tue, 27 Jun 2023 12:18:15 GMT
recorded_with: VCR 6.1.0

---

## File: spec/vcr/update_lesson_content.yml

---
http_interactions:
- request:
    method: get
    uri: https://api.github.com/repos/theodinproject/curriculum/contents//lesson_one_github_url
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - application/vnd.github.v3+json
      User-Agent:
      - Octokit Ruby Gem 8.0.0
      Content-Type:
      - application/json
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 404
      message: Not Found
    headers:
      Server:
      - GitHub.com
      Date:
      - Sun, 28 Jan 2024 17:52:55 GMT
      Content-Type:
      - application/json; charset=utf-8
      X-Github-Media-Type:
      - github.v3; format=json
      X-Github-Api-Version-Selected:
      - '2022-11-28'
      Access-Control-Expose-Headers:
      - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
        X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes,
        X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO,
        X-GitHub-Request-Id, Deprecation, Sunset
      Access-Control-Allow-Origin:
      - "*"
      Strict-Transport-Security:
      - max-age=31536000; includeSubdomains; preload
      X-Frame-Options:
      - deny
      X-Content-Type-Options:
      - nosniff
      X-Xss-Protection:
      - '0'
      Referrer-Policy:
      - origin-when-cross-origin, strict-origin-when-cross-origin
      Content-Security-Policy:
      - default-src 'none'
      Vary:
      - Accept-Encoding, Accept, X-Requested-With
      X-Ratelimit-Limit:
      - '60'
      X-Ratelimit-Remaining:
      - '59'
      X-Ratelimit-Reset:
      - '1706467975'
      X-Ratelimit-Resource:
      - core
      X-Ratelimit-Used:
      - '1'
      Content-Length:
      - '112'
      X-Github-Request-Id:
      - F4FB:A18F2:8998E39:8B5D3C6:65B69477
    body:
      encoding: ASCII-8BIT
      string: '{"message":"Not Found","documentation_url":"https://docs.github.com/rest/repos/contents#get-repository-content"}'
  recorded_at: Sun, 28 Jan 2024 17:52:55 GMT
- request:
    method: get
    uri: https://api.github.com/repos/theodinproject/curriculum/contents//lesson_two_github_url
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - application/vnd.github.v3+json
      User-Agent:
      - Octokit Ruby Gem 8.0.0
      Content-Type:
      - application/json
      Accept-Encoding:
      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
  response:
    status:
      code: 404
      message: Not Found
    headers:
      Server:
      - GitHub.com
      Date:
      - Sun, 28 Jan 2024 17:52:55 GMT
      Content-Type:
      - application/json; charset=utf-8
      X-Github-Media-Type:
      - github.v3; format=json
      X-Github-Api-Version-Selected:
      - '2022-11-28'
      Access-Control-Expose-Headers:
      - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining,
        X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes,
        X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO,
        X-GitHub-Request-Id, Deprecation, Sunset
      Access-Control-Allow-Origin:
      - "*"
      Strict-Transport-Security:
      - max-age=31536000; includeSubdomains; preload
      X-Frame-Options:
      - deny
      X-Content-Type-Options:
      - nosniff
      X-Xss-Protection:
      - '0'
      Referrer-Policy:
      - origin-when-cross-origin, strict-origin-when-cross-origin
      Content-Security-Policy:
      - default-src 'none'
      Vary:
      - Accept-Encoding, Accept, X-Requested-With
      X-Ratelimit-Limit:
      - '60'
      X-Ratelimit-Remaining:
      - '58'
      X-Ratelimit-Reset:
      - '1706467975'
      X-Ratelimit-Resource:
      - core
      X-Ratelimit-Used:
      - '2'
      Content-Length:
      - '112'
      X-Github-Request-Id:
      - F4FC:2344B8:898DB90:8B4EED5:65B69477
    body:
      encoding: ASCII-8BIT
      string: '{"message":"Not Found","documentation_url":"https://docs.github.com/rest/repos/contents#get-repository-content"}'
  recorded_at: Sun, 28 Jan 2024 17:52:55 GMT
recorded_with: VCR 6.2.0

---

## File: .github/dependabot.yml

version: 2
updates:
- package-ecosystem: bundler
  directory: "/"
  schedule:
    interval: weekly
  open-pull-requests-limit: 3
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: weekly
  open-pull-requests-limit: 3

---

## File: .github/PULL_REQUEST_TEMPLATE.md

## Because



## This PR



## Issue

Closes #XXXXX

## Additional Information



## Pull Request Requirements

-   [ ] I have thoroughly read and understand [The Odin Project Contributing Guide](https://github.com/TheOdinProject/theodinproject/blob/main/CONTRIBUTING.md)
-   [ ] The title of this PR follows the `keyword: brief description of change` format, using one of the following keywords:
    - `Feature` - adds new or amends existing user-facing behavior
    - `Chore` - changes that have no user-facing value, refactors, dependency bumps, etc
    - `Fix` - bug fixes
-   [ ] The `Because` section summarizes the reason for this PR
-   [ ] The `This PR` section has a bullet point list describing the changes in this PR
-   [ ] I have verified all tests and linters pass after making these changes.
-   [ ] If this PR addresses an open issue, it is linked in the `Issue` section
-   [ ] If applicable, this PR includes new or updated automated tests

---

## File: .github/ISSUE_TEMPLATE/bug_report.yaml

name: Bug report
title: "Bug: "
labels: "Status: Needs Review, Type: Bug"
body:
  - type: markdown
    attributes:
      value: Thank you for taking the time to submit a bug report to The Odin Project. In order to get issues closed in a reasonable amount of time, you must include a baseline of information about the bug in question.
  - type: checkboxes
    id: checks
    attributes:
      label: Checks
      options:
        - label: I have thoroughly read and understand [The Odin Project Contributing Guide](https://github.com/TheOdinProject/.github/blob/main/CONTRIBUTING.md)
          required: true
        - label: "The title of this issue follows the `Bug: brief description of bug` format, e.g. `Bug: Lesson complete button does not update on click`"
          required: true
        - label: Would you like to work on this issue?
          required: false
  - type: textarea
    id: bug-report
    attributes:
      label: Bug description
      placeholder: ex 'When clicking the complete lesson button at the bottom of the page the lesson doesn't complete'
    validations:
      required: true
  - type: textarea
    id: reproduction-steps
    attributes:
      label: How to reproduce
      placeholder: |
        1. Sign in
        2. Visit a lesson page
        3. Click the complete button
        4. The complete button does not update
    validations:
      required: true
  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected behavior
      placeholder: |
        1. Sign in
        2. Visit a lesson page
        3. Click the complete button
        4. The complete button updates correctly
    validations:
      required: true
  - type: dropdown
    id: browsers
    attributes:
      label: What browsers are you seeing the problem on?
      multiple: true
      options:
        - Firefox
        - Chrome
        - Safari
        - Microsoft Edge
        - Other
  - type: dropdown
    id: os
    attributes:
      label: What OS are you using?
      multiple: true
      options:
        - Windows
        - MacOS
        - Ubuntu
        - Android
        - iOS
  - type: input
    id: contact
    attributes:
      label: Discord Name
      placeholder: ex. odinite
    validations:
      required: false
  - type: textarea
    id: additional-comments
    attributes:
      label: Additional Comments
      placeholder: We ❤️ open source
    validations:
      required: false

---

## File: .github/ISSUE_TEMPLATE/chore.yaml

name: Chore
title: "Chore: "
labels: "Status: Needs Review, Type: Chore"
body:
  - type: markdown
    attributes:
      value: Thank you for taking the time to submit a chore to The Odin Project. In order to get issues closed in a reasonable amount of time, you must include a baseline of information about the chore in question.
  - type: checkboxes
    id: checks
    attributes:
      label: Checks
      options:
        - label: I have thoroughly read and understand [The Odin Project Contributing Guide](https://github.com/TheOdinProject/.github/blob/main/CONTRIBUTING.md)
          required: true
        - label: "The title of this issue follows the `Chore: brief description of chore` format, e.g. `Chore: Lesson complete button does not update on click`"
          required: true
        - label: Would you like to work on this issue?
          required: false
  - type: textarea
    id: chore-report
    attributes:
      label: Chore description
      placeholder: ex 'Refactor some buttons to use button components'
    validations:
      required: true
  - type: textarea
    id: acceptance-criteria
    attributes:
      label: Acceptance criteria
      placeholder: |
        - [ ] Create new button components
        - [ ] Replace all old buttons with the new components
        - [ ] Remove all styles that are now unused
    validations:
      required: true
  - type: textarea
    id: additional-comments
    attributes:
      label: Additional Comments
      placeholder: We ❤️ open source
    validations:
      required: false

---

## File: .github/ISSUE_TEMPLATE/config.yml

blank_issues_enabled: true
contact_links:
  - name: 📖 Curriculum content
    url: https://github.com/TheOdinProject/curriculum
    about: "This repository is for the underlying website. For changes to the content of TOP check out the curriculum repository"
  - name: 💡 Ideas, Feature Requests and Questions
    url: https://github.com/TheOdinProject/theodinproject/discussions
    about: "Have an idea? Think a feature should be added? Open a discussion here"
  - name: 💬 Join the Discord Community
    url: https://discord.gg/fbFCkYabZB
    about: "Outside of GitHub, the Odin community lives on Discord. Join our server here"

---

## File: .github/ISSUE_TEMPLATE/feature_request.yaml

name: Feature Request
title: "Feature Request: "
labels: "Status: Needs Review"
body:
  - type: markdown
    attributes:
      value: |
        Thank you for taking the time to submit a new feature request to The Odin Project.

        In order to get issues closed in a reasonable amount of time, you must include a baseline of information about the feature/enhancement you are proposing. Please read this template in its entirety before filling it out to ensure that it is filled out correctly.
  - type: checkboxes
    id: checks
    attributes:
      label: Checks
      options:
        - label: I have thoroughly read and understand [The Odin Project Contributing Guide](https://github.com/TheOdinProject/.github/blob/main/CONTRIBUTING.md)
          required: true
        - label: "The title of this issue follows the `Feature Request: brief description of feature request` format, e.g. `Feature Request: Add a dark mode to the website`"
          required: true
        - label: Would you like to work on this issue?
          required: false
  - type: textarea
    id: chore-report
    attributes:
      label: Description of the Feature Request
        A clear and concise description of what the feature or enhancement is, including how it would be useful/beneficial or what problem(s) it would solve.

        When suggesting an entirely new feature, it can help to provide a statement that follows the "When EVENT occurs, I want SOMETHING to happen, to achieve RESULT" format.

      placeholder: ex "When I visit the TOP website, I want to be able to view it in dark mode, because its easier on my eyes."
    validations:
      required: true
  - type: textarea
    id: acceptance-criteria
    attributes:
      label: Acceptance criteria
      placeholder: |
        - [ ] A theme toggle is present on the dashboard
        - [ ] Clicking the theme toggle changes between light and dark
        - [ ] A user's theme choice persists after leaving the website
    validations:
      required: true
  - type: textarea
    id: additional-comments
    attributes:
      label: Additional Comments
      placeholder: We ❤️ open source
    validations:
      required: false

---

## File: .github/workflows/ci.yml

name: CI

on:
  pull_request:
    branches: ["*"]
  push:
    branches: [main]

concurrency: ci-${{ github.ref }}

jobs:
  linters:
    name: Linters
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 20
          cache: yarn

      - name: Install packages
        run: |
          yarn install

      - name: Run linters
        run: |
          bin/rubocop --parallel
          bin/erb_lint --lint-all
          yarn run lint
          yarn run stylelint

  tests:
    name: Tests
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        # Set N number of parallel jobs you want to run tests on.
        # Use higher number if you have slow tests to split them on more parallel jobs.
        # Remember to update ci_node_index below to 0..N-1
        ci_node_total: [4]
        # set N-1 indexes for parallel jobs
        # When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc
        ci_node_index: [0, 1, 2, 3]
    services:
      postgres:
        image: postgres:15.7
        ports:
          - 5432:5432
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres

      redis:
        image: redis
        ports: ["6379:6379"]

    env:
      RAILS_ENV: test
      POSTGRES_USERNAME: postgres
      POSTGRES_PASSWORD: postgres
      SKIP_YARN_INSTALL: true
      CI_NODE_INDEX: ${{ matrix.ci_node_index }}
      CI_NODE_TOTAL: ${{ matrix.ci_node_total }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Prevent https://github.com/rails/rails/issues/53661
        run: touch tmp/local_secret.txt && openssl rand -hex 64 > tmp/local_secret.txt

      - name: Setup Ruby and install gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 20
          cache: yarn

      - name: Install packages
        run: |
          yarn install

      - name: Build assets
        run: |
          bin/rails javascript:build
          bin/rails css:build

      - name: Setup test database
        run: |
          bundle exec rake parallel:create
          bundle exec rake parallel:load_schema
          bundle exec rake parallel:migrate

      - name: Run tests
        run: ./bin/ci_spec

---

## File: .github/workflows/heroku-review-app-on-label.yml

name: Heroku Review App on label
on:
  pull_request:
    types: [labeled]

jobs:
  create-review-app:
    if: ${{ github.event.label.name == 'create-review-app' }}
    runs-on: ubuntu-latest

    steps:
      - uses: fastruby/manage-heroku-review-app@v1.3
        with:
          action: create
        env:
          HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }}
          HEROKU_PIPELINE_ID: ${{ secrets.HEROKU_PIPELINE_ID }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: fastruby/pr-unlabeler@v1
        with:
          label-to-remove: "create-review-app"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  destroy-review-app:
    if: ${{ github.event.label.name == 'destroy-review-app' }}
    runs-on: ubuntu-latest

    steps:
      - uses: fastruby/manage-heroku-review-app@v1.3
        with:
          action: destroy
        env:
          HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }}
          HEROKU_PIPELINE_ID: ${{ secrets.HEROKU_PIPELINE_ID }}

      - uses: fastruby/pr-unlabeler@v1
        with:
          label-to-remove: "destroy-review-app"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

--- METRICS ---
- Files Extracted: 36
- Estimated Token Budget: ~22898 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/TheOdinProject/theodinproject
